Packages

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

Current section

Files

Jump to
smith examples inspection.livemd
Raw

examples/inspection.livemd

# Inspect a model as you build it

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

## What you will learn

A valid solid can still be the wrong shape. Measure evaluated geometry, state the
requirements explicitly, and keep each report tied to the revision it checked.
These tools work in ordinary Elixir scripts; neither vision nor Livebook is required.
This lesson uses one plate to measure hole spacing, detect an overhanging feature,
compare removed material, and produce a dimensioned drawing. Read
[A plate, step by step](plate.livemd) first. A terminal-only version is available
in `examples/inspection.exs`.

## Name the geometry you want to inspect

Names refer to snapshots you supply. They can describe complete parts, intermediate
stages, or selected subshapes. They do not promise persistent face identities after
an edit. Reevaluate the model and rebuild selections when dimensions change.

```elixir
alias Smith.{Drawing, Inspection, Measure, Plane, Selector}

plate = Smith.box(40, 24, 6)
{:ok, plate_result} = Smith.evaluate(plate)

holes = [Smith.cylinder(2, 8, at: {10, 12, -1}), Smith.cylinder(2, 8, at: {30, 12, -1})]
{:ok, drilled} = plate_result |> Smith.from_result() |> Smith.cut(holes) |> Smith.evaluate()
Smith.Kino.render(drilled, view: :top, edges: true)
```

<div class="smith-doc-preview" data-preview="inspection-book-drilled" data-model="drilled" data-label="Plate with measured holes">
<p>Interactive preview available in HexDocs.</p>
</div>

## Query before selecting

Topology inspection returns metadata rather than native handles. Pages include
source revision, total matches, and the next offset. Native topology order is not
stable across revisions. Prefer a geometric selector to a remembered face index.

```elixir
{:ok, page} = Inspection.topology(drilled, :edges, selector: Selector.type(:circle), limit: 10)

left = fn edge ->
  edge.type == :circle and abs(elem(edge.center, 0) - 10) < 1.0e-6 and
    abs(elem(edge.center, 2) - 6) < 1.0e-6
end

right = fn edge ->
  edge.type == :circle and abs(elem(edge.center, 0) - 30) < 1.0e-6 and
    abs(elem(edge.center, 2) - 6) < 1.0e-6
end

{:ok, spacing} = Measure.distance(drilled, {:circle_center, left}, {:circle_center, right}, axis: :x)
{:ok, diameter} = Measure.diameter(drilled, left)
{:ok, width} = Measure.extent(drilled, :x)
Kino.DataTable.new(page.items)
```

A measurement selector must match exactly one item. An ambiguous match is an
error, not permission to choose the first edge. Circle centers and radii come
from native curves, including arcs; they are not inferred from mesh vertices.

## State requirements and read the result

```elixir
{:ok, report} = Inspection.run(%{plate: drilled}, checks: [
  {:topology, :plate, :solids, expected: 1},
  {:bounds, :plate, {{0, 0, 0}, {40, 24, 6}}, tolerance: 1.0e-6},
  {:measurement, :plate, spacing, expected: 20, tolerance: 1.0e-6},
  {:measurement, :plate, diameter, expected: 4, tolerance: 1.0e-6}
])

:passed = report.status
{:ok, json} = Inspection.json(report)
Kino.DataTable.new(Enum.map(report.checks, &Map.take(&1, [:kind, :status, :measured, :expected, :unit])))
```

`{:ok, report}` means inspection ran. `report.status` tells you whether the
requirements passed. A failed requirement has `status: :failed`, with its measured
value, expectation, units, and tolerance. A kernel error, unknown name, or stale
measurement is `:error`. An empty or failed section must not be treated as evidence
that there is no material.

The JSON contains no native handles. Model summaries include revisions, native
bounds, area, volume, and topology counts. A text-only agent can use these values
to decide what to change and rerun the same requirements afterward.

## Find unwanted material and interference

```elixir
ledge = Smith.box(4, 10, 1, at: {38, 7, 5})
{:ok, failed} = Inspection.run(%{plate: plate_result, ledge: ledge}, checks: [
  {:contained, :ledge, :plate, tolerance: 1.0e-7}
])

:failed = failed.status
[excess] = failed.checks
IO.inspect({excess.measured, excess.unit, excess.bounds}, label: "Outside material")
ledge_scene = [{plate_result, {130, 150, 170}}, {ledge, {210, 55, 45}}]
Smith.Kino.render(ledge_scene, edges: true)
```

