Smith.Sketch (Smith v0.1.0)

Copy Markdown View Source

Deferred planar outlines with optional cutouts and corner rounding.

Build an outline with rectangle/3, circle/2, polygon/2, or profile/2. Coordinates are local to a Smith.Plane; standalone sketches default to XY at Z=0. Distances are millimeters.

iex> outline =
...>   Smith.Sketch.rectangle(40, 20) |> Smith.Sketch.cut(Smith.Sketch.circle(3))
iex> {:ok, face} = Smith.evaluate(outline)
iex> OCEx.shape_type(face.shape)
{:ok, :face}

Use Smith.extrude/2, Smith.revolve/4, Smith.loft/2, or Smith.sweep/3 to build solid model recipes. A bare sketch evaluates to one face and cannot be exported as a printable bundle.

Sketches have no constraint solver. Their local coordinates, dimensions, and cuts come from ordinary Elixir calculations. Validation runs when evaluated, and cuts must retain one connected face. Rounding belongs to the original outline and is always applied before its cuts.

See sketches and planes for coordinate conventions and examples of side planes, holes, and solid generation.

Summary

Functions

Returns a directed circular-arc description for profile/2.

Describes a circle by radius in local millimeters.

Subtracts a sketch or ordered list of sketches from this region.

Sets a common corner radius on the original rectangle or convex polygon.

Returns a directed line description for profile/2.

Places a sketch on a new plane, preserving its local coordinates.

Describes a closed polygon from at least three local {u, v} points.

Describes a closed outline made from local lines, circular arcs, and interpolated splines.

Describes a rectangle with a width and height in local millimeters.

Describes a rectangle with four circular corner rounds of radius mm.

Describes a straight slot along local X with semicircular ends.

Returns a nonperiodic interpolated B-spline description for profile/2.

Types

t()

@type t() :: %Smith.Sketch{
  cuts: [term()],
  data: term(),
  kind: atom(),
  options: keyword(),
  plane: Smith.Plane.t() | :default,
  rounding: keyword() | :none
}

Functions

arc(center, radius, start, sweep)

@spec arc({number(), number()}, number(), number(), number()) :: tuple()

Returns a directed circular-arc description for profile/2.

center is a local {u, v} point and radius is in millimeters. start is measured from local +X in degrees. Positive sweep turns counterclockwise from local +X toward +Y; negative sweep reverses the turn.

At evaluation, radius must exceed 1.0e-7 mm, and the absolute sweep must be greater than 1.0e-9 and at most 360 degrees. This returns an arc description tuple, not a sketch or native edge.

circle(radius, opts \\ [])

@spec circle(number(), keyword()) :: t()

Describes a circle by radius in local millimeters.

Radius must exceed the native tolerance of 1.0e-7 mm at evaluation. Supports the options of rectangle/3, defaulting to centered alignment. :at therefore places the circle center unless another alignment is chosen. align: :none retains a center at local zero before adding :at.

The sketch evaluates to a disk face. Use cut/2 to make a ring, or Smith.extrude/2 to make a cylinder.

cut(sketch, tools)

@spec cut(t(), t() | [t()]) :: t()

Subtracts a sketch or ordered list of sketches from this region.

A cutter without an explicit plane inherits the parent's frame, including in nested cuts. Its :at is relative to that frame's origin, not the parent's anchor or center. Explicit cutter planes must be coplanar; there is no projection from another plane.

Cuts may overlap, contain holes, reach the outside edge, or miss the parent. Repeated subtraction does not remove material twice. An empty list returns the original sketch. After every cut the result must be one face: removing it entirely fails with :empty_sketch, splitting it fails with :disconnected_sketch, and mismatched planes fail with :non_coplanar_sketches.

iex> ring = Smith.Sketch.circle(5) |> Smith.Sketch.cut(Smith.Sketch.circle(3))
iex> {:ok, part} = ring |> Smith.extrude(2) |> Smith.evaluate()
iex> {:ok, volume} = OCEx.volume(part.shape)
iex> abs(volume - 32 * :math.pi()) < 1.0e-6
true

fillet(sketch, opts)

@spec fillet(t(), keyword()) :: t()

Sets a common corner radius on the original rectangle or convex polygon.

Accepts only radius:, a positive distance in millimeters. Each corner is replaced by a tangent circular arc; adjacent arcs must leave a positive straight segment. Concave, collinear, degenerate, or overlarge fillets fail with :invalid_fillet. Circles and authored arc profiles return :unsupported_sketch_fillet.

Calling this again replaces the earlier radius. Rounding is applied before all sketch cuts, regardless of pipeline order; it does not round corners introduced by a cutter. For edges on an evaluated solid recipe use Smith.fillet/2.

line(from, to)

@spec line({number(), number()}, {number(), number()}) :: tuple()

Returns a directed line description for profile/2.

from and to are local {u, v} points. Their separation must exceed 1.0e-7 mm when evaluated. This returns {:line, from, to}, not a sketch or a native edge.

