Draw and export a mechanical part
Mix.install([{:smith, "~> 0.4.0"}, {:kino, "~> 0.19.0"}])
Before you start
After mechanical parts, make orthographic views of a counterbored plate, inspect hidden holes, and add a measured dimension. Orthographic views have no perspective scaling.
Run cells in order. Lengths are millimeters and modeling angles are degrees. Each 3D preview supports orbit, zoom, and fullscreen.
Build a counterbored plate
The outline has 3 mm corners. Two through holes have 8 mm head recesses, 3 mm deep. A third, smaller hole stops 4 mm below the top. An explicit entry plane keeps the hole coordinates fixed as cuts change the top face.
alias Smith.{Drawing, Plane, Sketch}
blank = Sketch.rounded_rectangle(50, 30, 3) |> Smith.extrude(8)
entry = Plane.xy(z: 8)
plate = for x <- [-15, 15], reduce: blank do
body -> Smith.counterbore(body, on: entry, at: {x, 0},
diameter: 4, bore_diameter: 8, bore_depth: 3, through: :all)
end
plate = Smith.hole(plate, on: entry, at: {0, 8}, diameter: 3, depth: 4)
{:ok, part} = Smith.evaluate(plate)
{:ok, volume} = OCEx.volume(part.shape)
expected = (50 * 30 - (4 - :math.pi()) * 9) * 8 -
2 * :math.pi() * (4 * 8 + (16 - 4) * 3) - :math.pi() * 2.25 * 4
true = abs(volume - expected) < 1.0e-5
Smith.Kino.render(part, label: "Counterbores and blind locating hole")
Compare three views
The normal points toward the viewer. XY looks along −Z; XZ looks along +Y with world Z pointing up on the page. The custom plane is oblique but still orthographic: distant edges do not get smaller.
planes = [{"Top", :xy}, {"Front", :xz},
{"Oblique", Plane.new(normal: {1, -1, 1}, x_direction: {1, 1, 0})}]
drawings = for {name, plane} <- planes do
{:ok, drawing} = Drawing.new(part, on: plane)
{name, drawing}
end
drawings |> Enum.map(fn {name, drawing} ->
Kino.Layout.grid([Kino.Markdown.new("**#{name}**"), Smith.Kino.render(drawing, label: name, tolerance: 0.01)])
end) |> Kino.Layout.grid(columns: 3)
Gray dashed lines are hidden features. Visible lines are painted above them. These static images complement the rotatable 3D preview. SVG dimensions are in millimeters; Livebook may scale the image to fit its output area.
Inspect the hidden features
The front view reveals the bores inside the plate. Native curves remain available for queries; sampled polylines are ordinary lists of local XY points. Lowering the sampling tolerance changes those lists, not the original geometry.
{"Front", front} = List.keyfind(drawings, "Front", 0)
{:ok, lines} = Drawing.polylines(front, tolerance: 0.01)
true = lines.hidden != []
Kino.DataTable.new([
%{layer: "visible", edges: length(lines.visible)},
%{layer: "hidden", edges: length(lines.hidden)}
])
Hide dashed lines and show tangent boundaries
hidden: false omits hidden geometry at export. tangents: true affects native
drawing construction: it includes smooth G1 boundaries between faces. Seams
within a periodic surface are omitted in both cases.
{:ok, tangent_view} = Drawing.new(part, on: :xz, tangents: true)
{:ok, clean_svg} = Drawing.svg(tangent_view, hidden: false, title: "Visible boundaries")
Smith.Kino.render(tangent_view, hidden: false, label: "Visible boundaries")
Add a measured width
The label comes from evaluated geometry. dimension/3 keeps the measurement tied
to this revision, and render/2 fits the SVG to the notebook area. Fullscreen makes
small details readable; downloading the SVG preserves its millimeter units.
{"Top", top} = List.keyfind(drawings, "Top", 0)
{:ok, width} = Smith.Measure.extent(part, :x)
{:ok, measured} = Drawing.dimension(top, width, orientation: :horizontal, offset: -8)
Smith.Kino.render(measured, label: "Measured mounting plate", hidden: false)
Export views and printable geometry
The drawing directory includes the source geometry revision. SVG uses polylines with physical mm dimensions and a margin; DXF uses LWPOLYLINE entities in mm on VISIBLE/HIDDEN layers. DXF keeps upward Y and has no SVG margin. The unannotated views can be exchanged as SVG or DXF. Dimensions are exported separately as SVG; annotated DXF is not supported. Neither writer joins cutting contours or generates toolpaths. Check scale in the receiving application.
directory = Path.join("output/drawings", part.revision)
File.mkdir_p!(directory)
{:ok, _} = Drawing.write(measured, Path.join(directory, "measured.svg"), hidden: false)
paths = for {name, drawing} <- drawings, extension <- ["svg", "dxf"] do
path = Path.join(directory, String.downcase(name) <> "." <> extension)
{:ok, ^path} = Drawing.write(drawing, path, tolerance: 0.01)
%{view: name, format: extension, path: path}
end
{:ok, printable} = Smith.export(part, "output/models", name: "drawing-plate",
on_bed: true, angular_tolerance: 0.1)
true = printable.verification.mesh.watertight
Kino.DataTable.new(paths)
STL and 3MF are separate checked exports of the solid. For an assembly drawing,
pass an assembly result to Drawing.new/2; installed parts occlude one another.
Use Assembly.view(result, :exploded) for a drawing of that explicit pose.