@code3d/core 0.0.1-alpha.0 → 0.0.1-alpha.3

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 (131) hide show
  1. package/README.md +223 -786
  2. package/THIRD_PARTY.md +40 -0
  3. package/bld/THIRD_PARTY_NOTICES.txt +182 -0
  4. package/bld/chunks/chunk-BF55AIEQ.js +31 -0
  5. package/bld/chunks/chunk-BF55AIEQ.js.map +7 -0
  6. package/bld/chunks/chunk-P5VGKAEM.js +30 -0
  7. package/bld/chunks/chunk-P5VGKAEM.js.map +7 -0
  8. package/bld/chunks/chunk-RPY2WVCB.js +24834 -0
  9. package/bld/chunks/chunk-RPY2WVCB.js.map +7 -0
  10. package/bld/chunks/chunk-TJIN6S4R.js +1 -0
  11. package/bld/chunks/chunk-TJIN6S4R.js.map +7 -0
  12. package/bld/chunks/chunk-U5G2VU2I.js +19 -0
  13. package/bld/chunks/chunk-U5G2VU2I.js.map +7 -0
  14. package/bld/library/cached.d.ts +33 -0
  15. package/bld/library/cached.d.ts.map +1 -0
  16. package/bld/library/font.d.ts +39 -0
  17. package/bld/library/font.d.ts.map +1 -0
  18. package/bld/library/google-font.d.ts +14 -0
  19. package/bld/library/google-font.d.ts.map +1 -0
  20. package/bld/library/index.d.ts +8 -2
  21. package/bld/library/index.d.ts.map +1 -1
  22. package/bld/library/index.js +60 -3
  23. package/bld/library/index.js.map +7 -1
  24. package/bld/library/kernel-artifact-codec.d.ts.map +1 -1
  25. package/bld/library/kernel-cache.d.ts +24 -6
  26. package/bld/library/kernel-cache.d.ts.map +1 -1
  27. package/bld/library/material.d.ts +1 -1
  28. package/bld/library/material.d.ts.map +1 -1
  29. package/bld/library/open-cascade.d.ts +4 -0
  30. package/bld/library/open-cascade.d.ts.map +1 -0
  31. package/bld/library/replicad.d.ts +3 -2
  32. package/bld/library/replicad.d.ts.map +1 -1
  33. package/bld/library/replicad.js +10 -24
  34. package/bld/library/replicad.js.map +7 -1
  35. package/bld/library/retained-memory.d.ts.map +1 -1
  36. package/bld/library/runtime.d.ts +146 -80
  37. package/bld/library/runtime.d.ts.map +1 -1
  38. package/bld/library/sketch-solver.d.ts +1 -1
  39. package/bld/library/sketch-solver.d.ts.map +1 -1
  40. package/bld/library/sketch.d.ts +9 -1
  41. package/bld/library/sketch.d.ts.map +1 -1
  42. package/bld/library/text.d.ts +53 -0
  43. package/bld/library/text.d.ts.map +1 -0
  44. package/bld/library/three.js +3 -3
  45. package/bld/library/three.js.map +7 -1
  46. package/bld/library/topology.d.ts.map +1 -1
  47. package/bld/node/index.d.ts.map +1 -1
  48. package/bld/node/index.js +62 -14
  49. package/bld/node/index.js.map +7 -1
  50. package/bld/node/replicad.js +13 -3
  51. package/bld/node/replicad.js.map +7 -1
  52. package/bld/tooling/index.d.ts +17 -15
  53. package/bld/tooling/index.d.ts.map +1 -1
  54. package/bld/tooling/index.js +147 -19
  55. package/bld/tooling/index.js.map +7 -1
  56. package/package.json +18 -6
  57. package/src/library/cached.ts +132 -0
  58. package/src/library/font.ts +186 -0
  59. package/src/library/google-font.ts +97 -0
  60. package/src/library/index.ts +8 -3
  61. package/src/library/kernel-artifact-codec.ts +129 -20
  62. package/src/library/kernel-cache.ts +128 -34
  63. package/src/library/loft.ts +4 -0
  64. package/src/library/material.ts +3 -4
  65. package/src/library/open-cascade.ts +13 -0
  66. package/src/library/replicad.ts +5 -5
  67. package/src/library/retained-memory.ts +6 -1
  68. package/src/library/runtime.ts +2064 -1730
  69. package/src/library/sketch-solver.ts +3 -1
  70. package/src/library/sketch.ts +81 -13
  71. package/src/library/text.ts +477 -0
  72. package/src/library/topology.ts +24 -0
  73. package/src/library/tsconfig.json +2 -1
  74. package/src/node/index.ts +14 -3
  75. package/src/node/tsconfig.json +2 -1
  76. package/src/tooling/index.ts +48 -46
  77. package/src/tooling/tsconfig.json +2 -1
  78. package/bld/library/alignment-geometry.js +0 -322
  79. package/bld/library/alignment-geometry.js.map +0 -1
  80. package/bld/library/bound-solver.js +0 -252
  81. package/bld/library/bound-solver.js.map +0 -1
  82. package/bld/library/extrude.js +0 -28
  83. package/bld/library/extrude.js.map +0 -1
  84. package/bld/library/kernel-artifact-codec.js +0 -148
  85. package/bld/library/kernel-artifact-codec.js.map +0 -1
  86. package/bld/library/kernel-cache.js +0 -245
  87. package/bld/library/kernel-cache.js.map +0 -1
  88. package/bld/library/kernel-shapes.js +0 -108
  89. package/bld/library/kernel-shapes.js.map +0 -1
  90. package/bld/library/loft.js +0 -161
  91. package/bld/library/loft.js.map +0 -1
  92. package/bld/library/material.js +0 -176
  93. package/bld/library/material.js.map +0 -1
  94. package/bld/library/model-color.js +0 -36
  95. package/bld/library/model-color.js.map +0 -1
  96. package/bld/library/open-cascade-error.js +0 -22
  97. package/bld/library/open-cascade-error.js.map +0 -1
  98. package/bld/library/relation-solver.js +0 -258
  99. package/bld/library/relation-solver.js.map +0 -1
  100. package/bld/library/retained-memory.js +0 -27
  101. package/bld/library/retained-memory.js.map +0 -1
  102. package/bld/library/runtime.js +0 -2954
  103. package/bld/library/runtime.js.map +0 -1
  104. package/bld/library/shell.js +0 -221
  105. package/bld/library/shell.js.map +0 -1
  106. package/bld/library/sketch-curve-intersections.js +0 -117
  107. package/bld/library/sketch-curve-intersections.js.map +0 -1
  108. package/bld/library/sketch-curves.js +0 -89
  109. package/bld/library/sketch-curves.js.map +0 -1
  110. package/bld/library/sketch-drag-rules.js +0 -522
  111. package/bld/library/sketch-drag-rules.js.map +0 -1
  112. package/bld/library/sketch-face.js +0 -72
  113. package/bld/library/sketch-face.js.map +0 -1
  114. package/bld/library/sketch-incidence.js +0 -114
  115. package/bld/library/sketch-incidence.js.map +0 -1
  116. package/bld/library/sketch-precision.js +0 -55
  117. package/bld/library/sketch-precision.js.map +0 -1
  118. package/bld/library/sketch-regions.js +0 -193
  119. package/bld/library/sketch-regions.js.map +0 -1
  120. package/bld/library/sketch-solver.js +0 -578
  121. package/bld/library/sketch-solver.js.map +0 -1
  122. package/bld/library/sketch.js +0 -650
  123. package/bld/library/sketch.js.map +0 -1
  124. package/bld/library/spatial.js +0 -195
  125. package/bld/library/spatial.js.map +0 -1
  126. package/bld/library/topology-id.js +0 -67
  127. package/bld/library/topology-id.js.map +0 -1
  128. package/bld/library/topology-inspection.js +0 -267
  129. package/bld/library/topology-inspection.js.map +0 -1
  130. package/bld/library/topology.js +0 -455
  131. package/bld/library/topology.js.map +0 -1
