Project curves onto surfaces

Copy Markdown View Source

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-6

Interactive 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-6

Interactive 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-5

Interactive 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.watertight

Interactive 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.