fluidcad 0.0.36 → 0.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/LICENSE.txt +21 -504
  2. package/README.md +1 -1
  3. package/lib/dist/common/edge.d.ts +1 -1
  4. package/lib/dist/common/face.d.ts +1 -1
  5. package/lib/dist/common/scene-object.d.ts +6 -0
  6. package/lib/dist/common/scene-object.js +8 -0
  7. package/lib/dist/common/shape-factory.d.ts +1 -1
  8. package/lib/dist/common/shape-history-tracker.d.ts +1 -1
  9. package/lib/dist/common/shape.d.ts +1 -1
  10. package/lib/dist/common/solid.d.ts +1 -1
  11. package/lib/dist/common/transformable-primitive.d.ts +12 -1
  12. package/lib/dist/common/transformable-primitive.js +27 -0
  13. package/lib/dist/common/vertex.d.ts +1 -1
  14. package/lib/dist/common/wire.d.ts +1 -1
  15. package/lib/dist/core/2d/index.d.ts +1 -0
  16. package/lib/dist/core/2d/index.js +1 -0
  17. package/lib/dist/core/2d/text.d.ts +30 -0
  18. package/lib/dist/core/2d/text.js +37 -0
  19. package/lib/dist/core/helix.d.ts +20 -0
  20. package/lib/dist/core/helix.js +36 -0
  21. package/lib/dist/core/index.d.ts +3 -1
  22. package/lib/dist/core/index.js +2 -0
  23. package/lib/dist/core/interfaces.d.ts +180 -0
  24. package/lib/dist/core/plane.d.ts +26 -6
  25. package/lib/dist/core/plane.js +21 -44
  26. package/lib/dist/core/wrap.d.ts +17 -0
  27. package/lib/dist/core/wrap.js +39 -0
  28. package/lib/dist/features/2d/offset.js +2 -2
  29. package/lib/dist/features/2d/text.d.ts +67 -0
  30. package/lib/dist/features/2d/text.js +320 -0
  31. package/lib/dist/features/cylinder.d.ts +3 -1
  32. package/lib/dist/features/cylinder.js +5 -2
  33. package/lib/dist/features/extrude-base.d.ts +1 -0
  34. package/lib/dist/features/extrude-to-face.d.ts +1 -0
  35. package/lib/dist/features/extrude-to-face.js +6 -0
  36. package/lib/dist/features/fillet.d.ts +1 -1
  37. package/lib/dist/features/helix.d.ts +41 -0
  38. package/lib/dist/features/helix.js +337 -0
  39. package/lib/dist/features/plane-from-object.d.ts +16 -4
  40. package/lib/dist/features/plane-from-object.js +101 -8
  41. package/lib/dist/features/select.js +32 -8
  42. package/lib/dist/features/simple-extruder.d.ts +1 -1
  43. package/lib/dist/features/simple-extruder.js +7 -2
  44. package/lib/dist/features/sphere.d.ts +3 -1
  45. package/lib/dist/features/sphere.js +5 -2
  46. package/lib/dist/features/sweep.js +7 -2
  47. package/lib/dist/features/wrap.d.ts +39 -0
  48. package/lib/dist/features/wrap.js +116 -0
  49. package/lib/dist/filters/edge/belongs-to-face.d.ts +3 -1
  50. package/lib/dist/filters/edge/belongs-to-face.js +14 -10
  51. package/lib/dist/filters/filter.d.ts +1 -1
  52. package/lib/dist/filters/from-object.d.ts +1 -1
  53. package/lib/dist/filters/tangent-expander.d.ts +1 -1
  54. package/lib/dist/filters/tangent-expander.js +57 -40
  55. package/lib/dist/helpers/scene-helpers.d.ts +2 -0
  56. package/lib/dist/helpers/scene-helpers.js +1 -1
  57. package/lib/dist/index.d.ts +2 -0
  58. package/lib/dist/index.js +3 -1
  59. package/lib/dist/io/file-import.d.ts +7 -0
  60. package/lib/dist/io/file-import.js +28 -1
  61. package/lib/dist/io/font-registry.d.ts +45 -0
  62. package/lib/dist/io/font-registry.js +272 -0
  63. package/lib/dist/math/bspline-interpolation.d.ts +29 -0
  64. package/lib/dist/math/bspline-interpolation.js +194 -0
  65. package/lib/dist/oc/boolean-ops.d.ts +3 -1
  66. package/lib/dist/oc/boolean-ops.js +15 -1
  67. package/lib/dist/oc/color-transfer.d.ts +1 -1
  68. package/lib/dist/oc/constraints/constraint-helpers.d.ts +4 -4
  69. package/lib/dist/oc/constraints/curve/tangent-circle-solver.js +10 -9
  70. package/lib/dist/oc/constraints/curve/tangent-line-solver.js +5 -6
  71. package/lib/dist/oc/convert.d.ts +1 -1
  72. package/lib/dist/oc/draft-ops.d.ts +1 -1
  73. package/lib/dist/oc/edge-ops.d.ts +2 -2
  74. package/lib/dist/oc/edge-ops.js +13 -14
  75. package/lib/dist/oc/edge-props.d.ts +1 -1
  76. package/lib/dist/oc/edge-query.d.ts +1 -1
  77. package/lib/dist/oc/edge-query.js +3 -8
  78. package/lib/dist/oc/errors.d.ts +8 -0
  79. package/lib/dist/oc/errors.js +27 -0
  80. package/lib/dist/oc/explorer.d.ts +2 -2
  81. package/lib/dist/oc/extrude-ops.d.ts +28 -2
  82. package/lib/dist/oc/extrude-ops.js +56 -7
  83. package/lib/dist/oc/face-ops.d.ts +2 -1
  84. package/lib/dist/oc/face-ops.js +11 -0
  85. package/lib/dist/oc/face-props.d.ts +1 -1
  86. package/lib/dist/oc/face-query.d.ts +12 -1
  87. package/lib/dist/oc/face-query.js +39 -0
  88. package/lib/dist/oc/fillet-ops.d.ts +1 -1
  89. package/lib/dist/oc/fillet-ops.js +4 -4
  90. package/lib/dist/oc/geometry.d.ts +1 -1
  91. package/lib/dist/oc/geometry.js +12 -14
  92. package/lib/dist/oc/helix-ops.d.ts +37 -0
  93. package/lib/dist/oc/helix-ops.js +88 -0
  94. package/lib/dist/oc/hit-test.d.ts +1 -1
  95. package/lib/dist/oc/index.d.ts +4 -0
  96. package/lib/dist/oc/index.js +2 -0
  97. package/lib/dist/oc/init.d.ts +1 -1
  98. package/lib/dist/oc/init.js +1 -1
  99. package/lib/dist/oc/intersection.js +1 -1
  100. package/lib/dist/oc/io.d.ts +6 -6
  101. package/lib/dist/oc/io.js +31 -24
  102. package/lib/dist/oc/measure/classify.d.ts +34 -0
  103. package/lib/dist/oc/measure/classify.js +246 -0
  104. package/lib/dist/oc/measure/measure-ops.d.ts +9 -0
  105. package/lib/dist/oc/measure/measure-ops.js +210 -0
  106. package/lib/dist/oc/measure/measure-types.d.ts +39 -0
  107. package/lib/dist/oc/measure/measure-types.js +1 -0
  108. package/lib/dist/oc/measure/sampling.d.ts +9 -0
  109. package/lib/dist/oc/measure/sampling.js +77 -0
  110. package/lib/dist/oc/measure/vec.d.ts +13 -0
  111. package/lib/dist/oc/measure/vec.js +23 -0
  112. package/lib/dist/oc/mesh.d.ts +1 -1
  113. package/lib/dist/oc/mesh.js +40 -28
  114. package/lib/dist/oc/path-sampler.d.ts +29 -0
  115. package/lib/dist/oc/path-sampler.js +63 -0
  116. package/lib/dist/oc/props.d.ts +1 -1
  117. package/lib/dist/oc/props.js +4 -1
  118. package/lib/dist/oc/shape-hash.d.ts +26 -0
  119. package/lib/dist/oc/shape-hash.js +32 -0
  120. package/lib/dist/oc/shape-ops.d.ts +5 -3
  121. package/lib/dist/oc/shape-ops.js +6 -5
  122. package/lib/dist/oc/sweep-ops.d.ts +13 -1
  123. package/lib/dist/oc/sweep-ops.js +174 -18
  124. package/lib/dist/oc/text-outline.d.ts +62 -0
  125. package/lib/dist/oc/text-outline.js +212 -0
  126. package/lib/dist/oc/thin-face-maker.d.ts +0 -19
  127. package/lib/dist/oc/thin-face-maker.js +3 -68
  128. package/lib/dist/oc/topology-index.d.ts +1 -1
  129. package/lib/dist/oc/vertex-ops.d.ts +1 -1
  130. package/lib/dist/oc/wire-ops.d.ts +18 -3
  131. package/lib/dist/oc/wire-ops.js +56 -5
  132. package/lib/dist/oc/wrap-development.d.ts +105 -0
  133. package/lib/dist/oc/wrap-development.js +179 -0
  134. package/lib/dist/oc/wrap-ops.d.ts +100 -0
  135. package/lib/dist/oc/wrap-ops.js +406 -0
  136. package/lib/dist/rendering/render-solid.js +10 -2
  137. package/lib/dist/scene-manager.d.ts +2 -0
  138. package/lib/dist/scene-manager.js +29 -0
  139. package/lib/dist/tests/features/2d/offset.test.js +74 -1
  140. package/lib/dist/tests/features/cylinder-curve-filter.test.js +3 -3
  141. package/lib/dist/tests/features/extrude-to-face.test.js +38 -1
  142. package/lib/dist/tests/features/helix.test.d.ts +1 -0
  143. package/lib/dist/tests/features/helix.test.js +295 -0
  144. package/lib/dist/tests/features/plane.test.js +95 -0
  145. package/lib/dist/tests/features/repeat-primitive.test.d.ts +1 -0
  146. package/lib/dist/tests/features/repeat-primitive.test.js +60 -0
  147. package/lib/dist/tests/features/rib.test.js +6 -1
  148. package/lib/dist/tests/features/sweep.test.js +170 -1
  149. package/lib/dist/tests/features/text.test.d.ts +1 -0
  150. package/lib/dist/tests/features/text.test.js +347 -0
  151. package/lib/dist/tests/features/wrap-development.test.d.ts +1 -0
  152. package/lib/dist/tests/features/wrap-development.test.js +130 -0
  153. package/lib/dist/tests/features/wrap-extruded-target.test.d.ts +1 -0
  154. package/lib/dist/tests/features/wrap-extruded-target.test.js +106 -0
  155. package/lib/dist/tests/features/wrap-repeat.test.d.ts +1 -0
  156. package/lib/dist/tests/features/wrap-repeat.test.js +93 -0
  157. package/lib/dist/tests/features/wrap.test.d.ts +1 -0
  158. package/lib/dist/tests/features/wrap.test.js +331 -0
  159. package/lib/dist/tests/math/bspline-interpolation.test.d.ts +1 -0
  160. package/lib/dist/tests/math/bspline-interpolation.test.js +119 -0
  161. package/lib/dist/tests/measure.test.d.ts +1 -0
  162. package/lib/dist/tests/measure.test.js +288 -0
  163. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  164. package/llm-docs/api/helix.md +64 -0
  165. package/llm-docs/api/index.json +11 -2
  166. package/llm-docs/api/text.md +52 -0
  167. package/llm-docs/api/types/helix.md +105 -0
  168. package/llm-docs/api/types/text.md +138 -0
  169. package/llm-docs/api/types/wrap.md +131 -0
  170. package/llm-docs/api/wrap.md +62 -0
  171. package/llm-docs/index.json +121 -1
  172. package/mcp/dist/server.js +20 -1
  173. package/mcp/dist/tools/inspection.d.ts +17 -0
  174. package/mcp/dist/tools/inspection.js +14 -0
  175. package/package.json +7 -3
  176. package/server/dist/fluidcad-server.d.ts +11 -1
  177. package/server/dist/fluidcad-server.js +21 -1
  178. package/server/dist/index.js +4 -2
  179. package/server/dist/preferences.d.ts +4 -0
  180. package/server/dist/preferences.js +2 -0
  181. package/server/dist/routes/measure.d.ts +3 -0
  182. package/server/dist/routes/measure.js +32 -0
  183. package/server/dist/routes/params.js +1 -1
  184. package/server/dist/routes/preferences.js +6 -0
  185. package/server/dist/routes/sketch-edits.js +2 -1
  186. package/ui/dist/assets/{index-MRqwG9Vh.js → index-D8zV21wB.js} +149 -102
  187. package/ui/dist/assets/{index-CDJmUpFI.css → index-dAFdg2Un.css} +1 -1
  188. package/ui/dist/index.html +2 -2
