Packages

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

Current section

Files

Jump to
smith examples assembly.livemd
Raw

examples/assembly.livemd

# A named assembly and print pack

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

## Before you start

After [mechanical parts](mechanical-parts.livemd), make two instances of a reusable foot and place reference hardware above them. An assembly keeps these parts separately named and printable.

Run cells in order. Lengths are millimeters and modeling angles are degrees.
Each 3D preview supports orbit, zoom, and fullscreen.


## Define a reusable part

Use ordinary functions for features and parameters. Both instances below reuse the
same recipe, which Smith evaluates once within this assembly evaluation. The
rounded sketch sets the footprint; an explicit entry plane fixes the counterbore
at its center. The 3 mm recess leaves 5 mm of material below the screw head.

```elixir
defmodule NotebookParts do
  def foot(width, depth, height) do
    Smith.Sketch.rounded_rectangle(width, depth, 1, align: {:min, :min})
    |> Smith.extrude(height)
    |> Smith.counterbore(on: Smith.Plane.xy(z: height), at: {width / 2, depth / 2},
      diameter: 5, bore_diameter: 8, bore_depth: 3, through: :all)
  end
end
foot = NotebookParts.foot(12, 16, 8)
Smith.Kino.render(foot, label: "1 · Reusable foot")
```

## Place two instances

Installed geometry and printable orientation are independent. References are
available for inspection but excluded from manufactured geometry and print packs.

```elixir
alias Smith.Assembly
pair =
  Assembly.new(:support)
  |> Assembly.part(:left, foot, position: {-20, 0, 0}, print: [on_bed: true])
  |> Assembly.part(:right, foot, position: {20, 0, 0}, print: [on_bed: true],
    exploded_offset: {0, 0, 16})
  |> Assembly.reference(:device, Smith.box(28, 16, 6), position: {-8, 0, 8})
{:ok, result} = Smith.evaluate(pair)
Smith.Kino.render(result, label: "2 · Installed feet")
```

## Inspect the members

Use `Assembly.members/1` to list evaluated leaves without reaching into the
recipe's internal fields. Each member has a path, a kind, an installed flag,
and its evaluated result. References are listed too, but are never manufactured
parts. The same function traverses nested assemblies.

```elixir
{:ok, members} = Assembly.members(result)
2 = Enum.count(members, &(&1.kind == :part))
1 = Enum.count(members, &(&1.kind == :reference))

members
|> Enum.map(fn member ->
  %{path: member.key, kind: member.kind, installed: member.installed}
end)
|> Kino.DataTable.new()
```

## Inspect the exploded view

Create a view from the evaluated assembly. The right foot moves up by its declared
exploded offset; its installed and print placements stay unchanged. The view is
a combined geometry snapshot. Keep the original assembly result for export.

```elixir
{:ok, exploded} = Assembly.view(result, :exploded)
Smith.Kino.render(exploded, label: "3 · Exploded feet")
```

## Inspect a reference

```elixir
{:ok, device} = Assembly.fetch(result, :device)
Smith.Kino.render(device, label: "Reference · Device")
```

## Export the complete assembly

Each manufactured part gets verified STL and 3MF files. The bundle also includes
installed STEP geometry, a separate reference STEP, and a ZIP of printable parts.

```elixir
{:ok, solids} = OCEx.solids(result.shape)
2 = length(solids)
{:ok, files} = Smith.export(result, "output/livebook", name: "support")
files
```

For a reusable group of parts, use `Assembly.subassembly/4`. The [nested assembly notebook](nested-assemblies.livemd) covers parent/child placement, path lookup, printable extras, and per-leaf exports.