Projection transfers boundary curves to target surfaces, similar to casting the outline of a shape onto another surface. Start with workplanes and sketches. It does not intersect filled sketch material with a body. Use it to place an outline on a face, measure a curve on a curved surface, or create a planar profile for another operation. Coordinates stay in world space.
Parallel projection
alias Smith.{Plane, Selector, Sketch}
source = Sketch.circle(4, on: Plane.xy(z: 12))
inclined = Plane.new(normal: {1, 0, 1}, x_direction: {1, 0, -1})
target = Sketch.rectangle(30, 30, on: inclined)
outline = Smith.project(source, target, direction: {0, 0, -1})
{:ok, projected} = Smith.evaluate(outline)
{:ok, [_wire]} = OCEx.wires(projected.shape)Interactive preview available in HexDocs.
Interactive preview available in HexDocs.
Interactive preview available in HexDocs.
The source circle lies above the target plane z = -x. Its projection is an
ellipse on that plane. The direction is normalized, so its magnitude does not
set a travel distance. Parallel projection is bidirectional: changing {0,0,-1}
to {0,0,1} does not select a different side.
Fill a planar outline
profile = Smith.face(outline)
{:ok, face_result} = Smith.evaluate(profile)
{:ok, area} = OCEx.area(face_result.shape)
true = abs(area - 16 * :math.pi() * :math.sqrt(2)) < 1.0e-6
part = profile |> Smith.extrude({0, 0, 3})
{:ok, result} = Smith.evaluate(part)
{:ok, volume} = OCEx.volume(result.shape)
true = abs(volume - 48 * :math.pi()) < 1.0e-6Interactive preview available in HexDocs.
Interactive preview available in HexDocs.
face/1 accepts one closed planar wire. It preserves world placement and returns
a face recipe that composes with extrusion, revolution, and Boolean operations.
It does not guess which projected boundaries are holes or pick one of several
surface hits. Multiple wires return :wrong_shape_type, open wires return
:open_wire, and nonplanar boundaries cannot be filled by this operation.
Project through a point
enlarged = Sketch.circle(2, on: Plane.xy(z: 5))
|> Smith.project(Sketch.rectangle(20, 20), from: {0, 0, 10})
|> Smith.face()
{:ok, enlarged_result} = Smith.evaluate(enlarged)
{:ok, area} = OCEx.area(enlarged_result.shape)
true = abs(area - 16 * :math.pi()) < 1.0e-6Interactive preview available in HexDocs.
This conical projection follows half-rays from the point through the source. The target is twice as far from the point as the source plane, so the radius doubles from 2 to 4 mm. Targets between the point and source can also be hit; targets behind the point are excluded. Source curves passing through the point, or other degenerate configurations, may fail.
Exactly one of :direction and :from is required. There is no implicit view
direction or perspective point.
Choose the target surface
body = Smith.box(20, 20, 4, align: {:center, :center, :min})
top = Smith.surface(body, Selector.facing(:z))
top_outline = Smith.project(source, top, direction: {0, 0, -1})
{:ok, top_result} = Smith.evaluate(top_outline)
{:ok, [_wire]} = OCEx.wires(top_result.shape)Interactive preview available in HexDocs.
Interactive preview available in HexDocs.
Projecting onto the whole box instead would retain both the top and bottom hits. Projection does not sort intersections by distance or choose a visible surface. Selecting target faces first makes that choice explicit. Native targets may be faces, shells, solids, or compounds of those types; free edges are rejected.
Curved surfaces and clipping
wall = Smith.cylinder(5, 10) |> Smith.surface(Selector.type(:cylinder))
curves = Smith.line({-3, -10, 5}, {3, -10, 5})
|> Smith.project(wall, direction: {0, 1, 0})
{:ok, curves_result} = Smith.evaluate(curves)
{:ok, wires} = OCEx.wires(curves_result.shape)
2 = length(wires)
{:ok, length} = OCEx.length(curves_result.shape)
true = abs(length - 20 * :math.asin(3 / 5)) < 1.0e-5Interactive preview available in HexDocs.
Interactive preview available in HexDocs.
The line produces an arc on each side of the cylinder. Inspect them with
Smith.edges/2, Smith.inspect_edges/2, or OCEx.edge_sample/2. The projected
curves remain exact native geometry; this operation does not use a mesh.
Target boundaries and holes clip curves, so a closed source can yield open or
disconnected results. A sketch contributes all its boundary wires, including
holes. A completely enclosing outline that misses the target boundary does not
fill the target. A missed or failed source boundary returns :projection_failed;
collections fail if any boundary fails. Successful partial intersections remain
valid output. If a swept source curve lies on a target surface, the intersection
is not an isolated curve: OCCT can return boundaries of the coincident region.
Avoid coincident or tangent configurations when a unique projected curve is needed.
Preview and export
Kino renders faces and solids as triangle surfaces, and wire-only results as sampled curves. Preview each stage or inspect exact geometry with curve queries. Wires can be exported as BREP/STEP through OCEx, but printable STL/3MF require solid geometry.
{:ok, files} = Smith.export(result, "output", name: "projected-cap", on_bed: true,
angular_tolerance: 0.1)
true = files.verification.mesh.watertightInteractive preview available in HexDocs.
The projection Livebook shows these stages and exports a two-part print pack. Orthographic drawings and hidden-line removal are separate operations; this API projects source curves onto supplied target geometry.