Skip to main content

Wrap

wrap() takes a flat sketch and bends it onto a curved face — like applying a label to a bottle. The sketch is developed onto the surface, preserving every length and angle, then raised from the surface by the given thickness. It's the tool for embossed logos, engraved text, and any feature that has to follow a cylindrical or conical wall.

import { sketch, plane, rect, move, select, wrap, cylinder } from 'fluidcad/core';
import { face } from 'fluidcad/filters';

cylinder(25, 60);
const target = select(face().cylinder());

const decal = sketch(plane("front", 25), () => {
move([5, 23]);
rect(30, 14);
});

wrap(2, decal, target);

Basic wrap

The first argument is the pad thickness, measured along the surface normal. The middle argument is the sketch to wrap, and the last argument is the target face — typically a select(face().cylinder()) or select(face().cone()) selection. Both the sketch and the face are required:

wrap(2, decal, target) // wrap the sketch `decal` onto the face `target`

True wrap, not a projection

A projection would squash the sketch as the surface curves away. wrap() instead unrolls the surface flat, places the sketch on it, and rolls it back up — a 30mm-wide rectangle becomes exactly 30mm of arc. Text stays readable at any width, and a sketch wide enough would go all the way around the target (up to one full turn).

The placement follows the sketch plane:

  • The sketch plane's origin "touches down" on the nearest point of the surface — sketch coordinates are measured from there along the unrolled surface.
  • The direction of the surface axis maps to the sketch plane's matching in-plane direction, so for the usual setup — a plane tangent to (or offset from) the cylinder — the sketch wraps exactly as drawn.

The sketch plane doesn't need to touch the surface: its offset is ignored, only its origin and orientation matter.

Text decals

Text wraps glyph by glyph with no distortion — the classic engraved-bottle look:

import { sketch, plane, text, move, select, wrap, cylinder } from 'fluidcad/core';
import { face } from 'fluidcad/filters';

cylinder(25, 60);
const target = select(face().cylinder());

const decal = sketch(plane("front", 25), () => {
move([0, 24]);
text("FLUID").size(12);
});

wrap(1, decal, target);

Wrapped text

Engraving

Chain .remove() to sink the sketch into the surface instead of raising it — the thickness becomes the engraving depth:

import { sketch, plane, text, move, select, wrap, cylinder } from 'fluidcad/core';
import { face } from 'fluidcad/filters';

cylinder(25, 60);
const target = select(face().cylinder());

const decal = sketch(plane("front", 25), () => {
move([0, 24]);
text("FLUID").size(12);
});

wrap(1, decal, target).remove();

Engraved text

Holes

Sketch regions keep their holes: nest a profile inside another and the inner region is subtracted, walls and all. A frame with a window:

import { sketch, plane, rect, move, select, wrap, cylinder } from 'fluidcad/core';
import { face } from 'fluidcad/filters';

cylinder(25, 60);
const target = select(face().cylinder());

const decal = sketch(plane("front", 25), () => {
move([2, 20]);
rect(36, 20);
move([7, 25]);
rect(26, 10);
});

wrap(2, decal, target);

Wrapped frame with a window

Conical faces

Cones unroll into a fan rather than a flat strip, and wrap() accounts for that — features curve with the cone's development, staying true to their sketched dimensions along the surface:

import { sketch, plane, line, revolve, slot, select, wrap } from 'fluidcad/core';
import { face } from 'fluidcad/filters';

sketch("xz", () => {
line([0, 0], [30, 0]);
line([30, 0], [20, 50]);
line([20, 50], [0, 50]);
line([0, 50], [0, 0]);
});
revolve("z");

const target = select(face().cone());

const decal = sketch(plane("front", 26), () => {
slot([-8, 25], [8, 25], 4);
});

wrap(1.5, decal, target);

Slot wrapped onto a cone

Fusion scope

wrap() is a boolean operation with the usual scope controls:

wrap(2, decal, target) // fuse with all touching solids (default)
wrap(2, decal, target).remove() // engrave instead of emboss
wrap(2, decal, target).new() // keep the pad as a separate solid
wrap(2, decal, target).add().scope(c) // fuse only with `c`

Region picking

Like extrude(), the sketch is partitioned into regions first. Use .pick() to wrap only the regions containing the given points, or .drill(false) to disable hole drilling:

wrap(2, decal, target).pick([5, 25]) // only the region containing this point
wrap(2, decal, target).drill(false) // treat nested profiles as separate regions

Accessing geometry

const w = wrap(2, decal, target)

w.startFaces() // faces lying on the target surface (the pad's base)
w.endFaces() // raised (or engraved) faces offset by the thickness
w.sideFaces() // walls around each region's outer boundary
w.internalFaces() // walls of holes inside a region
w.startEdges() // edges on the base faces
w.endEdges() // edges on the offset faces
w.sideEdges() // edges along the outer walls
w.internalEdges() // edges along the hole walls

Limitations

  • The target must be a cylindrical or conical face — the only surfaces a sketch can wrap onto without distortion. For other faces, consider extrude() up to a face instead.
  • The sketch cannot be wider than one full turn around the target.
  • The sketch plane must not be perpendicular to the target's axis (there would be no direction to wrap around), and its origin must not lie on the axis.