Wrap
Wrap takes a flat sketch and bends it onto a cylindrical or conical face — like a label on a bottle — then raises it from the surface by a thickness. It is the tool for embossed logos, engraved text, and any feature that has to follow a curved wall.
In the viewport
- Sketch the decal on a plane tangent to (or parallel to) the target face, then click Wrap on the toolbar.
- Fill in the dialog and click the face to wrap onto.
- Click Apply.

- 1Add / Remove / NewAdd embosses the decal on the surface, Remove engraves it into the surface, New keeps the wrapped pad as a body of its own.
- 2SketchThe flat artwork to bend onto the surface, filled with the last sketch. Its plane decides where the decal lands — see the placement rules below.
- 3Target faceThe face to wrap onto. It has to be cylindrical or conical; clicking any other face leaves the slot empty.
- 4ThicknessHow far the decal stands off the surface, measured along the surface normal. On the Remove tab it is the engraving depth instead.
- 5ApplyWrites the statement and adds the timeline row; Exit writes nothing.
From flat sketch to curved decal

a flat rectangleA Ø25 bottle and a flat 30 × 14 rectangle on a plane tangent to it — the artwork as drawn.

wrap(2, decal, target)The rectangle bent around the bottle and raised 2 off the surface. It is still 30 mm wide — measured along the curve now, not across the chord.
The bottle and the decal
import { sketch, plane, cylinder, line } from 'fluidcad/core';
import { coincident, distance, fix, horizontal, vertical } from "fluidcad/constraints";
// The bottle: a Ø25 cylinder, 60 tall.
cylinder(25, 60);
// The decal, still flat: a 30 x 14 rectangle on a plane tangent to the
// cylinder (front plane offset by the radius).
sketch(plane("front", 25), () => {
const sg1 = line([5, 23], [35, 23]);
const sg2 = line([35, 23], [35, 37]);
const sg3 = line([35, 37], [5, 37]);
const sg4 = line([5, 37], [5, 23]);
coincident(sg1.end(), sg2.start());
coincident(sg2.end(), sg3.start());
coincident(sg3.end(), sg4.start());
coincident(sg4.end(), sg1.start());
horizontal(sg1);
vertical(sg2);
horizontal(sg3);
vertical(sg4);
fix(sg1.start(), [5, 23]);
distance(sg1.start(), sg1.end(), 30);
distance(sg2.start(), sg2.end(), 14);
});
The complete file
import { sketch, plane, select, wrap, cylinder, line } from 'fluidcad/core';
import { face } from 'fluidcad/filters';
import { coincident, distance, fix, horizontal, vertical } from "fluidcad/constraints";
// The bottle: a Ø25 cylinder, 60 tall.
cylinder(25, 60);
// The face to wrap onto — the dialog's Cylindrical face slot.
const target = select(face().cylinder());
// The decal: a 30 x 14 rectangle on a plane tangent to the cylinder
// (front plane offset by the radius). Sketch lengths are kept along the
// surface, so 30 wide stays 30 of arc.
const decal = sketch(plane("front", 25), () => {
const sg1 = line([5, 23], [35, 23]);
const sg2 = line([35, 23], [35, 37]);
const sg3 = line([35, 37], [5, 37]);
const sg4 = line([5, 37], [5, 23]);
coincident(sg1.end(), sg2.start());
coincident(sg2.end(), sg3.start());
coincident(sg3.end(), sg4.start());
coincident(sg4.end(), sg1.start());
horizontal(sg1);
vertical(sg2);
horizontal(sg3);
vertical(sg4);
fix(sg1.start(), [5, 23]);
distance(sg1.start(), sg1.end(), 30);
distance(sg2.start(), sg2.end(), 14);
});
// Raise the decal 2 off the surface: thickness, sketch, target face.
wrap(2, decal, target);
In code the arguments are the thickness, the sketch and the target face:
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 30 mm wide rectangle becomes exactly 30 mm of arc. Text stays readable at any width, and a sketch wide enough goes 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 does not 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 code behind it
import { sketch, plane, text, line, 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), () => {
// The text flows along a guide line starting at [0, 24].
const path = line([0, 24], [60, 24]).guide();
text("FLUID", path).size(12);
});
wrap(1, decal, target);
Engraving
The Remove tab sinks the sketch into the surface instead of raising it — the thickness becomes the engraving depth:

The code behind it
import { sketch, plane, text, line, 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), () => {
// The text flows along a guide line starting at [0, 24].
const path = line([0, 24], [60, 24]).guide();
text("FLUID", path).size(12);
});
wrap(1, decal, target).remove();
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:

The code behind it
import { sketch, plane, select, wrap, cylinder, line } from 'fluidcad/core';
import { face } from 'fluidcad/filters';
import { coincident, distance, fix, horizontal, vertical } from "fluidcad/constraints";
cylinder(25, 60);
const target = select(face().cylinder());
const decal = sketch(plane("front", 25), () => {
const sg1 = line([2, 20], [38, 20]);
const sg2 = line([38, 20], [38, 40]);
const sg3 = line([38, 40], [2, 40]);
const sg4 = line([2, 40], [2, 20]);
const sg5 = line([7, 25], [33, 25]);
const sg6 = line([33, 25], [33, 35]);
const sg7 = line([33, 35], [7, 35]);
const sg8 = line([7, 35], [7, 25]);
coincident(sg1.end(), sg2.start());
coincident(sg2.end(), sg3.start());
coincident(sg3.end(), sg4.start());
coincident(sg4.end(), sg1.start());
horizontal(sg1);
vertical(sg2);
horizontal(sg3);
vertical(sg4);
fix(sg1.start(), [2, 20]);
distance(sg1.start(), sg1.end(), 36);
distance(sg2.start(), sg2.end(), 20);
coincident(sg5.end(), sg6.start());
coincident(sg6.end(), sg7.start());
coincident(sg7.end(), sg8.start());
coincident(sg8.end(), sg5.start());
horizontal(sg5);
vertical(sg6);
horizontal(sg7);
vertical(sg8);
fix(sg5.start(), [7, 25]);
distance(sg5.start(), sg5.end(), 26);
distance(sg6.start(), sg6.end(), 10);
});
wrap(2, decal, target);
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:

The code behind it
import { sketch, plane, line, arc, revolve, select, wrap } from 'fluidcad/core';
import { coincident, horizontal, tangent, equal, radius, fix } from 'fluidcad/constraints';
import { face } from 'fluidcad/filters';
sketch("xz", () => {
const b = line([0, 0], [30, 0]);
const s = line([30, 0], [20, 50]);
const t = line([20, 50], [0, 50]);
const l = line([0, 50], [0, 0]);
coincident(b.end(), s.start());
coincident(s.end(), t.start());
coincident(t.end(), l.start());
coincident(l.end(), b.start());
});
revolve("z");
const target = select(face().cone());
const decal = sketch(plane("front", 26), () => {
// A slot profile: two parallel lines closed by semicircular caps.
const bottom = line([-8, 21], [8, 21]);
const capR = arc([8, 21], [8, 29], [8, 25]);
const top = line([8, 29], [-8, 29]);
const capL = arc([-8, 29], [-8, 21], [-8, 25]);
coincident(bottom.end(), capR.start());
coincident(capR.end(), top.start());
coincident(top.end(), capL.start());
coincident(capL.end(), bottom.start());
horizontal(bottom);
horizontal(top);
tangent(bottom, capR);
tangent(top, capL);
equal(capR, capL);
radius(capR, 4);
});
wrap(1.5, decal, target);
Fusion scope
Wrap is a boolean operation with the usual scope controls — the dialog's three tabs plus .scope() in code:
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.