<div class="smith-doc-preview" data-preview="inspection-book-ledge" data-model="ledge_scene" data-label="Overhanging feature">
<p>Interactive preview available in HexDocs.</p>
</div>

This reports 20 mm³ outside the plate. Containment measures volume for solids,
area for faces, and length for curves. Its tolerance uses those units. To catch a
supported feature, inspect its bottom face against the supporting body: any exposed
area is useful evidence even when the final union is a valid solid.

For mating parts, use `{:clearance, :pin, :socket_body, minimum: 0.1,
tolerance: 1.0e-6}`. It measures material distance and also checks common volume.
A pin contained inside solid material has zero distance, but that is interference,
not a successful zero-clearance fit. Linear and volume tolerances are separate.

## Save views and a report

```elixir
{:ok, artifacts} = Inspection.write(report, "output/inspection",
  views: [:isometric, :top, :front, :right],
  sections: [Plane.xz(y: 12)],
  width: 640,
  height: 480
)
IO.puts(artifacts.report)
```

Each run gets its own directory and a JSON manifest. Images reference both the
source and rendered geometry revisions. Failed containment checks also produce
images of the excess material. These files are also usable by agents without Livebook. Read the returned manifest rather than selecting
an old image by its filename. An image generation failure does not write a final
report manifest; partial image files can remain in that run's directory.

`Smith.Render.write/3` writes an individual PNG. It uses an orthographic software
depth buffer, with no GPU, browser, display server, or additional renderer install.
Top looks from +Z, front from −Y, and right from +X. Named views use world axes,
not inferred product orientation. Pass a `Smith.Plane` for another convention.
Images use tessellation. Mesh deflection, numerical measurement tolerance,
manufacturing tolerance, and printer compensation are different quantities.

## Compare modeling stages

```elixir
{:ok, change} = Inspection.compare(plate_result, drilled)
IO.inspect({change.added_mm3, change.removed_mm3}, label: "Stage material changes")

layers = [
  {drilled, {130, 150, 170}},
  {change.removed, {210, 55, 45}},
  {change.added, {45, 170, 90}}
]
Smith.Kino.render(layers, view: :isometric, edges: true)
```

<div class="smith-doc-preview" data-preview="inspection-book-change" data-model="layers" data-label="Removed material in red">
<p>Interactive preview available in HexDocs.</p>
</div>

The removed material is red and added material green. The same layer list works
with `Smith.Render.png/2`. These are opaque solids, so orbit or clip the scene to
inspect occluded regions. Full BREP differences can be expensive for coincident
curved faces; they are intentionally stronger than comparing total volumes.

## Put measured dimensions on a drawing

```elixir
{:ok, drawing} = Drawing.new(drilled, on: :xy)
{:ok, drawing} = Drawing.dimension(drawing, width, orientation: :horizontal, offset: -6)
{:ok, drawing} = Drawing.dimension(drawing, spacing, orientation: :horizontal, offset: 18)
{:ok, drawing} = Drawing.dimension(drawing, diameter, offset: 5)
{:ok, svg} = Drawing.svg(drawing, hidden: false, title: "Measured mounting plate")
Smith.Kino.render(drawing, label: "Measured mounting plate", hidden: false)
```

<div class="smith-doc-preview" data-preview="inspection-book-dimensions" data-model="svg" data-label="Dimensions measured from geometry">
<p>Dimensioned SVG available in HexDocs.</p>
</div>

Dimensions retain source revision, measured value, and anchors. A mismatched
revision or foreshortened projection is rejected. Linear, radius, diameter, and
angular dimensions are supported. SVG includes extension lines, arrows, and
center marks; placement is explicit. There is no automatic label layout or sketch
constraint solver. Annotated DXF currently returns `:unsupported_annotations`.

## Working with agents

Give an agent the requirements, coordinate convention, source drawing or model,
and the location of its output directory. Ask it to keep assumptions separate
from measurements and to return the inspection manifest with its changes.

The [agent modeling guide](https://hexdocs.pm/smith/agent-modeling.html) is available as plain Markdown
through ExDoc's `llms.txt`. Smith also ships `skills/smith-cad/SKILL.md` for harnesses
that support skills. Documentation and skills help an agent choose the tools;
the numerical checks provide evidence that its model meets the requirements.