on(sketch, plane)

@spec on(t(), Smith.Plane.t()) :: t()

Places a sketch on a new plane, preserving its local coordinates.

Overrides both its original :on option and any earlier call to on/2. The original sketch is unchanged. Cutters without an explicit plane inherit this new frame. Explicitly placed cutters retain their world placement and must still be coplanar.

iex> outline = Smith.Sketch.rectangle(4, 6)
iex> model =
...>   outline |> Smith.Sketch.on(Smith.Plane.yz(x: 10)) |> Smith.extrude(2)
iex> {:ok, part} = Smith.evaluate(model)
iex> {:ok, {x, y, z}} = OCEx.center_of_mass(part.shape)
iex> abs(x - 11) < 1.0e-6 and abs(y) < 1.0e-6 and abs(z) < 1.0e-6
true

polygon(points, opts \\ [])

@spec polygon([{number(), number()}], keyword()) :: t()

Describes a closed polygon from at least three local {u, v} points.

Closure is implicit; do not repeat the first point. The outline must enclose nonzero area and form a valid face. Concave polygons are allowed without fillet/2.

Supports :on, :at, and :align as in rectangle/3. Alignment defaults to :none: supplied coordinates are retained, then shifted by :at. Explicit alignment uses the bounds of those coordinates.

profile(edges, opts \\ [])

@spec profile([tuple()], keyword()) :: t()

Describes a closed outline made from local lines, circular arcs, and interpolated splines.

Supply a nonempty list of line/2, arc/4, and spline/2 descriptions in connected boundary order. Options are :on and :at as in rectangle/3. Coordinates are retained and shifted by :at; :align is not accepted. Open, disconnected, and invalid boundaries fail during evaluation.

iex> sketch =
...>   Smith.Sketch.profile([
...>     Smith.Sketch.arc({0, 0}, 2, 0, 180),
...>     Smith.Sketch.line({-2, 0}, {2, 0})
...>   ])
iex> {:ok, face} = Smith.evaluate(sketch)
iex> {:ok, area} = OCEx.area(face.shape)
iex> abs(area - 2 * :math.pi()) < 1.0e-6
true

rectangle(width, height, opts \\ [])

@spec rectangle(number(), number(), keyword()) :: t()

Describes a rectangle with a width and height in local millimeters.

Both dimensions must be positive; native size tolerances apply at evaluation. The default bounds are centered on local {0, 0}.

Options

  • :on — a Smith.Plane. A standalone sketch defaults to XY at Z=0.
  • :at — local anchor {u, v}, default {0, 0}.
  • :align{x_alignment, y_alignment}, each :min, :center, or :max. Defaults to {:center, :center}. The chosen point of the local bounds lands at :at.

align: :none uses the unaligned rectangle {0, 0}..{width, height}, then adds :at. Unknown/duplicate options return :invalid_sketch_options at evaluation; unsupported alignments return :invalid_alignment.

iex> sketch = Smith.Sketch.rectangle(4, 6, align: {:min, :max}, at: {10, 20})
iex> {:ok, face} = Smith.evaluate(sketch)
iex> OCEx.bounds(face.shape)
{:ok, {{10.0, 14.0, 0.0}, {14.0, 20.0, 0.0}}}

rounded_rectangle(width, height, radius, opts \\ [])

@spec rounded_rectangle(number(), number(), number(), keyword()) :: t()

Describes a rectangle with four circular corner rounds of radius mm.

Supports the placement options of rectangle/3. Equivalent to a rectangle followed by fillet/2; rounding precedes subsequent cutouts. Radius must be positive and smaller than half either dimension, allowing for native tolerance. Use slot/3 for semicircular ends meeting at the full width.

slot(length, width, opts \\ [])

@spec slot(number(), number(), keyword()) :: t()

Describes a straight slot along local X with semicircular ends.

length is the overall end-to-end dimension; width is the diameter of each end, both in mm. Length must be at least width, and width must exceed 1.0e-7 mm. A nonzero straight span (length minus width) must also exceed the native edge tolerance of 1.0e-7 mm. Equal dimensions produce a circle. Smaller length or invalid dimensions fail with :invalid_sketch at evaluation.

Supports :on, :at, and :align as in rectangle/3, defaulting to centered alignment. align: :none also retains a centered outline. Use cut/2 to subtract this sketch from another outline.

spline(points, tangents \\ nil)

@spec spline(
  [{number(), number()}],
  {{number(), number()}, {number(), number()}} | nil
) :: tuple()

Returns a nonperiodic interpolated B-spline description for profile/2.

Supply at least two distinct local {u, v} points in traversal order. The curve passes through these points, not through a control polygon. Optional tangents is a pair of nonzero local direction vectors at the first and last point. Directions are mapped through the sketch plane without translation; OCCT chooses their derivative magnitudes.

The curve need not remain inside the points' bounds. Invalid points, tangents, and degenerate interpolation fail during sketch evaluation. Close the outline with other edges before extruding or sweeping it.