package/README.md CHANGED
@@ -1,824 +1,261 @@
1
- # `@code3d/core`
2
-
3
- The code3d authoring runtime. Model projects install this package directly and
4
- may execute the same ESM TypeScript source in code3d or a supported Node.js
5
- runtime.
6
-
7
- The App also offers zero-install authoring with built-in core and screws. When
8
- the project's root `package.json` declares `@code3d/core`, the App uses the
9
- project's installed packages exclusively, including their declarations; missing
10
- dependencies are errors rather than a reason to substitute built-in packages.
11
- Direct Node execution requires installing the project dependencies.
12
-
13
- The public API includes solid primitives and Boolean operations, first-class
14
- planar face models (`circle`, `ellipse`, `rectangle`, `regularPolygon`), 3D
15
- curve models (`line`, `arc`, `bezier`, `spline`), point models, face extrusion, and
16
- through-section or spine-guided `loft`. Every geometric model is immutable,
17
- renderable, and relation-aware. Topology capabilities follow dimension:
18
- vertices provide `.vertex(id)`, edges add `.edge(id)`, and faces and solids
19
- add `.surface(id)`; only solids provide `fillet`, `chamfer`, and `shell`. Groups retain
20
- the common relation, expose, and material capabilities without pretending to
21
- contain geometry. Stable topology references can be used as geometric relation anchors.
22
- `Vertex`, `Edge`, and `Surface` references expose readonly `kind` and `id`
23
- properties. For example, `model.edges().map(edge => edge.id)` collects edge IDs
24
- for a later operation on that model. Their `kind` values are `vertex`, `edge`,
25
- and `surface`; IDs belong to that model and topology kind. Plain named anchors
26
- such as `model.up` do not expose these topology properties.
27
- `TopologyId` is a number or a flat numeric path. Topology-changing operations
28
- prefix one-to-one descendants with their one-based input index, so an input's
29
- `E3` becomes `[1, 3]`, or `[2, 3]` for the second input. New/ambiguous elements
30
- start at numeric `1` in each result. Fillet/chamfer/shell also add input index `1`;
31
- transforms preserve complete paths. Select a path with `.edge([1, 3])`, or a
32
- mixed collection with `.edges([1, [1, 3], [2, 3]])`. Loft caps use their endpoint
33
- section paths; Boolean operations inherit from all inputs. Splits and merges
34
- retire ambiguous source paths. Full rules are in the
35
- [topology guide](../web/src/content/docs/docs/guides/topology.md).
36
- `relate()` records placement for composition with other values; inspecting or
37
- rendering the resulting value by itself uses its own local geometry.
38
-
39
- ## Runtime defaults
40
-
41
- Dimension-based primitives keep required TypeScript signatures while supplying
42
- runtime defaults for omitted or `undefined` arguments. For example, `box()`
43
- produces the same geometry as `box(10, 10, 10)`, but TypeScript still requires all
44
- three dimensions. Explicit invalid values keep their normal errors. These
45
- defaults work in ordinary JavaScript execution as well as App previews.
46
- The [Modeling API](../web/src/content/docs/docs/reference/core.md#runtime-defaults-while-editing)
47
- lists the defaults for all supported primitives. The App displays them as
48
- placeholders without inserting arguments into source.
49
-
50
- ## Face extrusion
51
-
52
- `face.extrude(distance)` and `extrude(face, distance)` produce a `SolidModel`
53
- from one planar face model. The finite, non-zero signed distance follows the
54
- face's local normal, including any prior rotation. The starting face stays in
55
- place: an unrotated profile extruded by `3` spans Y = 0 to 3; `-3` spans -3 to 0.
56
- Origin offsets and input geometry are preserved, and the result supports ordinary
57
- solid operations. To extrude multiple faces, map them explicitly.
1
+ # @code3d/core
58
2
 
59
- ```ts
60
- import {circle, extrude, rectangle} from '@code3d/core';
61
-
62
- export const plate = rectangle(30, 20).extrude(3).fillet(0.5);
63
- export const pin = extrude(circle(2), -10);
64
- ```
65
-
66
- ## Editable sketches
3
+ The TypeScript modeling runtime used by Code3D. Compose solids, profiles, curves,
4
+ points, and editable sketches; inspect their geometry and reuse the same models
5
+ in the App or a supported Node.js runtime.
67
6
 
68
- Sketches are immutable 2D definitions, separate from geometric models and B-Reps:
7
+ ## Start with a model
69
8
 
70
- Start with `sketch()` for an empty sketch, or `base.derive()` for an empty local
71
- layer over an existing sketch. Omitting entries is equivalent to passing `[]`.
72
- The editor inserts the array only when the first drawing is completed; cancelling
73
- leaves the call unchanged, and undo restores the original call and its comments.
74
-
75
- ```ts
76
- import {sketch} from '@code3d/core';
77
-
78
- const sketch1 = sketch(
79
- [
80
- ['point', 1, [0, 0]],
81
- ['point', 2, [30, 0]],
82
- ['line', 3, [1, 2]],
83
- ],
84
- {
85
- constraints: [
86
- ['fixed', 1],
87
- ['horizontal', 3],
88
- ['length', 3, 30],
89
- ],
90
- },
91
- );
92
- const sketch2 = sketch1.derive([
93
- ['point', 1, [10, 20]],
94
- ['line', 2, [sketch1.point(2), 1]],
95
- ]);
9
+ ```sh
10
+ npm install @code3d/core
96
11
  ```
97
12
 
98
- Each tuple is `[kind, ID, data]`. Numeric curve point references name local points;
99
- `sketch1.point(id)` names a point owned by an upstream layer. Each layer has an
100
- independent positive-integer ID space shared by its geometry entities. Definitions
101
- may be empty, open, or contain crossing lines; crossings do not automatically
102
- split entities. Missing point references are errors.
13
+ ```ts
14
+ import {box, cylinder, group} from '@code3d/core';
103
15
 
104
- Circles use a center point reference and a current radius, not polygon segments:
16
+ const base = box(40, 4, 24).fillet(1);
17
+ const post = cylinder(3, 18).relate(self => self.on(base.up));
105
18
 
106
- ```ts
107
- const circles = sketch(
108
- [
109
- ['point', 1, [0, 0]],
110
- ['circle', 2, [1, 15]],
111
- ['circle', 3, [1, 8]],
112
- ],
113
- {constraints: [['radius', 3, 8]]},
114
- );
19
+ export default group([base, post]);
115
20
  ```
116
21
 
117
- `radius` uses `['radius', circleOrArcId, value]`. Both current radii and radius constraints
118
- must be positive and finite. The outer circle above remains free; the inner
119
- circle's independent constraint preserves its radius. A circle center may also
120
- use a named upstream point. Circle and point parameters have the same numeric
121
- runtime semantics, whether computed from expressions or written as literals.
22
+ Open this source in Code3D and select an expression to inspect its value. Node
23
+ loads the modeling kernel through the package's Node entry automatically; normal
24
+ model authors do not initialize it or manage evaluation caches themselves.
25
+
26
+ The App includes Core, [Materials](../materials/README.md), and
27
+ [Screws](../screws/README.md) for zero-install projects. When the active model's
28
+ package scope or an ancestor `package.json` declares `@code3d/core`, the App uses that project's installed
29
+ packages and declarations exclusively. Missing dependencies are errors. See
30
+ [project package installation](../web/src/content/docs/docs/getting-started/files.md#install-packages-in-browser-storage)
31
+ and the [agent file workflow](../../docs/agents/files.md).
32
+
33
+ ## Model values and coordinates
34
+
35
+ Operations produce new model values. Building another result must not change an
36
+ already-observable model's geometry, material, topology, or relations.
37
+
38
+ A model's local geometry and its placement in a composition are separate.
39
+ `relate()` records how a part is placed when composed with other parts; observing
40
+ that part alone shows its local geometry. `originOffset()` changes geometry
41
+ coordinates without changing the shape. Position arrays use `[x, y, z]`; scalar
42
+ angles use degrees. Read [local coordinates](../web/src/content/docs/docs/concepts/local-coordinates.md)
43
+ and [relations](../web/src/content/docs/docs/guides/relations.mdx) before mixing
44
+ origin changes, alignment, and rotation.
45
+
46
+ Build readable models from named intermediate values and public operations. A
47
+ profile followed by extrusion, or solids combined with Boolean operations,
48
+ keeps the construction understandable and editable by both people and agents.
49
+
50
+ ## Find the modeling API
51
+
52
+ | Task | Start here |
53
+ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
54
+ | Solids, planar profiles, curves, points, Boolean operations, extrusion and loft | [Modeling reference](../web/src/content/docs/docs/reference/core.md) |
55
+ | Place parts with bounds or align underlying geometry | [Relations](../web/src/content/docs/docs/guides/relations.mdx) |
56
+ | Change origins and rotate parts | [Origins and rotation](../web/src/content/docs/docs/guides/origins-and-rotation.mdx) |
57
+ | Hollow a solid or choose openings | [Shells](../web/src/content/docs/docs/guides/shells.mdx) |
58
+ | Select vertices, edges and surfaces, or expose named elements | [Topology](../web/src/content/docs/docs/guides/topology.md) |
59
+ | Build reusable model functions | [Reusable models](../web/src/content/docs/docs/guides/reusable-models.mdx) |
60
+ | Give functions editing tools and example arguments | [Model tools](../web/src/content/docs/docs/guides/model-tools.mdx) |
61
+ | Extend the runtime with Replicad geometry | [Custom primitives](../web/src/content/docs/docs/guides/custom-primitives.mdx) |
62
+ | Known boundaries | [Current limitations](../web/src/content/docs/docs/reference/limitations.md) |
63
+
64
+ Dimension-based primitives and numeric modeling methods retain required TypeScript
65
+ signatures while providing runtime defaults for omitted or `undefined` values.
66
+ Rotations and displacements default to zero, scaling to one, extrusion distance
67
+ to ten, and fillet radius, chamfer distance and shell thickness to one. Relation
68
+ rotation chains use the same angle defaults; `pivot()` defaults to local zero.
69
+ Explicit invalid values retain their normal errors. These defaults work in
70
+ ordinary JavaScript execution as well as App previews.
71
+
72
+ The App displays defaults as placeholders without inserting source arguments.
73
+ Committing a spatial drag fills all remaining omitted defaults in that operation;
74
+ for example, dragging the X ring of `rotate()` writes `rotate(angle, 0, 0)`. The
75
+ edit and completion share one undo step. Use explicit dimensions in finished
76
+ models; the [reference](../web/src/content/docs/docs/reference/core.md#runtime-defaults-while-editing)
77
+ lists the actual defaults.
78
+
79
+ Topology capabilities follow dimension: vertices expose vertex selection, edges
80
+ add edge selection, and faces and solids add surface selection. Only solids
81
+ provide `fillet`, `chamfer`, and `shell`. Groups compose values and support
82
+ relations, exposed elements, and materials without pretending to be geometry.
83
+
84
+ Groups are model values and can be nested directly with `group([inner, other])`,
85
+ including in mixed `Model[]` collections. Nesting preserves each group's hierarchy;
86
+ `expose()` adds named references when callers need to address members.
87
+
88
+ A topology ID belongs to its owning model and element kind. It is a number or a
89
+ flat numeric path, such as `.edge([1, 3])`. Operations track unambiguous ancestry;
90
+ transforms preserve complete paths. Inspect the result after topology changes
91
+ instead of assuming IDs from a different model still apply.
122
92
 
123
- Arcs store a current radius, reference a center, start and end point, and explicitly select the direction:
93
+ ## Editable sketches
94
+
95
+ Sketches are immutable 2D definitions, separate from B-Rep model values. Entries
96
+ carry positive IDs within a layer; constraints express what should stay true.
97
+ A derived layer can reference its upstream geometry.
124
98
 
125
99
  ```ts
126
- const rounded = sketch(
100
+ import {sketch} from '@code3d/core';
101
+
102
+ const profile = sketch(
127
103
  [
128
104
  ['point', 1, [0, 0]],
129
- ['point', 2, [10, 0]],
130
- ['point', 3, [0, 10]],
131
- ['arc', 4, [1, 10, 2, 3, 'ccw']],
105
+ ['circle', 2, [1, 8]],
132
106
  ],
133
107
  {
134
108
  constraints: [
135
- ['radius', 4, 10],
136
- ['sweep', 4, 90],
109
+ ['fixed', 1],
110
+ ['radius', 2, 8],
137
111
  ],
138
112
  },
139
113
  );
140
- ```
141
-
142
- `ccw` selects the counterclockwise arc in sketch coordinates; `cw` selects the
143
- clockwise arc, including major arcs. Native arc equations keep both endpoints
144
- on the circle; point coordinates and radius may move to satisfy them.
145
- The radius is ordinary current data, not an implicit radius constraint.
146
- To initialize inconsistent data, endpoints are projected along their supplied
147
- directions to the supplied radius, or to an explicit radius dimension when one
148
- already exists. This avoids projecting satisfied endpoints outward and then
149
- allowing an underconstrained solve to translate the arc while shrinking it back.
150
- Shared endpoints average simultaneous
151
- proposals, without giving one arc ownership; locked/upstream, fixed and explicitly
152
- positioned axes are not overwritten. The resulting seed is then solved against
153
- all structural equations and explicit constraints. Thus an isolated arc with
154
- radius 15 and endpoints initially at distances 10 starts at radius 15; if the
155
- center and endpoints are explicitly fixed at radius 10, its radius solves to 10.
156
- No extra lock, soft objective or degree of freedom is introduced.
157
- Center and endpoints
158
- can each reference a named upstream point. Zero-radius and coincident-endpoint arcs
159
- are errors; use `circle` for a full circle.
160
- The independent `sweep` constraint uses `['sweep', arcId, degrees]`, strictly greater than
161
- 0 and less than 360. Its positive magnitude follows the tuple's `cw`/`ccw`
162
- direction, so 270 means a major arc in either direction. It does not fix the arc's
163
- orientation: with a fixed center and radius, dragging an endpoint can rotate both
164
- endpoints while preserving the sweep.
165
-
166
- Geometry tuples hold current data; `constraints` specify what must remain true.
167
- Constraints use `['kind', target, value?]` and have no persistent IDs. Point coordinates have the same runtime
168
- meaning whether computed from an expression or written as literals. They may
169
- move during solving unless constrained. `fixed` locks one point at its supplied
170
- coordinates; `horizontal` / `vertical` target one local line. `length` targets
171
- one local line and takes its value in the third field. `['angle', line, degrees]`
172
- sets its orientation relative to +X, named Orientation in the editor.
173
- `['angle', [line1, line2], degrees]` sets the signed angle from the first line's
174
- authored start-to-end direction to the second, positive counterclockwise and
175
- equivalent modulo 360. `['parallel', [line1, line2]]` and
176
- `['perpendicular', [line1, line2]]` relate two local lines independently of their
177
- endpoint order; the finite lines need not intersect. Point constraints
178
- `x` / `y` likewise target one point with a third-field coordinate value, and
179
- `coincident` takes `[pointRef, pointRef]`. `midpoint` takes `[midpointRef, startRef, endRef]`
180
- and places the first point halfway between the other two, with no line entity required.
181
- A point reference may name locked upstream
182
- geometry. Lines must have nonzero length; length constraints must be positive.
183
- PlaneGCS solves each layer without modifying upstream values. The snapshot
184
- reports degrees of freedom and redundant constraint indices. Conflicts are
185
- located at their source tuples when inline source is available.
186
-
187
- In the App, select a sketch expression or variable to open its 2D editor. Draw
188
- continuous lines, drag literal-coordinate points, and delete local entities.
189
- In Select, an ordinary click or box selection replaces the selection, Ctrl
190
- toggles elements, and Shift only adds them. Drag left-to-right to select fully
191
- enclosed geometry or right-to-left to select intersecting geometry. With multiple
192
- elements selected, constraint tools can remove any editable local relation on
193
- the selected elements; adding a constraint requires the entire selection to
194
- satisfy its prerequisites. Parallel accepts two or more local lines and creates
195
- pairwise relations; Perpendicular and Angle between lines require exactly two.
196
- Circle takes a center (with optional X/Y input), followed by a radius or a
197
- circumference click. Entered Radius creates a persistent radius constraint;
198
- blank Radius follows the pointer and remains free. Drag a circle edge to change
199
- its radius, or its center to move it. Expression radii remain source-edited;
200
- gesture locks, numeric writeback and rounded-source replay use the same pipeline
201
- as point coordinates. Circle creation, deletion and associated constraint changes
202
- are single undo steps; deleting a circle retains shared and upstream centers,
203
- and removes only newly disconnected local points.
204
- Arc takes a center (optional X/Y), a start point (optional Radius), and an end
205
- point projected to that radius. New drawings default to clockwise (CW); press R to
206
- reverse the preview. Completing or canceling restores CW for the next drawing;
207
- existing arcs keep their explicit direction.
208
- Entered Radius and end-point Sweep become independent persistent
209
- constraints; blank fields remain free. R preserves the entered sweep magnitude.
210
- All points, the arc and
211
- constraints are one source transaction/undo. Drag its edge to edit a literal
212
- radius, or drag its ordinary center or endpoints;
213
- or select an interval and Delete to trim it. Radius and sweep labels lie on the
214
- directed arc; sweep guides connect its center and endpoints. Arc radius expressions
215
- use the same source protection and gesture-only locks as circle radii.
216
- Deletion also recognizes ordinary points lying on finite curves, not just explicit
217
- references, and preserves points still connected to other curves.
218
- Circles and finite arcs can delimit line trims, including upstream curves; the
219
- cutting curves and their expressions/constraints stay unchanged. Tangencies
220
- provide one boundary and arc gaps provide none. Circles and arcs use the same
221
- interval selection and Trim tool. Circles have cyclic intervals without an
222
- artificial zero-angle seam; zero or one boundary means whole-circle deletion.
223
- Trimming a circle leaves a CW arc with the same ID. End trims retain an arc ID;
224
- interior trims retire it and allocate two fresh IDs, preserving direction.
225
- Center/radius expressions and radius constraints follow surviving arcs; original
226
- whole-arc sweep constraints are removed. Coincident intervals are trimmed
227
- together, sharing cut points and one undo transaction.
228
- Endpoints are created or reused by Line; there is no standalone Point tool.
229
- Type X/Y for the start, then length/angle for each segment. Tab switches fields
230
- and Enter accepts the next endpoint. Each segment is one undo step and reuses
231
- its endpoint for the next segment. Escape ends the chain without removing
232
- completed segments; press it again to exit the tool. Blank fields follow the
233
- pointer. Snap uses points, the origin, a dense adaptive grid and horizontal/vertical
234
- directions; hold Alt to bypass it.
235
- After choosing the start point, press X for a horizontal axis lock or Y for
236
- vertical; press the same key again to unlock. This also works in numeric fields.
237
- The pointer chooses either direction along the locked axis; Length still applies.
238
- Entering Angle replaces the axis lock and locking an axis clears Angle.
239
- Snap/Alt do not override the lock; finishing or canceling a segment clears it.
240
- Entered coordinates, length and angle, and the final active X/Y lock generate
241
- explicit constraints in the same source transaction as the new segment. Turning
242
- the lock off before committing creates no direction constraint; resetting the
243
- next segment does not remove existing constraints. Ordinary automatic snapping
244
- does not create constraints. Numeric fields keep native text undo/redo, whose
245
- grouping belongs to the browser; canvas undo edits source.
246
- Rectangle uses two opposite corners. Enter Width/Height or let the pointer set
247
- them; positive dimensions retain their magnitude while the pointer chooses the
248
- quadrant. It creates ordinary points and lines with horizontal/vertical constraints,
249
- so later edits preserve right angles. Entered sizes constrain adjacent sides.
250
- Snapped corners reuse existing point references, including named upstream points.
251
- The entire rectangle is one source transaction and undo step. Escape cancels its
252
- draft; a successful rectangle starts a new draft. No rectangle entity is added
253
- to the author format. Center rectangle chooses a center and corner instead;
254
- Width/Height are still full side lengths. It retains an ordinary referenceable
255
- center point, allocated before new corners, and adds one midpoint constraint
256
- between it and opposite corners. A derived sketch can reference that center
257
- with `base.point(id)`. Both rectangle modes share inputs, snapping and undo.
258
- Dragging previews a soft solver target and writes every changed editable point
259
- in one transaction. Hard constraints remain satisfied. Rules receive the whole
260
- gesture context, without framework-level point classification or partitioning.
261
- They recognize centers for preferred local translation, prefer related
262
- centers or far connected points as soft references, and handle an unconstrained
263
- sole junction per branch. Radius gestures prefer the curve center.
264
- Dragging an arc endpoint first prefers keeping its related arc centers in place.
265
- Dragging a circle or arc center prefers keeping the radii of its curves unchanged
266
- before following the mouse. A point that is both an arc endpoint and another
267
- curve's center retains both preferences, with the related arc centers first.
268
- Within those preferences, the gesture reaches the closest feasible mouse position,
269
- then prefers local translation and minimizes exterior movement. A constrained
270
- center or endpoint can therefore stop short of the pointer. Dragging the curve
271
- itself still adjusts an editable radius. Connected lines do not disable center
272
- translation: their constraints determine how exterior points follow.
273
- Finally, a common lowest-priority preference keeps every other point near its
274
- gesture-start position, resolving only freedom left by those earlier stages.
275
- It does not pull a translated shape back or turn the other points into fixed
276
- anchors. Point aliases share one preference, and radius gestures apply this
277
- final preference to all points without adding new radius constraints.
278
- Each stage respects all hard constraints; later stages retain earlier achieved
279
- target parameter values for this frame, not every equivalent optimum. No original
280
- reference position is made an unconditional anchor, and no stage lock survives
281
- the frame, adds source constraints or reduces the reported model DOF. An unrestricted center
282
- rectangle translates when its center moves, without hidden editor metadata.
283
- Points already on lines, circles or directed arcs at gesture start retain that relation:
284
- they can slide along the curve, and follow changes to endpoints, centers and radii. The editor
285
- uses model-space geometric tolerance, not pointer hit areas; lines crossed during
286
- a gesture do not become sticky. Curves keep their IDs and types, without
287
- splitting or adding author constraints. Lines and arcs retain their finite bounds;
288
- CW/CCW arcs never include their missing circular portion. Center moves prefer
289
- translating their followers, while radius gestures prefer their existing polar
290
- directions. Read-only upstream curves can guide local
291
- points. Source replay checks that these gesture-only connections remain satisfied.
292
- This does not create intersection points or persist curve parameters.
293
- Movement without authored or inferred equations needs no native kernel;
294
- point-on-curve dragging uses the Worker solver. During a drag, the editor uses the AST to lock each expression
295
- coordinate to its evaluated author value: `[width, 0]` locks X but allows Y to move.
296
- These numeric locks apply to all local points, not just the dragged point, and do
297
- not become permanent constraints or change normal evaluation. If imposing these
298
- locks changes the displayed geometry, the solver satisfies them before preparing
299
- the rule context. Initially unsatisfied author data can therefore adjust on
300
- the first drag. Editable axes alone are written back; expressions never gain
301
- offsets. Frames retain a gesture-start reference alongside the preceding solution.
302
- Previews forward-solve the exact, losslessly serialized data that recompilation uses.
303
- Deleting a point also deletes connected local lines and affected constraints. Upstream geometry stays
304
- locked but can supply endpoints for new lines. Coordinates using expressions
305
- remain editable in code, not by dragging; literal axes on the same point remain
306
- draggable. The editor preserves existing IDs and
307
- allocates new IDs from the current local maximum, without `nextId` metadata.
308
- Deleted IDs may therefore be reused; downstream references are not automatically
309
- rewritten.
310
-
311
- ### Closed regions and modeling
312
-
313
- `s.face()` requires exactly one closed region, including its holes. `s.faces()`
314
- returns all regions as an ordinary readonly array; a sketch without curves returns
315
- `[]`. Neither query assigns persistent region IDs. Future ID-based selection will
316
- retain the no-argument meanings; array positions are not stable identifiers.
317
-
318
- Straight lines, circles and finite CW/CCW arcs form exact B-Rep boundaries.
319
- Upstream geometry is included. Disconnected contours produce separate faces;
320
- nested contours alternate material, holes and islands. Standalone points do not
321
- form boundaries. Open, crossing, touching, overlapping or branched contours report
322
- an error instead of implicitly trimming, closing, discarding or rewriting entities.
323
- The editor previews valid regions with a subtle fill and leaves unfinished sketches
324
- editable; construction diagnostics belong to the `.face()` / `.faces()` call.
325
-
326
- ```ts
327
- const ring = sketch([
328
- ['point', 1, [0, 0]],
329
- ['circle', 2, [1, 12]],
330
- ['circle', 3, [1, 8]],
331
- ]);
332
- const sleeve = ring.face().extrude(20);
333
- const tools = anotherSketch.faces().map(face => face.extrude(10));
334
- const result = stock.cut(tools); // equivalent to cut(stock, tools)
335
- ```
336
-
337
- Sketch `[x, y]` maps to model `[x, 0, -y]`, without recentering. Face extrusion
338
- follows its plane normal (`+Y` before rotation). Distance is signed, finite and
339
- nonzero; the start cap stays at the original face. `extrude(face, distance)` is
340
- equivalent to `face.extrude(distance)` and takes one face, never an array. Results
341
- are normal immutable solid models with caching, rendering, topology and source
342
- tracing; ordinary `.map()` handles multiple independent outputs.
343
-
344
- `loft(sections, options)` still takes one face per section. Zero holes and one
345
- corresponding hole per section work with ordinary or spine-guided lofts. Different
346
- hole counts and multiple unpaired holes produce explicit errors; holes are never
347
- silently filled. Arrays in `cut` and `loft` describe one operation's inputs, not
348
- automatic mapping. General hole correspondence and region ID selectors remain
349
- future API work.
350
-
351
- See the [modeling example](../app/examples/sketch-modeling.ts),
352
- the [sketch example](../app/examples/sketches.ts) and
353
- [third-party solver sources](THIRD_PARTY.md).
354
114
 
355
- ## Type imports
356
-
357
- Types used by public signatures, generic constraints, and return values are
358
- exported alongside the authoring API, including their named type dependencies.
359
- This includes `ElementKind`, `ModelKind`, `ModelGeometryKind`, `TopologyKind`,
360
- the named-element and expose result types, and the model capability interfaces.
361
-
362
- ```ts
363
- import type {
364
- Anchor,
365
- ElementKind,
366
- NamedElements,
367
- SolidModel,
368
- } from '@code3d/core';
369
-
370
- type Mount<Kind extends ElementKind> = Anchor<Kind>;
371
- type Part<Elements extends NamedElements> = SolidModel<Elements>;
372
- ```
373
-
374
- Code3d model types come from `@code3d/core`; Replicad builder types such as
375
- `Shape3D` come from `@code3d/core/replicad`. Type exports do not add runtime
376
- properties or operations. `Quaternion` belongs to the tooling transform API;
377
- author rotations use `rotate(x, y, z)` in degrees.
378
-
379
- ## Materials
380
-
381
- `model.material(value)` returns a new model with a complete replacement material.
382
- Pass a native [Three.js material](https://threejs.org/docs/pages/Material.html),
383
- for example `MeshStandardMaterial` or `MeshPhysicalMaterial`, from
384
- `@code3d/core/three`. This entry directly re-exports the native classes and types
385
- from Core's Three.js dependency, in both the App and Node. Reusable modeling
386
- packages use the same entry so their material instances match Core.
387
-
388
- ```ts
389
- import {box, group} from '@code3d/core';
390
- import {MeshPhysicalMaterial, MeshStandardMaterial} from '@code3d/core/three';
391
-
392
- const lacquer = new MeshPhysicalMaterial({
393
- color: '#eb633e',
394
- roughness: 0.25,
395
- clearcoat: 1,
396
- });
397
- const part = box(10, 10, 10).material(lacquer);
398
- const matte = part.material(new MeshStandardMaterial({color: '#8ed5d1'}));
399
- // matte uses the complete new material; it does not inherit clearcoat.
400
- const assembly = group([part, matte]).material(lacquer);
401
- ```
402
-
403
- Assignment captures the material and loaded texture pixels by value. Later
404
- changes to the Three.js instance do not alter an existing model; call
405
- `material()` again to capture new values. A group overrides the entire
406
- material of every descendant, including nested groups. The outermost override
407
- wins; shared parts retain their materials outside that composition.
408
-
409
- Named imports and `import * as THREE from "@code3d/core/three"` are both supported.
410
- A reusable modeling package shares the host project's Core dependency; its own
411
- independently bundled Three.js copy does not provide the same class identity.
412
-
413
- The renderer restores and owns Three.js instances from the captured JSON.
414
- Modeling emphasis uses separate preview copies; Render mode and its PNG export
415
- use the authored material. Use mesh materials for surfaces/solids,
416
- `LineBasicMaterial` or `LineDashedMaterial` for curves, and `PointsMaterial` for
417
- vertices. Choose a corresponding class when overriding a group.
418
-
419
- Loaded image, canvas, ImageBitmap, data and cube textures are captured with their
420
- sampling settings. In the modeling worker, use `ImageBitmapLoader` for image
421
- assets. Assign only after loading completes. Tessellated faces expose their
422
- native UV coordinates normalized to 0–1 per face; use texture `repeat`, `offset`
423
- and `rotation` to adjust placement. This is a per-face mapping, not a model-wide
424
- UV unwrap. See the App's `/examples/materials.ts`.
425
-
426
- Materials follow Three.js's `toJSON()` / `MaterialLoader` representation.
427
- Custom material classes, renderer callbacks such as `onBeforeCompile`, live
428
- video/render-target textures, compressed/layered textures and manual mipmaps
429
- cannot cross this boundary and are rejected. Material-local clipping, shadow-side and precision overrides are not serialized
430
- by Three.js and are also rejected. Shader materials must use uniforms
431
- supported by Three.js's JSON representation.
432
-
433
- For a simple color, `.material('#f80')` replaces the whole material with the
434
- default material for that geometry kind. Strings accept CSS names, `#RGB`,
435
- `#RGBA`, `#RRGGBB`, `#RRGGBBAA`, `rgb(...)` and `rgba(...)`, including percentage
436
- channels and space-separated values with slash alpha. For example,
437
- `.material('#f008')` equals `.material('#ff000088')`, and
438
- `.material('rgb(100% 0% 0% / 50%)')` is half-opaque red. Native Three.js instances
439
- use their own `opacity` and `transparent` settings.
440
-
441
- STEP and 3MF preserve the effective base color and opacity; they cannot encode
442
- Three.js shaders or textures. STL contains geometry only.
443
-
444
- ## Bound relations and rotation
445
-
446
- `geometry.on(target.up)` translates the source's matching bounding boundary
447
- onto a directional `Bound`. Targets are `up` (+Y), `down` (−Y), `right` (+X),
448
- `left` (−X), `front` (+Z), and `back` (−Z), in the target model's local frame.
449
- Bounds describe the current finite geometry, including solved children of a
450
- group. They are references owned by the model, not topology surfaces or extra
451
- box models. Their calculation uses analytic geometry independently of meshing.
452
-
453
- The source can be a model, vertex, edge, surface, or finite point anchor.
454
- Only the selected geometry contributes its extent. Its support boundary is
455
- computed in the target's direction, even when the geometry is tilted. `on`
456
- never rotates or centers a model. A single contact preserves tangential
457
- position; multiple contacts solve their translation conditions together and
458
- report conflicting positions. Mathematical lines and planes without finite
459
- geometry cannot be sources. Arbitrary models, points, lines, and surfaces
460
- cannot be targets.
461
-
462
- ```ts
463
- import {box, group} from '@code3d/core';
464
- const base = box(10, 10, 10);
465
- const part = box(20, 20, 20).relate(self => [
466
- self.on(base.right),
467
- self.on(base.down),
468
- ]);
469
- export default group([base, part]); // part at [15, -15, 0]
115
+ export const part = profile.face().extrude(3);
470
116
  ```
471
117
 
472
- An explicit `.offset(x, y, z)` pins the matching bound centers in all three
473
- coordinates of the target reference frame, including an all-zero offset.
474
- Repeated offsets add. `bound.flip()` reverses facing and therefore the side
475
- from which the source touches it, while leaving the reference frame unchanged.
476
- Surface `flip()` likewise reverses facing metadata; neither operation mirrors
477
- or rotates geometry. Two flips restore the original facing.
478
-
479
- `relate` always owns the placement. These forms are legal:
118
+ Closed regions bridge sketches to ordinary face and solid modeling. Read the
119
+ [sketch reference](../web/src/content/docs/docs/reference/core.md#editable-sketch-regions)
120
+ and [agent sketch workflow](../../docs/agents/sketches.md) for constraints,
121
+ derived layers, observations, and failure diagnostics. Exact tuple types and
122
+ solver behavior live in [sketch.ts](src/library/sketch.ts) and
123
+ [sketch-solver.ts](src/library/sketch-solver.ts).
480
124
 
481
- ```ts
482
- part.relate(self => self.on(base.up));
483
- part.relate(() => part.on(base.up));
484
- part.relate(self => base.on(self.up)); // moves part below base
485
- ```
486
-
487
- References to the original receiver are rebound to the new self. Every
488
- returned relation must involve self or the original receiver. Old model values
489
- and old references keep their meaning.
125
+ ### Relating a sketch to a model plane
490
126
 
491
- Explicit rotation belongs to a particular contact chain:
127
+ `s.relate(self => self.plane.align(target))` returns a new sketch with spatial
128
+ relations, leaving its shared 2D definition and the original sketch unchanged.
129
+ It works before a face exists, including `sketch()` and open contours.
492
130
 
493
131
  ```ts
494
- self.on(base.up).rotate(0, 30, 0);
495
- self.on(base.up).pivot([50, 0, 0]).rotate(0, 0, 45);
496
- self.on(base.up).pivotVertex(3).rotate(0, 0, 45);
497
- self.on(base.up).around(base.axis).rotate(30);
498
- ```
499
-
500
- `pivot` coordinates, `pivotVertex` IDs, and XYZ axes use relate's **self**,
501
- regardless of which side of `on` contains self. Direct rotation uses self's
502
- origin. Angles are degrees, applied X, then Y, then Z. Each pivot or axis
503
- selection lasts for its next rotation; intermediate chains only complete with
504
- `rotate`. An axis reference includes position and direction; external axes use
505
- their resolved composition pose. Rotations compose in call order, following
506
- that chain's contact placement. Other contacts constrain the final pose.
507
- Explicit orientations on one self must agree. Remaining translations minimize
508
- changes at the authored contact stages; duplicate stages do not add bias.
509
-
510
- Related objects are solved at composition, Boolean, and loft boundaries.
511
- Standalone views keep the object's own geometry. A group moves its assembled
512
- children rigidly. `expose()` carries finite references into the group frame.
513
- Core and App share the same solvers. Pure bound assemblies use exact linear
514
- translation equations; geometric align relations add joint rigid-pose solving
515
- without another WASM initialization.
516
-
517
- In the App, selecting a directional property fills its bound face with the
518
- same translucent yellow-green as its bounding box, adding corner brackets
519
- only when that box is absent. The source of `on` shows the complete measured
520
- bounding box; a topology source limits it to the selected geometry.
521
- `pivot` has translation handles, `pivotVertex` uses
522
- self's vertex picker, `around` shows the referenced axis, and `rotate` has
523
- three angle rings or one axis ring. Source edits retain parameter provenance,
524
- preview/cancel behavior, and undo. See the
525
- [bent loft example](../app/examples/bound-rotation.ts).
526
-
527
- ## Geometric alignment
528
-
529
- `source.align(target)` relates points, curves, and surfaces and can determine
530
- both position and orientation. Select a solid's `center`, `axis`, `vertex`,
531
- `edge`, or `surface`; a solid or group itself is not an align reference.
532
-
533
- | Pair | Meaning |
534
- | --------------- | ------------------------------------------------------------ |
535
- | point–point | Coincident points |
536
- | point–curve | Point lies on the underlying curve |
537
- | point–surface | Point lies on the underlying surface |
538
- | curve–curve | Complete underlying curves coincide, with the same direction |
539
- | curve–surface | The whole underlying curve lies on the surface |
540
- | surface–surface | Underlying surfaces coincide, with matching normal sense |
541
-
542
- The first implementation supports points, straight lines, circles, ellipses,
543
- planes, cylinders, and spheres. Edge trims, arc ranges, face boundaries, and
544
- holes do not limit the supporting geometry. Equal-radius arcs may coincide
545
- without matching endpoints; straight lines retain axial sliding and twisting.
546
- Use a curve's `start`, `midpoint`, `end`, or a selected vertex for more specific
547
- positioning. Unsupported geometry reports an error. Different radii and other
548
- proven impossibilities report geometric incompatibility; numerical
549
- nonconvergence does not prove that no solution exists.
132
+ import {box, sketch} from '@code3d/core';
550
133
 
551
- ```ts
552
- const placed = cylinder(5, 20).relate(self => [
553
- self.axis.align(base.axis),
554
- self.on(base.up),
134
+ const host = box(40, 20, 30).rotate(0, 0, 25);
135
+ const profile = sketch([
136
+ ['point', 1, [0, 0]],
137
+ ['circle', 2, [1, 4]],
555
138
  ]);
556
- const backwards = line([0, 0, 0], [0, 10, 0]).relate(self =>
557
- self.align(base.axis.reverse()),
558
- );
559
- ```
560
-
561
- `reverse()` changes a line reference's positive direction. `flip()` changes a
562
- surface's normal sense. Both preserve geometry and reference axes. Point
563
- membership ignores direction, and curve-to-surface membership adds no arbitrary
564
- heading within the surface. `around(axis.reverse())` reverses the signed
565
- rotation direction; curved edges still do not define a single rotation axis.
566
-
567
- `align(...).offset(x, y, z)` translates **self** in the target reference axes
568
- after alignment, then applies the authored `rotate`/`pivot`/`around` chain.
569
- Repeated offsets add; zero adds no positioning condition. Unlike `on.offset`,
570
- it does not pin trim centers or parameter origins. This holds when self is the
571
- written target too. Multiple relations are solved jointly. An already satisfied
572
- relation preserves its pose; remaining freedom is selected deterministically
573
- by geometric seeds and local numerical solving, without a uniqueness guarantee.
574
-
575
- In relation context, axes have one positive arrow and edges retain their actual
576
- curved highlight as the shaft, with only a tangent arrowhead at the directed
577
- endpoint (a stable seam for a closed edge). Source and target arrowheads share
578
- a fixed 10-by-6 CSS-pixel size. Passive axes have arrows at both ends. Passive line
579
- decorations use 1px; interactive topology selection uses 2px. Surface normals
580
- retain their facing arrows.
581
- See [the alignment example](../app/examples/geometric-alignment.ts).
582
-
583
- Source inspection previews each relation call through that stage, before later
584
- offsets or rotations. The current pair shares one marker color: the inspected
585
- side keeps its base opacity and the other uses 70% of that opacity. Remaining
586
- related objects are dim gray. `on`/`align` and subsequent chain calls focus
587
- self; their target arguments focus the actual target reference, which is self
588
- in a reverse-written relation. Model opacity uses role-specific caps rather than
589
- multiplying existing material opacity. See the
590
- [relation guide](../web/src/content/docs/docs/guides/relations.mdx) and
591
- [visualization conventions](../../.agents/skills/code3d-visualization/SKILL.md).
592
-
593
- When several relations include align on one model, use the numeric parameter
594
- panel or source to edit offsets and rotations. Each edit resolves the coupled
595
- equations. Spatial drags are available for a single align relation; combined
596
- geometric relations do not show a misleading rigid-transform preview.
597
-
598
- ## Exposed geometry and topology
599
-
600
- `expose()` preserves geometry as a reference in the returned model's frame.
601
- A solid, face, edge, or vertex model becomes a `Solid`, `Surface`, `Edge`, or
602
- `Vertex` reference. Existing topology references retain their identity; pure
603
- point, line, plane, and frame anchors retain their reference-geometry meaning.
604
- Named members remain available, including on an exposed group frame.
605
- Selected topology contributes its own finite extent to bound positioning; a
606
- custom model origin does not change that extent.
607
-
608
- ```ts
609
- const plate = box(32, 4, 24);
610
- const assembly = group([plate]).expose({body: plate, mount: plate.surface(1)});
611
- const boundary = assembly.mount.edges();
612
- const corners = boundary[0].vertices();
613
- const center = assembly.mount.center;
614
- ```
615
-
616
- References support geometric queries and `on()`. They do not have model
617
- operations such as `rotate`, `scaled`, `fillet`, or `relate`. A relation authored
618
- through `self.mount.center` acts on `self`, including when `self` is an assembly.
619
- Every chained result carries that assembly context while its geometry and IDs
620
- continue to refer to the original immutable source. Exposing an upstream value
621
- captures that source; later modeling operations do not reinterpret its IDs in
622
- a different geometry. To expose result topology, select it from that result.
623
-
624
- Subtopology access follows dimension: a surface can query its edges and
625
- vertices, and an edge its vertices. Singular queries validate membership;
626
- plural queries retain authored order and allow `[]`. IDs always use the source
627
- geometry's namespace, so a shared edge has the same ID through either face.
628
- An edge's vertices are its actual topological vertices; a closed edge can have
629
- one vertex. A source used in multiple occurrences must be exposed through the
630
- intended child's reference, such as `left.body`, to select its placement.
631
-
632
- Every geometric reference has a local bounding-box `center` point, carried
633
- through rotation and scaling. `Edge.start`, `.midpoint`, and `.end` sample curve
634
- parameters 0, 0.5, and 1; the midpoint need not be the bounding-box center or the
635
- half-length point. These calculated points are anchors, not topology vertices.
636
- `edge.on()` and `surface.on()` use finite geometry extents rather than their
637
- sampled tangent or normal; `.center.on()` uses only the calculated point.
638
-
639
- ## Origins and rotation
640
-
641
- All models, including groups, support immutable origin editing and rotation:
642
-
643
- ```ts
644
- const part = box(24, 6, 14)
645
- .originVertex(3)
646
- .originOffset(0, 2, 0)
647
- .rotate(15, 35, 0);
648
- ```
649
-
650
- - `originPoint(pointRef)` makes a center, named point, or topology vertex local zero.
651
- - `originVertex(id)` selects a geometric model’s own input vertex; it is equivalent
652
- to `model.originPoint(model.vertex(id))`.
653
- - `originCenter()` makes the model's `center` anchor local zero.
654
- - `originOffset(dx, dy, dz)` re-expresses every local point as `p - [dx, dy, dz]`.
655
- - `rotate(x, y, z)` rotates about local zero in degrees, applying fixed local
656
- X, then Y, then Z rotations. Repeated calls compose in source order.
657
-
658
- The model origin is always zero in its own coordinates. Origin offsets compose
659
- and cancel; geometry, centers, named references and topology positions all use
660
- the resulting coordinates. Directions and topology IDs are preserved. Old model
661
- values and captured references keep their original meaning. `center` begins at
662
- the body's local bounding-box center and follows transforms; rotating does not
663
- recalculate it from the new axis-aligned bounds. `scaled()` scales about current
664
- local zero. Later origin edits preserve the already-rotated shape.
665
-
666
- Dimensions use scalar arguments; positions use arrays. `point()` is local zero;
667
- `point([x, y, z])` equals `point().originOffset(-x, -y, -z)`. `line([x, y, z])`
668
- starts at zero; `line(start, end)` accepts two position arrays. Their input
669
- coordinates remain local geometry coordinates. For example, `line([10, 0, 0])`
670
- has center `[5, 0, 0]`, and `.rotate(0, 90, 0)` takes its end to `[0, 0, -10]`.
671
- A curve's tangent reference frame does not redefine model XYZ. Directional
672
- bounds use the model axes, including after geometric rotation.
673
-
674
- Groups provide `originPoint()`, `originOffset()` and `rotate()`. Their default origin is
675
- chosen when constructed: solve the direct members' placement, then take the
676
- axis-aligned bounding-box center of their **origins**, retaining the assembly
677
- axes. Geometry size does not affect this choice. A nested group contributes
678
- only its own origin; an empty group defaults to zero. Explicit origin edits
679
- re-express the assembled result together, preserving internal constraints and
680
- member spacing. The default is not recalculated on later operations.
681
- `rotate(x, y, z)` turns the solved assembly together about its current origin,
682
- including nested instances, without re-solving internal relations. Named
683
- references and topology follow the members; directional bounds use group axes.
684
-
685
- ```ts
686
- const base = box(20, 4, 10).originOffset(0, 2, 0);
687
- const lid = box(20, 2, 10).originOffset(0, -1, 0);
688
- const assembly = group([base, lid]); // Common origins at their contact plane.
689
- const mounted = assembly.originPoint(lid.center);
690
- const tilted = mounted.rotate(0, 0, 30); // Rotate the whole assembly about the lid center.
139
+ const opening = profile.relate(s => s.plane.align(host.surface(4)));
140
+ const result = host.cut([opening.face().extrude(-20)]);
141
+ const draft = sketch().relate(s => s.plane.align(host.surface(2)));
691
142
  ```
692
143
 
693
- `originPoint()` converts references to the receiver's local frame, including
694
- solved member placements. With repeated geometry, select a specific instance's
695
- named point, for example `assembly.originPoint(rightPart.body.center)`; an
696
- ambiguous shared source is rejected. Groups have no aggregate vertex IDs or
697
- geometric `center` or scaling methods. The
698
- [group origins example](../app/examples/group-origins.ts) shows direct assembly
699
- and selection in repeated instances.
700
-
701
- In the App, origin offsets have translation arrows; `originVertex`
702
- uses vertex picking and an origin marker. Dragging an `originPoint()`, `originCenter()` or
703
- `originVertex()` marker adds or edits an `originOffset()` call. Rotation rings edit the corresponding
704
- angle about its effective axis, including when other angles are nonzero. Dragging
705
- uses the gesture-start snapshot: the candidate origin moves against fixed
706
- geometry. Release writes source and switches to result coordinates, with origin
707
- zero and geometry shifted by the negative displacement; Escape restores the
708
- start state. The
709
- [origin and rotation example](../app/examples/origin-and-rotation.ts) demonstrates
710
- these scopes.
711
-
712
- ## Tubes
713
-
714
- `tube(outerRadius, innerRadius, y)` creates a concentric, constant-section
715
- straight tube with a through bore. Like `cylinder(radius, y)`, it is centered
716
- at the origin and extends along Y. All dimensions must be positive and finite,
717
- and `innerRadius` must be smaller than `outerRadius`; use `cylinder` for a solid
718
- cross-section. There are no wall-thickness overloads, tapers, or path options.
144
+ The target may be a named plane or a planar `host.surface(id)`. The sketch plane
145
+ normal is local `+Y`; alignment uses the same directed-plane, target-frame offset
146
+ and rotation semantics as model relations. It does not implicitly center the
147
+ sketch on a trimmed surface. An unbounded sketch plane cannot use `on()` to place
148
+ finite geometry against a bound; use `align()`. Topology-only pivots such as
149
+ `pivotVertex()` require a geometric model, not an empty sketch frame.
150
+
151
+ `derive()`, `face()` / `faces()` and extrusion inherit the relations. Boolean
152
+ operations and loft resolve their inputs in the shared composition context;
153
+ placement is not baked into tuple coordinates. A spatial copy shares point
154
+ identities with its original, so a derived layer can still use `profile.point(id)`.
155
+ References target the actual immutable model value: creating a later rotated or
156
+ repositioned model does not redirect existing sketch relations.
157
+
158
+ In the App, select the related value (`opening`) to edit against read-only model
159
+ outlines projected into the sketch's local plane. Select `profile` for its original
160
+ local view. Both edit the same source array, with ordinary undo; separate placements
161
+ of that geometry are not separate authoring definitions. Context outlines are
162
+ visual references only, not snapping targets or imported geometry constraints.
163
+ The select-surface-and-create UI is tracked separately within
164
+ [#114](https://github.com/vilicvane/code3d/issues/114).
165
+ Try [sketch-on-surface.ts](../app/examples/sketch-on-surface.ts).
166
+
167
+ ## Cached computations and custom primitives
168
+
169
+ `cached(fn, options?)` memoizes synchronous, deterministic data computations.
170
+ Pass changing captured state as arguments and treat returned data as immutable.
171
+ Memory hits reuse the retained result; optional `encoder` / `decoder` pairs only
172
+ run when saving to disk or restoring it. The App fingerprints static definitions
173
+ and their dependencies for persistent reuse; dynamic closures and ordinary Node
174
+ calls use function identity for memory reuse. No author cache IDs are needed.
175
+
176
+ `definePrimitive(builder)` from `@code3d/core/replicad` also caches construction,
177
+ normalization and geometry analysis. Each call still creates fresh model metadata
178
+ and independently owned geometry handles. The builder transfers its returned
179
+ solid to Core and owns its intermediate resources. Screws uses this shared cache.
180
+ Read [cached computations](../web/src/content/docs/docs/reference/core.md#cached-computations)
181
+ and [custom primitives](../web/src/content/docs/docs/guides/custom-primitives.mdx)
182
+ for supported data, resource ownership and examples.
183
+
184
+ ## Text and fonts
719
185
 
720
186
  ```ts
721
- import {tube} from '@code3d/core';
187
+ import {googleFont, text, extrude, group} from '@code3d/core';
722
188
 
723
- export const collar = tube(6, 4, 12).material('#8ed5d1');
189
+ const face = googleFont('Play');
190
+ export default group(extrude(text('Hello', face, 10), 1));
724
191
  ```
725
192
 
726
- ## Coils
727
-
728
- `coil(coilRadius, wireRadius, pitch, turns)` creates a right-handed coil with a
729
- circular wire section and plain ends. `coilRadius` measures from the Y axis to
730
- the wire centerline, `pitch` is the Y advance per turn, and `turns` can be any
731
- positive finite number, including a fraction. The centerline's Y interval is
732
- centered at the origin; the end sections extend slightly beyond that interval.
733
- The named axis stays on Y even for a partial turn.
734
-
735
- All dimensions must be positive and finite. The wire radius must be smaller
736
- than the coil radius, the pitch must exceed the wire diameter, and neighboring
737
- turns must remain separated. This is geometry, not a spring specification:
738
- there are no spring end treatments, force parameters, or material assumptions.
739
-
740
- ```ts
741
- import {coil} from '@code3d/core';
742
-
743
- export const winding = coil(5, 0.75, 4, 2.5).material('#d8ff3e');
193
+ In the App, `googleFont()` uses a static family name and optional weight/italic
194
+ settings; `font()` accepts a static font-file URL or TTF/OTF bytes. The engine
195
+ prepares remote resources before synchronous model execution. Text returns
196
+ ordinary planar faces with a common baseline; `extrude(faces, distance)` preserves
197
+ their order and placement. Node can read local file URLs or use downloaded,
198
+ decoded font bytes. See the [text reference](../web/src/content/docs/docs/reference/core.md#text),
199
+ [runnable example](../app/examples/text.ts) and [font notices](THIRD_PARTY.md).
200
+
201
+ ## Materials and entry points
202
+
203
+ `.material()` accepts a color or a native Three.js material. Use
204
+ `@code3d/core/three` when constructing native materials, and
205
+ [@code3d/materials](../materials/README.md) for common presets. A model captures
206
+ its material value; changing the original Three.js object later does not change
207
+ that model. The renderer supplies lighting and environment reflections.
208
+
209
+ | Import | Responsibility |
210
+ | ----------------------- | ------------------------------------------------------------------------ |
211
+ | `@code3d/core` | Public model authoring API; Node entry initializes the kernel |
212
+ | `@code3d/core/three` | Shared Three.js exports for material and geometry integration |
213
+ | `@code3d/core/replicad` | Replicad access for custom primitive builders |
214
+ | `@code3d/core/tooling` | Evaluation, inspection and resource lifetime integration used by the App |
215
+
216
+ Tooling integrations own evaluation lifetimes and disposal. Follow the existing
217
+ [tooling entry](src/tooling/index.ts), [evaluation tests](test/model-test.ts), and
218
+ [App compiler](../app/src/model/compiler.ts) when embedding the runtime. Ordinary
219
+ model files should stay on the authoring API.
220
+
221
+ ## Source and development
222
+
223
+ For changes to Core itself, start with the [modeling architecture](../../.agents/docs/architecture/modeling.md)
224
+ and shared [development guide](../../.agents/docs/development.md), then follow
225
+ the implementation and tests below.
226
+
227
+ - [Public exports](src/library/index.ts), [model runtime](src/library/runtime.ts),
228
+ and [public type tests](test/public-types.ts).
229
+ - [Spatial values](src/library/spatial.ts), [relation solving](src/library/relation-solver.ts),
230
+ and [topology](src/library/topology.ts).
231
+ - [Cached computations](src/library/cached.ts), [fonts](src/library/font.ts),
232
+ [text geometry](src/library/text.ts) and their [tests](test/).
233
+ - [Material values](src/library/material.ts), [kernel cache](src/library/kernel-cache.ts),
234
+ and [Node entry](src/node/index.ts).
235
+ - [Executable App examples](../app/examples/) and [runtime tests](test/).
236
+
237
+ Public JavaScript entries are prebundled ESM with shared chunks. Node, browser,
238
+ tooling and interop entries share the same kernel and cache instances. TypeScript
239
+ declarations, declaration maps and their sources remain available for editor
240
+ navigation. The build and npm `prepack` use the same package build script; see the
241
+ [development guide](../../.agents/docs/development.md#公开包产物) for installed
242
+ tarball verification and CI publishing.
243
+
244
+ For a standalone TypeScript project, include `ESNext` and `DOM` in `compilerOptions.lib`.
245
+ Use `module: "ESNext"` and `moduleResolution: "Bundler"` when esbuild or another
246
+ bundler handles execution. Code3D's App uses this resolution mode, supports
247
+ extensionless relative imports and selects browser package exports.
248
+ The public packages are built and verified with `skipLibCheck: false`. NodeNext
249
+ currently needs `skipLibCheck` because the `manifold-3d@3.0.1` declarations omit
250
+ relative `.js` extensions. Core includes the declaration dependencies needed by
251
+ its HarfBuzz and Replicad integrations.
252
+
253
+ From the repository root:
254
+
255
+ ```sh
256
+ npm run build:packages
257
+ npm test --workspace @code3d/core
744
258
  ```
745
259
 
746
- See the [primitive showcase](../app/examples/primitives.ts) for a coil composed
747
- with the other built-in primitives.
748
-
749
- ## Custom primitives
750
-
751
- `definePrimitive` turns a synchronous Replicad builder into a normal code3d
752
- solid model. Replicad stays behind an explicit author-interoperability entry so
753
- raw shapes do not become part of the root model API. Import `definePrimitive`
754
- and `replicad` from `@code3d/core/replicad`.
755
-
756
- The runnable [custom primitive example](../app/examples/custom-primitives.ts)
757
- builds a twisted knob with a D-shaped shaft bore, then composes two instances.
758
- It demonstrates direct parameter annotations, a default argument, Replicad
759
- extrusion and booleans, and intermediate resource cleanup. The screws package's
760
- private [thread builder](../screws/src/library/thread.ts) is another consumer.
761
- Built-in tubes and coils should be imported directly from core, not reimplemented
762
- in author examples.
763
-
764
- The builder's argument types, names, and optional parameters are preserved.
765
- Write validation inside the builder. It executes on every call, including when
766
- the same arguments are reused, so changes to captured state remain observable.
767
- Core caches the actual returned B-Rep, so identical output can reuse downstream
768
- operations and meshes across evaluations without skipping the builder. Each
769
- model owns its disposable geometry and uses the standard mesh tolerance.
770
-
771
- Returning a shape transfers its ownership to code3d; do not mutate, delete, or
772
- return it again. Intermediate resources remain the builder's responsibility,
773
- following Replicad's ownership rules. A single-solid aggregate produced by
774
- Replicad booleans is normalized; shells, multiple-solid aggregates, and stray
775
- lower-dimensional geometry are rejected, and rejected returned shapes are
776
- released. Kernel installation and replacement remain owned by code3d.
777
-
778
- Place `@code3d.param` annotations directly on the exported function variable to
779
- enable its call-site tool panel, including when consumed through emitted
780
- declarations. No wrapper or definition options are needed. For a standalone
781
- preview, export an ordinary example invocation; `@code3d.arguments`
782
- is not expanded to recognize primitive factory definitions.
783
-
784
- ## Tooling evaluation lifetime
785
-
786
- The App uses the selected runtime's `@code3d/core/tooling` entry, from the project
787
- when core is declared or from the built-in package otherwise. This internal
788
- integration surface evolves with the App during prototyping and does not promise
789
- API stability. It includes topology source identities, assembly transforms, and
790
- calculated-anchor frames alongside origin, spatial-operation, and sketch layer
791
- snapshots. It requires installing OpenCascade and the sketch constraint solver
792
- from that same package dependency graph.
793
- Call `beginModelEvaluation(): () => void` before each serial source
794
- evaluation to reset source locations, parameter provenance, and operation
795
- traces. Call the returned function in `finally`, after creating snapshots.
796
- Geometry, model identity, and relations remain unchanged; already-created
797
- snapshots keep their previous evaluation's metadata.
798
-
799
- The kernel cache retains the complete working set of the latest evaluation,
800
- including exact transformed-bound queries and render meshes. During evaluation,
801
- both the previous and current working sets are protected from eviction. Finishing
802
- keeps the current set and at most 256 unused historical entries, releasing older
803
- native values. This scales retention with the current model without accumulating
804
- every edited revision. Calls outside an evaluation use the bounded history.
805
-
806
- Packages may retain model values privately. The App therefore drops its own
807
- references after creating snapshots instead of forcibly disposing every model
808
- it encounters. Unreachable Replicad wrappers release their native resources
809
- through their finalizers; explicit disposal is appropriate only when the caller
810
- owns the complete model lifetime. This boundary is tooling-only: ordinary
811
- model authors do not initialize an evaluation session.
812
-
813
- Rendering snapshots contain serializable meshes and model metadata, without
814
- native shapes. File export uses a separate `ModelGeometrySnapshot` retained
815
- by the compiler Worker. Core clones each distinct source shape once; the
816
- compiler releases these copies before the next compilation, when replacing
817
- the runtime, or when it is disposed. The snapshot's shapes are borrowed by
818
- consumers: each export clones them before transformations or consuming kernel
819
- operations and releases its temporary geometry on both success and failure.
820
- Repeated exports therefore preserve the retained geometry and author models.
821
-
822
- Core owns snapshot creation; the App owns export placement and file generation,
823
- using Replicad from the same runtime. This division already serves the current
824
- consumers and changes only when a concrete use case calls for it.
260
+ Use the [agent entry](../../docs/agents.md) to work on a project through the CLI,
261
+ or the [App README](../app/README.md) to develop the editor and visualization.