Packages

Composable, native Elixir CAD with sketches, assemblies, and verified printable exports

Current section

Files

Jump to
smith examples drawings.livemd
Raw

examples/drawings.livemd

# Draw and export a mechanical part

```elixir
Mix.install([{:smith, "~> 0.2.0"}, {:kino, "~> 0.19.0"}])
```

## Before you start

After [mechanical parts](mechanical-parts.livemd), 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.

```elixir
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.

```elixir
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.

```elixir
{"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.

```elixir
{: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.

```elixir
{"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)
```

<!-- <div class="smith-doc-preview" data-preview="drawing-book-measured" data-model="measured" data-label="Measured mounting plate"></div> -->

## 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.

```elixir
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.