Smith.Assembly (Smith v0.1.0)

Copy Markdown View Source

Named solid parts and reference geometry with explicit placement.

An assembly is a deferred value. Add members with part/4 and reference/4, then call Smith.evaluate/1. Equal member recipes are evaluated once per assembly evaluation; each member gets its own placement. Use subassembly/4 to place another assembly and retain its members. Equal leaf recipes share one evaluation across the entire tree. There is no global cache. Named attachment frames and directed connections provide rigid, revolute, linear, cylindrical, and ball poses; closed linkages are not solved.

iex> foot = Smith.box(10, 10, 4)
iex> recipe =
...>   Smith.Assembly.new(:pair)
...>   |> Smith.Assembly.part(:left, foot)
...>   |> Smith.Assembly.part(:right, foot, position: {20, 0, 0})
iex> {:ok, assembly} = Smith.evaluate(recipe)
iex> {:ok, solids} = OCEx.solids(assembly.shape)
iex> length(solids)
2

The evaluated compound contains installed manufactured parts, without fusing them. References and installed: false extras are available through fetch/2 but excluded from that compound. All members must contain at least one solid.

See assemblies for print placement, separate part exports, metadata, and assembly reports.

Summary

Functions

Connects a moving attachment frame to a target frame.

Returns a member in final world coordinates.

Returns a named joint's evaluated world frame and owning member path.

Defines a named attachment frame on an immediate assembly member.

Lists all leaf members in depth-first insertion order.

Creates an empty named assembly recipe.

Adds a named manufactured part to an assembly recipe.

Adds named reference geometry that is excluded from print output.

Adds a named instance of another assembly.

Builds a geometry snapshot of an evaluated assembly for inspection.

Types

member_path()

@type member_path() :: atom() | String.t() | [atom() | String.t()]

t()

@type t() :: %Smith.Assembly{
  connections: [map()],
  entries: [map()],
  joints: [map()],
  name: atom() | String.t()
}

Functions

connect(assembly, source, opts)

@spec connect(t(), member_path(), keyword()) :: t()

Connects a moving attachment frame to a target frame.

source and required to: are joint names or paths to joints in nested instances. The source's top-level member moves as a rigid unit, including all descendants, references, and extras. The target stays attached to its member. Matching frames align both normals and X directions; there is no implicit face-to-face reversal. Use opposing local planes when needed. The connection overrides the source member's initial placement.

Motion options

kind:CoordinatesMeaning
:rigid (default)noneCoincident frames
:revoluteangle:Rotation around target local Z, degrees
:linearoffset:Translation along target local Z, mm
:cylindricalangle:, offset:Both motions
:ballangles: {x, y, z}Rotations about fixed target X, then Y, then Z, degrees

All coordinates default to zero. Optional limits: supplies inclusive {min, max} pairs keyed by :angle/:offset, or by :x/:y/:z for a ball. Unspecified limits are unbounded. Out-of-range values return :joint_limit; they are not clamped.

Each moving member may have one connection. Dependencies are resolved in target-first order, independent of declaration order. Self-connections, multiple connections for one member, and cycles fail explicitly. Connections inside a subassembly resolve before parent-level connections move that instance. This calculates a pose, not a physical simulation or general constraint solver; collisions, clearances, loads, and closed linkages are not solved.

fetch(result, name)

@spec fetch(Smith.Assembly.Result.t(), member_path()) ::
  {:ok, Smith.Result.t() | Smith.Assembly.Result.t()} | {:error, :unknown_part}

Returns a member in final world coordinates.

Works for manufactured parts, uninstalled printable extras, and references. Atom and string names match after underscores become hyphens. Unknown names return {:error, :unknown_part}. Print and display transforms do not affect the fetched geometry.

A subassembly returns a Smith.Assembly.Result; a leaf returns a Smith.Result. Use a list such as [:left, :foot] or a slash path such as "left/foot" to address descendants. Fetching from a returned subassembly uses paths relative to it, but its geometry remains in world coordinates. Empty paths and descent through a leaf return :unknown_part.

iex> recipe =
...>   Smith.Assembly.new(:mount)
...>   |> Smith.Assembly.part(:left_foot, Smith.box(2, 3, 4),
...>     position: {10, 0, 0}
...>   )
iex> {:ok, assembly} = Smith.evaluate(recipe)
iex> {:ok, foot} = Smith.Assembly.fetch(assembly, "left-foot")
iex> OCEx.bounds(foot.shape)
{:ok, {{10.0, 0.0, 0.0}, {12.0, 3.0, 4.0}}}
iex> Smith.Assembly.fetch(assembly, :missing)
{:error, :unknown_part}

fetch_joint(result, name)

@spec fetch_joint(Smith.Assembly.Result.t(), member_path()) ::
  {:ok, Smith.Assembly.Joint.t()} | {:error, :unknown_joint}

Returns a named joint's evaluated world frame and owning member path.

Accepts a name, slash path, or list as in fetch/2. Nested lookup prefixes the owner path but leaves the world frame unchanged. Unknown joints return {:error, :unknown_joint}. The returned Smith.Assembly.Joint is a snapshot; reevaluate the recipe to change its pose.

joint(assembly, name, opts)

@spec joint(t(), atom() | String.t(), keyword()) :: t()

Defines a named attachment frame on an immediate assembly member.

