Skip to main content

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

  1. Sketch the decal on a plane tangent to (or parallel to) the target face, then click Wrap on the toolbar.
  2. Fill in the dialog and click the face to wrap onto.
  3. Click Apply.
The Wrap dialog with a sketch, a picked cylindrical face and a thickness of 2
  1. 1
    Add / Remove / New
    Add embosses the decal on the surface, Remove engraves it into the surface, New keeps the wrapped pad as a body of its own.
  2. 2
    Sketch
    The flat artwork to bend onto the surface, filled with the last sketch. Its plane decides where the decal lands — see the placement rules below.
  3. 3
    Target face
    The face to wrap onto. It has to be cylindrical or conical; clicking any other face leaves the slot empty.
  4. 4
    Thickness
    How far the decal stands off the surface, measured along the surface normal. On the Remove tab it is the engraving depth instead.
  5. 5
    Apply
    Writes the statement and adds the timeline row; Exit writes nothing.

From flat sketch to curved decal

BeforeBefore modea flat rectangle

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

AfterAfter modewrap(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
label.part.js
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:

Wrapped text

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:

Engraved text

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:

Wrapped 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:

Slot wrapped onto a cone

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.