@@ -3,12 +3,39 @@ import { Matrix4 } from "../math/matrix4.js";
3
3
  import { Point } from "../math/point.js";
4
4
  import { Vector3d } from "../math/vector3d.js";
5
5
  import { rad } from "../helpers/math-helpers.js";
6
+ import { ShapeOps } from "../oc/shape-ops.js";
6
7
  import { isNumberParam, resolveParam } from "../core/param.js";
7
8
  export class TransformablePrimitive extends SceneObject {
8
9
  transform(matrix) {
9
10
  this.composeAppliedTransform(matrix);
10
11
  return this;
11
12
  }
13
+ /**
14
+ * Adds a primitive's built shape, baking in the clone transform when this
15
+ * primitive is a repeat/mirror copy. The renderer post-applies the user's
16
+ * own transform (translate/rotate/mirror) after build, so the clone
17
+ * transform is conjugated to land outside it:
18
+ * own · (own⁻¹ · clone · own) = clone · own.
19
+ */
20
+ addPrimitiveShape(shape, context) {
21
+ const cloneTransform = context?.getTransform() ?? null;
22
+ if (cloneTransform) {
23
+ const own = this.getAppliedTransform();
24
+ const matrix = own
25
+ ? own.inverse().multiply(cloneTransform).multiply(own)
26
+ : cloneTransform;
27
+ shape = ShapeOps.transform(shape, matrix);
28
+ }
29
+ this.addShape(shape);
30
+ }
31
+ /** Carries the user's own transform onto a repeat/mirror copy. */
32
+ syncPrimitiveWith(source) {
33
+ const applied = source.getAppliedTransform();
34
+ if (applied) {
35
+ this.transform(applied);
36
+ }
37
+ return this;
38
+ }
12
39
  translate(a, b, c) {
13
40
  let x, y, z;
14
41
  if (isNumberParam(a)) {
@@ -1,4 +1,4 @@
1
- import type { TopoDS_Vertex } from "occjs-wrapper";
1
+ import type { TopoDS_Vertex } from "ocjs-fluidcad";
2
2
  import { ShapeType } from "./shape-type.js";
3
3
  import { Shape } from "./shape.js";
4
4
  import { Point, Point2D } from "../math/point.js";
@@ -1,4 +1,4 @@
1
- import type { TopoDS_Wire } from "occjs-wrapper";
1
+ import type { TopoDS_Wire } from "ocjs-fluidcad";
2
2
  import { ShapeType } from "./shape-type.js";
3
3
  import { Shape } from "./shape.js";
4
4
  import { Vector3d } from "../math/vector3d.js";
@@ -21,5 +21,6 @@ export { default as offset } from './offset.js';
21
21
  export { default as project } from './project.js';
22
22
  export { default as intersect } from './intersect.js';
23
23
  export { default as bezier } from './bezier.js';
24
+ export { default as text } from './text.js';
24
25
  export { default as center } from './center.js';
25
26
  export { default as back } from './back.js';
@@ -21,5 +21,6 @@ export { default as offset } from './offset.js';
21
21
  export { default as project } from './project.js';
22
22
  export { default as intersect } from './intersect.js';
23
23
  export { default as bezier } from './bezier.js';
24
+ export { default as text } from './text.js';
24
25
  export { default as center } from './center.js';
25
26
  export { default as back } from './back.js';
@@ -0,0 +1,30 @@
1
+ import { PlaneLike } from "../../math/plane.js";
2
+ import { IText, ISceneObject } from "../interfaces.js";
3
+ interface TextFunction {
4
+ /**
5
+ * Renders a text string as extrudable outline geometry inside the current
6
+ * sketch, at the sketch cursor.
7
+ * @param text - The string to render.
8
+ */
9
+ (text: string): IText;
10
+ /**
11
+ * Renders a text string on a specific plane (standalone, outside a sketch).
12
+ * @param plane - The plane (e.g. "xy") or face to render the text on.
13
+ * @param text - The string to render.
14
+ */
15
+ (plane: PlaneLike | ISceneObject, text: string): IText;
16
+ /**
17
+ * Renders a text string following a planar curve. Each glyph is placed
18
+ * upright along the path's arc length; the text plane is the path's plane.
19
+ * Works inside a sketch (following a curve of that sketch) or standalone.
20
+ * The path is left in place — mark it `.guide()` to keep it out of
21
+ * extruded profiles.
22
+ * @param text - The string to render.
23
+ * @param path - The curve to follow: a sketch curve (line/arc/circle), a
24
+ * whole sketch, a planar primitive, or a selected edge/edge loop
25
+ * (e.g. `select(edge().circle())`).
26
+ */
27
+ (text: string, path: ISceneObject): IText;
28
+ }
29
+ declare const _default: TextFunction;
30
+ export default _default;
@@ -0,0 +1,37 @@
1
+ import { Text } from "../../features/2d/text.js";
2
+ import { registerBuilder } from "../../index.js";
3
+ import { isPlaneLike } from "../../math/plane.js";
4
+ import { SceneObject } from "../../common/scene-object.js";
5
+ import { resolvePlane } from "../../helpers/resolve.js";
6
+ function build(context) {
7
+ return function text() {
8
+ const first = arguments[0];
9
+ const second = arguments[1];
10
+ // A trailing scene object is a path to follow: `text("Hi", path)`.
11
+ // Valid both standalone and inside a sketch (following a sketch curve).
12
+ if (arguments.length >= 2 && second instanceof SceneObject) {
13
+ if (typeof first !== "string") {
14
+ throw new Error("text: when following a path, the first argument must be the text string.");
15
+ }
16
+ const obj = new Text(first, null, second);
17
+ context.addSceneObject(obj);
18
+ return obj;
19
+ }
20
+ // A leading plane/face is only valid standalone and only when a string
21
+ // follows it; `text("xy")` (one arg) renders the literal string "xy".
22
+ const standalone = arguments.length >= 2 && (isPlaneLike(first) || first instanceof SceneObject);
23
+ if (standalone) {
24
+ if (context.getActiveSketch() !== null) {
25
+ throw new Error("text(plane, ...) cannot be used inside a sketch. Use text(...) instead.");
26
+ }
27
+ const planeObj = resolvePlane(first, context);
28
+ const obj = new Text(String(arguments[1] ?? ""), planeObj);
29
+ context.addSceneObject(obj);
30
+ return obj;
31
+ }
32
+ const obj = new Text(String(first ?? ""));
33
+ context.addSceneObject(obj);
34
+ return obj;
35
+ };
36
+ }
37
+ export default registerBuilder(build);
@@ -0,0 +1,20 @@
1
+ import { AxisLike } from "../math/axis.js";
2
+ import { IHelix, ISceneObject } from "./interfaces.js";
3
+ interface HelixFunction {
4
+ /**
5
+ * Creates a helix wire along the given axis. Use chained methods (.pitch(),
6
+ * .turns(), .height(), .radius(), .endRadius()) to configure geometry.
7
+ * @param axis - The axis to build the helix around.
8
+ */
9
+ (axis: AxisLike): IHelix;
10
+ /**
11
+ * Creates a helix wire derived from a scene object's geometry.
12
+ * - A cylindrical or conical face: axis + radii + height come from the face.
13
+ * - A line edge: axis = the line, height = line length.
14
+ * - A circular edge: axis = circle normal at center, radius = circle radius.
15
+ * @param source - The scene object whose face/edge defines the helix.
16
+ */
17
+ (source: ISceneObject): IHelix;
18
+ }
19
+ declare const _default: HelixFunction;
20
+ export default _default;
@@ -0,0 +1,36 @@
1
+ import { registerBuilder } from "../index.js";
2
+ import { Helix } from "../features/helix.js";
3
+ import { isAxisLike } from "../math/axis.js";
4
+ import { AxisObject } from "../features/axis.js";
5
+ import { AxisObjectBase } from "../features/axis-renderable-base.js";
6
+ import { SceneObject } from "../common/scene-object.js";
7
+ import { normalizeAxis } from "../helpers/normalize.js";
8
+ function build(context) {
9
+ return function helix() {
10
+ if (arguments.length === 0) {
11
+ throw new Error("helix() requires an axis or scene object argument.");
12
+ }
13
+ const arg = arguments[0];
14
+ let source;
15
+ if (arg instanceof AxisObjectBase) {
16
+ source = arg;
17
+ context.addSceneObject(source);
18
+ }
19
+ else if (isAxisLike(arg)) {
20
+ const axis = normalizeAxis(arg);
21
+ source = new AxisObject(axis);
22
+ context.addSceneObject(source);
23
+ }
24
+ else if (arg instanceof SceneObject) {
25
+ source = arg;
26
+ context.addSceneObject(source);
27
+ }
28
+ else {
29
+ throw new Error("helix(): first argument must be an AxisLike or SceneObject.");
30
+ }
31
+ const result = new Helix(source);
32
+ context.addSceneObject(result);
33
+ return result;
34
+ };
35
+ }
36
+ export default registerBuilder(build);
@@ -1,4 +1,4 @@
1
- export type { ISceneObject, ITransformable, IBooleanOperation, IPlane, IAxis, ISelect, IGeometry, IExtrudableGeometry, IRect, ISlot, IPolygon, ITwoObjectsTangentLine, ITangentArcTwoObjects, IExtrude, ICut, ICommon, ISweep, ILoft, IRevolve, IDraft, IRib } from "./interfaces.js";
1
+ export type { ISceneObject, ITransformable, IBooleanOperation, IPlane, IAxis, ISelect, IGeometry, IExtrudableGeometry, IText, IRect, ISlot, IPolygon, ITwoObjectsTangentLine, ITangentArcTwoObjects, IExtrude, ICut, ICommon, ISweep, ILoft, IRevolve, IDraft, IRib, IHelix, IWrap } from "./interfaces.js";
2
2
  export { default as axis } from "./axis.js";
3
3
  export { default as local } from "./local.js";
4
4
  export { default as plane } from "./plane.js";
@@ -24,6 +24,8 @@ export { default as load } from "./load.js";
24
24
  export { default as loft } from "./loft.js";
25
25
  export { default as sweep } from "./sweep.js";
26
26
  export { default as rib } from "./rib.js";
27
+ export { default as wrap } from "./wrap.js";
28
+ export { default as helix } from "./helix.js";
27
29
  export { default as color } from "./color.js";
28
30
  export { default as draft } from "./draft.js";
29
31
  export { default as remove } from "./remove.js";
@@ -23,6 +23,8 @@ export { default as load } from "./load.js";
23
23
  export { default as loft } from "./loft.js";
24
24
  export { default as sweep } from "./sweep.js";
25
25
  export { default as rib } from "./rib.js";
26
+ export { default as wrap } from "./wrap.js";
27
+ export { default as helix } from "./helix.js";
26
28
  export { default as color } from "./color.js";
27
29
  export { default as draft } from "./draft.js";
28
30
  export { default as remove } from "./remove.js";
@@ -144,6 +144,82 @@ export interface IGeometry extends ISceneObject {
144
144
  }
145
145
  export interface IExtrudableGeometry extends IGeometry {
146
146
  }
147
+ export interface IText extends IExtrudableGeometry {
148
+ /**
149
+ * Sets the text height (em size) in model units. Default 10.
150
+ * @param value - The em size.
151
+ */
152
+ size(value: number): this;
153
+ /**
154
+ * Sets the font. A name without a font extension (e.g. `"Arial"`) is resolved
155
+ * to a system font; a value ending in `.ttf`/`.otf`/`.ttc`/`.woff` (e.g.
156
+ * `"fonts/Brand.ttf"`) is loaded as a workspace-relative file. When omitted, a
157
+ * default system font is used.
158
+ * @param name - A system family name or a workspace-relative font file path.
159
+ */
160
+ font(name: string): this;
161
+ /**
162
+ * Sets the font weight: a number (100–900) or a name such as `"regular"`,
163
+ * `"medium"`, or `"bold"`. Resolves to the matching face (or the wght axis of a
164
+ * variable font).
165
+ * @param value - The weight as a number or name.
166
+ */
167
+ weight(value: number | string): this;
168
+ /**
169
+ * Shortcut for `weight(700)`.
170
+ */
171
+ bold(): this;
172
+ /**
173
+ * Renders the italic/oblique face of the font.
174
+ * @param value - Whether to use italic (defaults to true).
175
+ */
176
+ italic(value?: boolean): this;
177
+ /**
178
+ * Horizontal alignment of the text. For straight text it is relative to the
179
+ * origin point; for text along a path it positions the run against the
180
+ * path: `"start"` begins at the path's start, `"center"` centers on the
181
+ * midpoint, `"end"` finishes at the path's end, `"space-between"` justifies
182
+ * the glyphs evenly across the whole path, and `"space-around"` spreads
183
+ * them with half a gap before the first glyph and after the last, like the
184
+ * CSS flexbox value (both path text only). `"left"` and `"right"` are
185
+ * synonyms of `"start"` and `"end"`.
186
+ * @param value - `"left"`/`"start"` (default), `"center"`,
187
+ * `"right"`/`"end"`, `"space-between"`, or `"space-around"`.
188
+ */
189
+ align(value: "left" | "center" | "right" | "start" | "end" | "space-between" | "space-around"): this;
190
+ /**
191
+ * Line-height multiplier for multi-line text (newlines in the string).
192
+ * @param value - Multiplier on the font's natural line height (default 1).
193
+ */
194
+ lineSpacing(value: number): this;
195
+ /**
196
+ * Extra spacing added between glyphs, in model units (default 0).
197
+ * @param value - The additional advance per glyph.
198
+ */
199
+ letterSpacing(value: number): this;
200
+ /**
201
+ * Shifts the baseline perpendicular to the path, in model units: positive
202
+ * values move the text toward its "up" side, negative below the path.
203
+ * Only applies to text following a path (`text(string, path)`).
204
+ * @param value - The perpendicular baseline shift.
205
+ */
206
+ offset(value: number): this;
207
+ /**
208
+ * Mirrors the text to the other side of the path, reversing the reading
209
+ * direction. On a closed path (circle, loop) text sits on the outside by
210
+ * default — `.flip()` moves it inside. On an open path it mirrors the text
211
+ * below the curve. Only applies to text following a path.
212
+ * @param value - Whether to flip (defaults to true).
213
+ */
214
+ flip(value?: boolean): this;
215
+ /**
216
+ * Shifts where the text starts along the path, as an arc-length distance
217
+ * from the path's start (combines with `align()`). On a closed path the
218
+ * text wraps around. Only applies to text following a path.
219
+ * @param distance - The arc-length shift in model units.
220
+ */
221
+ startAt(distance: number): this;
222
+ }
147
223
  export interface IOffset extends IExtrudableGeometry {
148
224
  /**
149
225
  * Closes an open offset by joining it back to the source wire with
@@ -794,6 +870,59 @@ export interface IRib extends IBooleanOperation {
794
870
  */
795
871
  extend(): this;
796
872
  }
873
+ export interface IWrap extends IBooleanOperation {
874
+ /**
875
+ * Selects the faces lying on the target surface (the base of the wrap).
876
+ * @param args - Numeric indices or {@link FaceFilterBuilder} instances to filter the selection.
877
+ */
878
+ startFaces(...args: (number | FaceFilterBuilder)[]): ISceneObject;
879
+ /**
880
+ * Selects the raised (or recessed) faces offset from the target surface by the wrap thickness.
881
+ * @param args - Numeric indices or {@link FaceFilterBuilder} instances to filter the selection.
882
+ */
883
+ endFaces(...args: (number | FaceFilterBuilder)[]): ISceneObject;
884
+ /**
885
+ * Selects edges on the base faces of the wrap.
886
+ * @param args - Numeric indices or {@link EdgeFilterBuilder} instances to filter the selection.
887
+ */
888
+ startEdges(...args: (number | EdgeFilterBuilder)[]): ISceneObject;
889
+ /**
890
+ * Selects edges on the offset faces of the wrap.
891
+ * @param args - Numeric indices or {@link EdgeFilterBuilder} instances to filter the selection.
892
+ */
893
+ endEdges(...args: (number | EdgeFilterBuilder)[]): ISceneObject;
894
+ /**
895
+ * Selects the wall faces created from the outer boundary of each wrapped region.
896
+ * @param args - Numeric indices or {@link FaceFilterBuilder} instances to filter the selection.
897
+ */
898
+ sideFaces(...args: (number | FaceFilterBuilder)[]): ISceneObject;
899
+ /**
900
+ * Selects edges on the wall faces, excluding edges shared with base/offset faces.
901
+ * @param args - Numeric indices or {@link EdgeFilterBuilder} instances to filter the selection.
902
+ */
903
+ sideEdges(...args: (number | EdgeFilterBuilder)[]): ISceneObject;
904
+ /**
905
+ * Selects the wall faces created from holes inside a wrapped region.
906
+ * @param args - Numeric indices or {@link FaceFilterBuilder} instances to filter the selection.
907
+ */
908
+ internalFaces(...args: (number | FaceFilterBuilder)[]): ISceneObject;
909
+ /**
910
+ * Selects edges bounding the hole walls of the wrap.
911
+ * @param args - Numeric indices or {@link EdgeFilterBuilder} instances to filter the selection.
912
+ */
913
+ internalEdges(...args: (number | EdgeFilterBuilder)[]): ISceneObject;
914
+ /**
915
+ * Enables or disables drill mode, which partitions the sketch into face regions
916
+ * before wrapping.
917
+ * @param value - `true` to enable (default), `false` to disable.
918
+ */
919
+ drill(value?: boolean): this;
920
+ /**
921
+ * Restricts wrapping to only the sketch regions containing the given points.
922
+ * @param points - 2D points in the sketch plane identifying regions to wrap.
923
+ */
924
+ pick(...points: Point2DLike[]): this;
925
+ }
797
926
  export type ShellJoinType = 'arc' | 'intersection' | 'tangent';
798
927
  export interface IShell extends ISceneObject {
799
928
  /**
@@ -814,3 +943,54 @@ export interface IShell extends ISceneObject {
814
943
  */
815
944
  join(type: ShellJoinType): this;
816
945
  }
946
+ /**
947
+ * A 3D helix wire — a single edge that traces a helix curve on a cylindrical or
948
+ * conical surface. Used as a path for `sweep()` to produce springs, threads, and
949
+ * coils.
950
+ *
951
+ * Created from one of:
952
+ * - An axis (`AxisLike`): user supplies geometry via chained config.
953
+ * - A cylindrical or conical face: axis + radii + height derived from the face.
954
+ * - A line edge: axis = the line, height = line length.
955
+ * - A circular edge: axis = circle normal, radius = circle radius.
956
+ */
957
+ export interface IHelix extends ISceneObject {
958
+ /**
959
+ * Axial rise per turn (distance along the helix axis covered per full revolution).
960
+ * If unset, derived from `height / turns`.
961
+ */
962
+ pitch(pitch: number): this;
963
+ /**
964
+ * Number of full turns. Fractional values are allowed. Default 1.
965
+ */
966
+ turns(turns: number): this;
967
+ /**
968
+ * Shifts the start of the helix along its axis, in axial mm. Positive values
969
+ * trim the start (move it toward the end); negative values extend it. Default 0.
970
+ */
971
+ startOffset(offset: number): this;
972
+ /**
973
+ * Extends (positive) or trims (negative) the helix at its end, in axial mm.
974
+ * Default 0.
975
+ */
976
+ endOffset(offset: number): this;
977
+ /**
978
+ * Total axial height. Overrides face/edge-derived height when set. For line-edge
979
+ * input, defaults to the line length. For circular-edge / pure-axis input,
980
+ * defaults to 50 if neither this nor `pitch * turns` determine it.
981
+ */
982
+ height(height: number): this;
983
+ /**
984
+ * Start radius. Defaults to 20 for axis/line-edge input. For a cylindrical
985
+ * face input, defaults to the face's radius and may be overridden (useful for
986
+ * sweep/fuse workflows where the helix tube must overlap the cylinder
987
+ * volumetrically — offset by ~1mm to avoid pure tangency). Ignored on
988
+ * conical face input (radius is derived from face geometry).
989
+ */
990
+ radius(radius: number): this;
991
+ /**
992
+ * End radius — when different from `radius()`, produces a conical helix.
993
+ * Defaults to `radius()`. Ignored on face/circle inputs.
994
+ */
995
+ endRadius(radius: number): this;
996
+ }
@@ -1,6 +1,11 @@
1
1
  import { PlaneLike, PlaneTransformOptions } from "../math/plane.js";
2
2
  import { IPlane, ISceneObject } from "./interfaces.js";
3
3
  export type PlaneRenderableOptions = PlaneTransformOptions;
4
+ /**
5
+ * Where to place a plane along an edge: a named position, or a normalized
6
+ * distance from the edge start (`0`) to its end (`1`). `0.5` is the midpoint.
7
+ */
8
+ export type EdgePlanePosition = 'start' | 'middle' | 'end' | number;
4
9
  interface PlaneFunction {
5
10
  /**
6
11
  * Creates a plane from a standard plane or normal vector.
@@ -20,8 +25,10 @@ interface PlaneFunction {
20
25
  */
21
26
  (plane: PlaneLike, offset: number): IPlane;
22
27
  /**
23
- * Creates a plane from a face selection.
24
- * @param selection - The selected face to create a plane from
28
+ * Creates a plane from a selection. A selected face yields that face's
29
+ * plane; a selected edge yields a plane at the edge start, oriented normal
30
+ * to the edge and facing outward (away from the edge body), like a start cap.
31
+ * @param selection - The selected face or edge to create a plane from
25
32
  */
26
33
  (selection: ISceneObject): IPlane;
27
34
  /**
@@ -31,11 +38,24 @@ interface PlaneFunction {
31
38
  */
32
39
  (selection: ISceneObject, options: PlaneRenderableOptions): IPlane;
33
40
  /**
34
- * Creates a plane from a face selection with an offset.
35
- * @param selection - The selected face to create a plane from
36
- * @param offset - The offset distance
41
+ * Creates a plane from a selection with a numeric second argument. For a
42
+ * face, the number is the offset distance along the face normal. For an
43
+ * edge, it is a normalized position from `0` (start) to `1` (end), and the
44
+ * plane is created at that point oriented normal to the edge. The normal
45
+ * follows the edge's forward direction, except at the start (`0`) where it
46
+ * faces outward (away from the edge body), so both ends read like caps.
47
+ * @param selection - The selected face or edge
48
+ * @param offsetOrPosition - Face: offset distance. Edge: normalized 0–1 position.
49
+ */
50
+ (selection: ISceneObject, offsetOrPosition: number): IPlane;
51
+ /**
52
+ * Creates a plane at a named position along an edge, oriented normal to the
53
+ * edge. At `'start'` the normal faces outward (away from the edge body);
54
+ * `'middle'` and `'end'` follow the edge's forward direction.
55
+ * @param edge - The selected edge
56
+ * @param position - `'start'`, `'middle'`, or `'end'`
37
57
  */
38
- (selection: ISceneObject, offset: number): IPlane;
58
+ (edge: ISceneObject, position: 'start' | 'middle' | 'end'): IPlane;
39
59
  /**
40
60
  * Transforms an existing plane with options.
41
61
  * @param plane - The existing plane to transform
@@ -23,56 +23,33 @@ function build(context) {
23
23
  }
24
24
  }
25
25
  if (arguments.length === 2) {
26
- if (typeof arguments[1] === 'number') {
27
- const options = { offset: arguments[1] };
28
- if (arguments[0] instanceof SceneObject) {
29
- context.addSceneObject(arguments[0]);
30
- const pln = new PlaneFromObject(arguments[0], options);
31
- context.addSceneObject(pln);
32
- return pln;
33
- }
34
- else if (isPlaneLike(arguments[0])) {
35
- const axis = normalizePlane(arguments[0]);
36
- const pln = new PlaneObject(axis, options);
37
- context.addSceneObject(pln);
38
- return pln;
39
- }
40
- }
41
- if ((arguments[0] instanceof PlaneObjectBase || isPlaneLike(arguments[0])) &&
42
- (arguments[1] instanceof PlaneObjectBase || isPlaneLike(arguments[1]))) {
43
- // axis between two others
44
- let a1;
45
- let a2;
46
- if (arguments[0] instanceof PlaneObjectBase) {
47
- a1 = arguments[0];
48
- }
49
- else {
50
- const axis = normalizePlane(arguments[0]);
51
- a1 = new PlaneObject(axis);
52
- }
53
- if (arguments[1] instanceof PlaneObjectBase) {
54
- a2 = arguments[1];
55
- }
56
- else {
57
- const axis = normalizePlane(arguments[1]);
58
- a2 = new PlaneObject(axis);
59
- }
60
- context.addSceneObject(a1);
61
- context.addSceneObject(a2);
62
- const pln = new PlaneMiddleRenderable(a1, a2);
26
+ const a0 = arguments[0];
27
+ const a1 = arguments[1];
28
+ // Plane midway between two planes / plane-likes.
29
+ if ((a0 instanceof PlaneObjectBase || isPlaneLike(a0)) &&
30
+ (a1 instanceof PlaneObjectBase || isPlaneLike(a1))) {
31
+ const p1 = a0 instanceof PlaneObjectBase ? a0 : new PlaneObject(normalizePlane(a0));
32
+ const p2 = a1 instanceof PlaneObjectBase ? a1 : new PlaneObject(normalizePlane(a1));
33
+ context.addSceneObject(p1);
34
+ context.addSceneObject(p2);
35
+ const pln = new PlaneMiddleRenderable(p1, p2);
63
36
  context.addSceneObject(pln);
64
37
  return pln;
65
38
  }
66
- else if (arguments[0] instanceof SceneObject) {
67
- context.addSceneObject(arguments[0]);
68
- const pln = new PlaneFromObject(arguments[0], arguments[1]);
39
+ // From a scene object. A face reads the second argument as an offset
40
+ // distance / transform options; an edge reads it as a normalized 0–1
41
+ // position (or 'start'/'middle'/'end'). The face-vs-edge decision is
42
+ // deferred to PlaneFromObject.build(), where the source shape is known.
43
+ if (a0 instanceof SceneObject) {
44
+ context.addSceneObject(a0);
45
+ const pln = new PlaneFromObject(a0, a1);
69
46
  context.addSceneObject(pln);
70
47
  return pln;
71
48
  }
72
- else if (isPlaneLike(arguments[0])) {
73
- const axis1 = normalizePlane(arguments[0]);
74
- const options = arguments[1];
75
- const pln = new PlaneObject(axis1, options);
49
+ // From a plane-like: number → offset, object → transform options.
50
+ if (isPlaneLike(a0)) {
51
+ const options = typeof a1 === 'number' ? { offset: a1 } : a1;
52
+ const pln = new PlaneObject(normalizePlane(a0), options);
76
53
  context.addSceneObject(pln);
77
54
  return pln;
78
55
  }
@@ -0,0 +1,17 @@
1
+ import { IWrap, ISceneObject } from "./interfaces.js";
2
+ import { type NumberParam } from "./param.js";
3
+ interface WrapFunction {
4
+ /**
5
+ * Wraps the given sketch onto a curved face, raising it from the surface by
6
+ * the given thickness (emboss). Chain `.remove()` to sink it into the
7
+ * surface instead (deboss), or `.new()` to keep the wrapped pad standalone.
8
+ * The sketch is developed onto the surface with its lengths preserved — a
9
+ * true wrap, not a projection. Cylindrical and conical faces are supported.
10
+ * @param thickness - Pad thickness measured along the surface normal (must be positive)
11
+ * @param sketch - The sketch to wrap onto the face
12
+ * @param face - The target face to wrap onto
13
+ */
14
+ (thickness: NumberParam, sketch: ISceneObject, face: ISceneObject): IWrap;
15
+ }
16
+ declare const _default: WrapFunction;
17
+ export default _default;
@@ -0,0 +1,39 @@
1
+ import { SceneObject } from "../common/scene-object.js";
2
+ import { registerBuilder } from "../index.js";
3
+ import { Wrap } from "../features/wrap.js";
4
+ import { isNumberParam, resolveParam } from "./param.js";
5
+ function isExtrudable(obj) {
6
+ return obj instanceof SceneObject && obj.isExtrudable();
7
+ }
8
+ function build(context) {
9
+ //@ts-ignore
10
+ return function wrap() {
11
+ const args = [...arguments];
12
+ if (args.length !== 3) {
13
+ throw new Error("wrap() expects (thickness, sketch, face).");
14
+ }
15
+ if (!isNumberParam(args[0])) {
16
+ throw new Error("wrap() thickness must be a positive number.");
17
+ }
18
+ const thickness = resolveParam(args[0]);
19
+ if (!(thickness > 0)) {
20
+ throw new Error("wrap() thickness must be a positive number.");
21
+ }
22
+ const source = args[1];
23
+ if (!(source instanceof SceneObject)) {
24
+ throw new Error("wrap() sketch must be a sketch or face-bearing scene object.");
25
+ }
26
+ const face = args[2];
27
+ if (!(face instanceof SceneObject) || isExtrudable(face)) {
28
+ throw new Error("wrap() requires a target face selection as its last argument.");
29
+ }
30
+ if (!isExtrudable(source)) {
31
+ context.addSceneObject(source);
32
+ }
33
+ context.addSceneObject(face);
34
+ const result = new Wrap(thickness, face, source);
35
+ context.addSceneObject(result);
36
+ return result;
37
+ };
38
+ }
39
+ export default registerBuilder(build);
@@ -55,9 +55,9 @@ export class Offset extends ExtrudableGeometryBase {
55
55
  });
56
56
  }
57
57
  let lastOffsetWire = null;
58
+ const plane = this.getPlane();
58
59
  for (const wireInfo of wires) {
59
- const isOpen = !wireInfo.wire.isClosed();
60
- const offsetWire = WireOps.offsetWire(wireInfo.wire, this.distance, isOpen);
60
+ const offsetWire = WireOps.offsetWireOnPlane(wireInfo.wire, this.distance, wireInfo.wire.isClosed(), plane);
61
61
  lastOffsetWire = offsetWire;
62
62
  const edges = offsetWire.getEdges();
63
63
  for (const edge of edges) {