Required on: names a part, reference, or subassembly at this level. at: is a Smith.Plane or :xy, :xz, or :yz; it defaults to :xy. The frame is in that member's original recipe coordinates, before its placement. Its normal is local Z and its X direction fixes rotational alignment. Names are unique in the assembly's joint namespace.

Frames follow their members through placement and connections. Refer to a child assembly's joint with a path such as [:module, :pin]. Joint definitions and connections are validated when the assembly is evaluated.

members(result)

@spec members(Smith.Assembly.Result.t()) :: {:ok, [map()]}

Lists all leaf members in depth-first insertion order.

Returns {:ok, members}. Each map contains normalized :path segments, a slash-separated :key, the original leaf :name, :kind (:part or :reference), its world-space :result, :options, and effective :installed. A manufactured leaf is installed only if it and all its ancestor instances are installed; references are never installed manufactured geometry.

Options include accumulated world display/exploded offsets and effective installed status. Print options are unchanged. This is the same leaf view used by assembly export. Neither enumeration nor lookup evaluates recipes.

new(name)

@spec new(atom() | String.t()) :: t()

Creates an empty named assembly recipe.

Names are atoms or strings beginning with an ASCII letter or digit, followed by letters, digits, underscores, or hyphens. Validation occurs at evaluation. Add at least one installed part before evaluating; otherwise the result is {:error, :no_installed_parts}.

part(assembly, name, recipe, opts \\ [])

@spec part(t(), atom() | String.t(), Smith.Model.t(), keyword()) :: t()

Adds a named manufactured part to an assembly recipe.

recipe must be a Smith.Model that evaluates to geometry containing at least one solid. Multiple solids are allowed. Names follow new/1 and must be unique across parts and references, after replacing underscores with hyphens. Names remain case-sensitive.

Options

  • :position — translation in the parent frame {x, y, z} in mm, default zero.
  • :rotation{axis_vector, degrees} about the parent-frame origin, applied before :position. Default: no rotation.
  • :installed — default true. Set false for a printable extra excluded from the installed compound and assembly preview.
  • :print — keyword list controlling print placement, described below.
  • :display_offset — world offset for the exported preview mesh, default zero. Applied by view/2 in display and exploded modes.
  • :exploded_offset — world offset stored in export metadata, default zero. Added to display placement by view/2 in exploded mode. Rendering the assembly directly still shows installed geometry.

Print placement starts from the installed shape. Within :print, :rotation uses {axis_vector, degrees} about world zero, followed by :offset (a world translation). Alternatively, on_bed: true centers the rotated bounds in XY and places minimum Z at zero. It cannot be combined with :offset. Defaults apply no print transform.

Options are checked during evaluation; unknown or duplicate keys fail with a Smith.Error identifying this part. Print placement is applied only at export, so geometrically invalid print transforms can fail there.

reference(assembly, name, recipe, opts \\ [])

@spec reference(t(), atom() | String.t(), Smith.Model.t(), keyword()) :: t()

Adds named reference geometry that is excluded from print output.

Accepts the same names and solid-containing model recipes as part/4, but only its :position and :rotation options. References are evaluated and can fail an assembly build. Retrieve one with fetch/2.

References are absent from the installed compound and its Kino preview. When STEP is requested, export writes them into a separate references-DO-NOT-PRINT.step. They never enter the print pack.

subassembly(assembly, name, recipe, opts \\ [])

@spec subassembly(t(), atom() | String.t(), t(), keyword()) :: t()

Adds a named instance of another assembly.

Child positions and rotations are relative to this instance. Each child rotates before translating; the parent transform applies afterwards. The same subassembly can be reused under different names and placements. Names must be unique among siblings, including parts and references.

Options are :position, :rotation, :installed, :display_offset, and :exploded_offset, with the defaults from part/4. Setting installed: false excludes the whole branch from the parent's installed shape; manufactured leaves still get print files. References remain references at every depth.

Print options belong to leaf parts and operate on their final world shapes. Display and exploded offsets are world vectors: offsets at ancestor and leaf levels add without rotation. They affect export metadata, not geometry. Retrieve an instance or leaf with fetch/2; enumerate leaves with members/1. A subassembly must contain an installed manufactured part when evaluated on its own. Invalid recipes return :invalid_subassembly.

view(result, mode \\ :installed)

@spec view(Smith.Assembly.Result.t(), :installed | :display | :exploded) ::
  {:ok, Smith.Result.t()} | {:error, term()}

Builds a geometry snapshot of an evaluated assembly for inspection.

Returns {:ok, %Smith.Result{}}, suitable for Smith.Kino.render/2. The mode selects which manufactured parts and offsets to show:

  • :installed (default) reuses the installed shape and its revision.
  • :display includes all manufactured leaves, including uninstalled extras, and applies their accumulated :display_offset.
  • :exploded adds accumulated :exploded_offset to the display offset.

Nested placement and joint connections have already been resolved. Offsets are world vectors; parent and leaf offsets add without rotation. References are excluded in all modes. Fetch a reference with fetch/2 to inspect it.

This does not reevaluate recipes or apply print transforms. It preserves the assembly and returns an unfused snapshot with a matching BREP revision. Export the original assembly to retain part names, separate files, and print placement; the view is only a combined shape, without assembly metadata.

Revision and native geometry failures return tagged errors. An unsupported mode returns :invalid_options; an unevaluated input returns :invalid_argument.