@code3d/core 0.0.1-alpha.0

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 (172) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +824 -0
  3. package/THIRD_PARTY.md +28 -0
  4. package/bld/library/alignment-geometry.d.ts +50 -0
  5. package/bld/library/alignment-geometry.d.ts.map +1 -0
  6. package/bld/library/alignment-geometry.js +322 -0
  7. package/bld/library/alignment-geometry.js.map +1 -0
  8. package/bld/library/bound-solver.d.ts +33 -0
  9. package/bld/library/bound-solver.d.ts.map +1 -0
  10. package/bld/library/bound-solver.js +252 -0
  11. package/bld/library/bound-solver.js.map +1 -0
  12. package/bld/library/extrude.d.ts +8 -0
  13. package/bld/library/extrude.d.ts.map +1 -0
  14. package/bld/library/extrude.js +28 -0
  15. package/bld/library/extrude.js.map +1 -0
  16. package/bld/library/index.d.ts +7 -0
  17. package/bld/library/index.d.ts.map +1 -0
  18. package/bld/library/index.js +3 -0
  19. package/bld/library/index.js.map +1 -0
  20. package/bld/library/kernel-artifact-codec.d.ts +3 -0
  21. package/bld/library/kernel-artifact-codec.d.ts.map +1 -0
  22. package/bld/library/kernel-artifact-codec.js +148 -0
  23. package/bld/library/kernel-artifact-codec.js.map +1 -0
  24. package/bld/library/kernel-cache.d.ts +63 -0
  25. package/bld/library/kernel-cache.d.ts.map +1 -0
  26. package/bld/library/kernel-cache.js +245 -0
  27. package/bld/library/kernel-cache.js.map +1 -0
  28. package/bld/library/kernel-shapes.d.ts +20 -0
  29. package/bld/library/kernel-shapes.d.ts.map +1 -0
  30. package/bld/library/kernel-shapes.js +108 -0
  31. package/bld/library/kernel-shapes.js.map +1 -0
  32. package/bld/library/loft.d.ts +7 -0
  33. package/bld/library/loft.d.ts.map +1 -0
  34. package/bld/library/loft.js +161 -0
  35. package/bld/library/loft.js.map +1 -0
  36. package/bld/library/material.d.ts +12 -0
  37. package/bld/library/material.d.ts.map +1 -0
  38. package/bld/library/material.js +176 -0
  39. package/bld/library/material.js.map +1 -0
  40. package/bld/library/model-color.d.ts +8 -0
  41. package/bld/library/model-color.d.ts.map +1 -0
  42. package/bld/library/model-color.js +36 -0
  43. package/bld/library/model-color.js.map +1 -0
  44. package/bld/library/open-cascade-error.d.ts +2 -0
  45. package/bld/library/open-cascade-error.d.ts.map +1 -0
  46. package/bld/library/open-cascade-error.js +22 -0
  47. package/bld/library/open-cascade-error.js.map +1 -0
  48. package/bld/library/relation-solver.d.ts +29 -0
  49. package/bld/library/relation-solver.d.ts.map +1 -0
  50. package/bld/library/relation-solver.js +258 -0
  51. package/bld/library/relation-solver.js.map +1 -0
  52. package/bld/library/replicad.d.ts +17 -0
  53. package/bld/library/replicad.d.ts.map +1 -0
  54. package/bld/library/replicad.js +24 -0
  55. package/bld/library/replicad.js.map +1 -0
  56. package/bld/library/retained-memory.d.ts +7 -0
  57. package/bld/library/retained-memory.d.ts.map +1 -0
  58. package/bld/library/retained-memory.js +27 -0
  59. package/bld/library/retained-memory.js.map +1 -0
  60. package/bld/library/runtime.d.ts +775 -0
  61. package/bld/library/runtime.d.ts.map +1 -0
  62. package/bld/library/runtime.js +2954 -0
  63. package/bld/library/runtime.js.map +1 -0
  64. package/bld/library/shell.d.ts +7 -0
  65. package/bld/library/shell.d.ts.map +1 -0
  66. package/bld/library/shell.js +221 -0
  67. package/bld/library/shell.js.map +1 -0
  68. package/bld/library/sketch-curve-intersections.d.ts +13 -0
  69. package/bld/library/sketch-curve-intersections.d.ts.map +1 -0
  70. package/bld/library/sketch-curve-intersections.js +117 -0
  71. package/bld/library/sketch-curve-intersections.js.map +1 -0
  72. package/bld/library/sketch-curves.d.ts +31 -0
  73. package/bld/library/sketch-curves.d.ts.map +1 -0
  74. package/bld/library/sketch-curves.js +89 -0
  75. package/bld/library/sketch-curves.js.map +1 -0
  76. package/bld/library/sketch-drag-rules.d.ts +35 -0
  77. package/bld/library/sketch-drag-rules.d.ts.map +1 -0
  78. package/bld/library/sketch-drag-rules.js +522 -0
  79. package/bld/library/sketch-drag-rules.js.map +1 -0
  80. package/bld/library/sketch-face.d.ts +4 -0
  81. package/bld/library/sketch-face.d.ts.map +1 -0
  82. package/bld/library/sketch-face.js +72 -0
  83. package/bld/library/sketch-face.js.map +1 -0
  84. package/bld/library/sketch-incidence.d.ts +26 -0
  85. package/bld/library/sketch-incidence.d.ts.map +1 -0
  86. package/bld/library/sketch-incidence.js +114 -0
  87. package/bld/library/sketch-incidence.js.map +1 -0
  88. package/bld/library/sketch-precision.d.ts +19 -0
  89. package/bld/library/sketch-precision.d.ts.map +1 -0
  90. package/bld/library/sketch-precision.js +55 -0
  91. package/bld/library/sketch-precision.js.map +1 -0
  92. package/bld/library/sketch-regions.d.ts +12 -0
  93. package/bld/library/sketch-regions.d.ts.map +1 -0
  94. package/bld/library/sketch-regions.js +193 -0
  95. package/bld/library/sketch-regions.js.map +1 -0
  96. package/bld/library/sketch-solver.d.ts +97 -0
  97. package/bld/library/sketch-solver.d.ts.map +1 -0
  98. package/bld/library/sketch-solver.js +578 -0
  99. package/bld/library/sketch-solver.js.map +1 -0
  100. package/bld/library/sketch.d.ts +142 -0
  101. package/bld/library/sketch.d.ts.map +1 -0
  102. package/bld/library/sketch.js +650 -0
  103. package/bld/library/sketch.js.map +1 -0
  104. package/bld/library/spatial.d.ts +28 -0
  105. package/bld/library/spatial.d.ts.map +1 -0
  106. package/bld/library/spatial.js +195 -0
  107. package/bld/library/spatial.js.map +1 -0
  108. package/bld/library/three.d.ts +3 -0
  109. package/bld/library/three.d.ts.map +1 -0
  110. package/bld/library/three.js +3 -0
  111. package/bld/library/three.js.map +1 -0
  112. package/bld/library/topology-id.d.ts +24 -0
  113. package/bld/library/topology-id.d.ts.map +1 -0
  114. package/bld/library/topology-id.js +67 -0
  115. package/bld/library/topology-id.js.map +1 -0
  116. package/bld/library/topology-inspection.d.ts +64 -0
  117. package/bld/library/topology-inspection.d.ts.map +1 -0
  118. package/bld/library/topology-inspection.js +267 -0
  119. package/bld/library/topology-inspection.js.map +1 -0
  120. package/bld/library/topology.d.ts +94 -0
  121. package/bld/library/topology.d.ts.map +1 -0
  122. package/bld/library/topology.js +455 -0
  123. package/bld/library/topology.js.map +1 -0
  124. package/bld/node/index.d.ts +2 -0
  125. package/bld/node/index.d.ts.map +1 -0
  126. package/bld/node/index.js +14 -0
  127. package/bld/node/index.js.map +1 -0
  128. package/bld/node/replicad.d.ts +3 -0
  129. package/bld/node/replicad.d.ts.map +1 -0
  130. package/bld/node/replicad.js +3 -0
  131. package/bld/node/replicad.js.map +1 -0
  132. package/bld/tooling/index.d.ts +26 -0
  133. package/bld/tooling/index.d.ts.map +1 -0
  134. package/bld/tooling/index.js +19 -0
  135. package/bld/tooling/index.js.map +1 -0
  136. package/package.json +53 -0
  137. package/src/library/alignment-geometry.ts +431 -0
  138. package/src/library/bound-solver.ts +410 -0
  139. package/src/library/extrude.ts +38 -0
  140. package/src/library/index.ts +88 -0
  141. package/src/library/kernel-artifact-codec.ts +166 -0
  142. package/src/library/kernel-cache.ts +337 -0
  143. package/src/library/kernel-shapes.ts +135 -0
  144. package/src/library/loft.ts +196 -0
  145. package/src/library/material.ts +228 -0
  146. package/src/library/model-color.ts +47 -0
  147. package/src/library/open-cascade-error.ts +26 -0
  148. package/src/library/relation-solver.ts +479 -0
  149. package/src/library/replicad.ts +40 -0
  150. package/src/library/retained-memory.ts +23 -0
  151. package/src/library/runtime.ts +5146 -0
  152. package/src/library/shell.ts +281 -0
  153. package/src/library/sketch-curve-intersections.ts +154 -0
  154. package/src/library/sketch-curves.ts +161 -0
  155. package/src/library/sketch-drag-rules.ts +767 -0
  156. package/src/library/sketch-face.ts +89 -0
  157. package/src/library/sketch-incidence.ts +204 -0
  158. package/src/library/sketch-precision.ts +81 -0
  159. package/src/library/sketch-regions.ts +271 -0
  160. package/src/library/sketch-solver.ts +973 -0
  161. package/src/library/sketch.ts +977 -0
  162. package/src/library/spatial.ts +254 -0
  163. package/src/library/three.ts +2 -0
  164. package/src/library/topology-id.ts +81 -0
  165. package/src/library/topology-inspection.ts +368 -0
  166. package/src/library/topology.ts +776 -0
  167. package/src/library/tsconfig.json +11 -0
  168. package/src/node/index.ts +20 -0
  169. package/src/node/replicad.ts +3 -0
  170. package/src/node/tsconfig.json +16 -0
  171. package/src/tooling/index.ts +160 -0
  172. package/src/tooling/tsconfig.json +15 -0
@@ -0,0 +1,479 @@
1
+ import {
2
+ axisRotation,
3
+ solveBodies as solveBoundBodies,
4
+ type BodyRelation as BoundRelation,
5
+ type BodyRotation,
6
+ } from './bound-solver.js';
7
+ import {
8
+ alignmentResidual,
9
+ cross,
10
+ dot,
11
+ nearestPoint,
12
+ orderedGeometry,
13
+ perpendicular,
14
+ reject,
15
+ scale,
16
+ subtract,
17
+ transformGeometry,
18
+ unit,
19
+ validateAlignment,
20
+ type AlignmentGeometry,
21
+ } from './alignment-geometry.js';
22
+ import {
23
+ addVectors,
24
+ composeTransforms,
25
+ identityRigidTransform,
26
+ invertTransform,
27
+ origin,
28
+ rotateVector,
29
+ rotation,
30
+ translation,
31
+ type Quaternion,
32
+ type RigidTransform,
33
+ type Vec3,
34
+ } from './spatial.js';
35
+
36
+ export {axisRotation, type BodyRotation};
37
+ type AlignEndpoint = Readonly<{
38
+ body: number;
39
+ geometry: AlignmentGeometry;
40
+ transform: RigidTransform;
41
+ }>;
42
+ type AlignRelation = Readonly<{
43
+ kind: 'align';
44
+ id: string;
45
+ source: AlignEndpoint;
46
+ target: AlignEndpoint;
47
+ offset: Vec3 | undefined;
48
+ rotations: readonly BodyRotation[];
49
+ }>;
50
+ type Relation = (BoundRelation & {kind: 'on'}) | AlignRelation;
51
+ export type Body = Readonly<{name: string; relations: readonly Relation[]}>;
52
+
53
+ /** Authored actions apply after satisfying the relation, about self or an external axis. */
54
+ export function beforeRelation(
55
+ pose: RigidTransform,
56
+ relation: Pick<Relation, 'kind' | 'rotations' | 'offset' | 'target'>,
57
+ poses: readonly RigidTransform[],
58
+ owner: number,
59
+ ): RigidTransform {
60
+ for (const action of [...relation.rotations].reverse())
61
+ pose =
62
+ 'local' in action
63
+ ? composeTransforms(pose, invertTransform(action.local))
64
+ : composeTransforms(
65
+ axisRotation(
66
+ composeTransforms(poses[action.body], action.axis),
67
+ -action.angle,
68
+ ),
69
+ pose,
70
+ );
71
+ if (relation.kind === 'align' && relation.offset) {
72
+ const target =
73
+ relation.target.body === owner ? pose : poses[relation.target.body];
74
+ const frame = composeTransforms(target, relation.target.transform);
75
+ pose = {
76
+ ...pose,
77
+ position: subtract(
78
+ pose.position,
79
+ rotateVector(relation.offset, frame.quaternion),
80
+ ),
81
+ };
82
+ }
83
+ return pose;
84
+ }
85
+
86
+ function afterRelation(
87
+ pose: RigidTransform,
88
+ relation: Relation,
89
+ poses: readonly RigidTransform[],
90
+ owner: number,
91
+ ): RigidTransform {
92
+ if (relation.kind === 'align' && relation.offset) {
93
+ const target =
94
+ relation.target.body === owner ? pose : poses[relation.target.body];
95
+ pose = {
96
+ ...pose,
97
+ position: addVectors(
98
+ pose.position,
99
+ rotateVector(
100
+ relation.offset,
101
+ composeTransforms(target, relation.target.transform).quaternion,
102
+ ),
103
+ ),
104
+ };
105
+ }
106
+ for (const action of relation.rotations)
107
+ pose =
108
+ 'local' in action
109
+ ? composeTransforms(pose, action.local)
110
+ : composeTransforms(
111
+ axisRotation(
112
+ composeTransforms(poses[action.body], action.axis),
113
+ action.angle,
114
+ ),
115
+ pose,
116
+ );
117
+ return pose;
118
+ }
119
+
120
+ function directionRotation(from: Vec3, to: Vec3): Quaternion {
121
+ const product = dot(from, to);
122
+ if (product < -1 + 1e-10) return [...perpendicular(from), 0];
123
+ const vector = cross(from, to),
124
+ w = 1 + product,
125
+ length = Math.hypot(...vector, w);
126
+ return [...scale(vector, 1 / length), w / length];
127
+ }
128
+
129
+ /** A geometric seed resolves antipodal stationary points without constraining free modes. */
130
+ function alignmentSeed(
131
+ source: AlignmentGeometry,
132
+ target: AlignmentGeometry,
133
+ ): RigidTransform {
134
+ if (alignmentResidual(source, target).every(value => Math.abs(value) < 1e-8))
135
+ return identityRigidTransform;
136
+ let q: Quaternion = [0, 0, 0, 1];
137
+ const direction = (g: AlignmentGeometry) =>
138
+ 'normal' in g ? g.normal : 'direction' in g ? g.direction : undefined;
139
+ const from = direction(source),
140
+ to = direction(target);
141
+ if (from && to) {
142
+ const [a, b] = orderedGeometry(source, target);
143
+ if (a.kind === 'line' && b.kind === 'plane') {
144
+ const projected = reject(a.direction, b.normal);
145
+ const tangent =
146
+ Math.hypot(...projected) > 1e-10
147
+ ? unit(projected)
148
+ : perpendicular(b.normal);
149
+ const projectedNormal = reject(b.normal, a.direction);
150
+ q =
151
+ source === a
152
+ ? directionRotation(a.direction, tangent)
153
+ : directionRotation(
154
+ b.normal,
155
+ Math.hypot(...projectedNormal) > 1e-10
156
+ ? unit(projectedNormal)
157
+ : perpendicular(a.direction),
158
+ );
159
+ } else {
160
+ const sameDirection =
161
+ source.kind === target.kind &&
162
+ ['line', 'circle', 'ellipse', 'plane'].includes(source.kind);
163
+ q = directionRotation(
164
+ from,
165
+ !sameDirection && dot(from, to) < 0 ? scale(to, -1) : to,
166
+ );
167
+ }
168
+ }
169
+ if (source.kind === 'ellipse' && target.kind === 'ellipse') {
170
+ const major = rotateVector(source.major, q);
171
+ q = composeTransforms(
172
+ rotation(
173
+ directionRotation(
174
+ major,
175
+ dot(major, target.major) < 0 ? scale(target.major, -1) : target.major,
176
+ ),
177
+ ),
178
+ rotation(q),
179
+ ).quaternion;
180
+ } else if (source.kind === 'ellipse' && target.kind === 'cylinder') {
181
+ const major = rotateVector(source.major, q),
182
+ normal = rotateVector(source.normal, q),
183
+ ratio = source.minorRadius / source.radius;
184
+ q = composeTransforms(
185
+ rotation(
186
+ directionRotation(
187
+ normal,
188
+ addVectors(
189
+ scale(normal, ratio),
190
+ scale(major, Math.sqrt(1 - ratio * ratio)),
191
+ ),
192
+ ),
193
+ ),
194
+ rotation(q),
195
+ ).quaternion;
196
+ } else if (source.kind === 'cylinder' && target.kind === 'ellipse') {
197
+ const ratio = target.minorRadius / target.radius;
198
+ const axis = addVectors(
199
+ scale(target.normal, ratio),
200
+ scale(target.major, Math.sqrt(1 - ratio * ratio)),
201
+ );
202
+ q = directionRotation(
203
+ source.direction,
204
+ dot(source.direction, axis) < 0 ? scale(axis, -1) : axis,
205
+ );
206
+ }
207
+ const pose = rotation(q);
208
+ const moved = transformGeometry(source, pose);
209
+ let shift: Vec3;
210
+ if (source.kind === 'point')
211
+ shift = subtract(nearestPoint(source.point, target), source.point);
212
+ else if (target.kind === 'point')
213
+ shift = subtract(target.point, nearestPoint(target.point, moved));
214
+ else if (target.kind === 'plane')
215
+ shift = subtract(nearestPoint(moved.point, target), moved.point);
216
+ else if (source.kind === 'plane')
217
+ shift = subtract(target.point, nearestPoint(target.point, moved));
218
+ else if (source.kind === 'line' && target.kind === 'cylinder')
219
+ shift = subtract(nearestPoint(moved.point, target), moved.point);
220
+ else if (target.kind === 'line' && source.kind === 'cylinder')
221
+ shift = subtract(target.point, nearestPoint(target.point, moved));
222
+ else if (moved.kind === 'circle' && target.kind === 'sphere')
223
+ shift = subtract(
224
+ addVectors(
225
+ target.point,
226
+ scale(
227
+ moved.normal,
228
+ Math.sqrt(Math.max(0, target.radius ** 2 - moved.radius ** 2)) *
229
+ (dot(subtract(moved.point, target.point), moved.normal) < 0
230
+ ? -1
231
+ : 1),
232
+ ),
233
+ ),
234
+ moved.point,
235
+ );
236
+ else if (moved.kind === 'sphere' && target.kind === 'circle')
237
+ shift = subtract(
238
+ addVectors(
239
+ target.point,
240
+ scale(
241
+ target.normal,
242
+ Math.sqrt(Math.max(0, moved.radius ** 2 - target.radius ** 2)) *
243
+ (dot(subtract(moved.point, target.point), target.normal) < 0
244
+ ? -1
245
+ : 1),
246
+ ),
247
+ ),
248
+ moved.point,
249
+ );
250
+ else if ('direction' in target)
251
+ shift = reject(subtract(target.point, moved.point), target.direction);
252
+ else if ('direction' in moved)
253
+ shift = reject(subtract(target.point, moved.point), moved.direction);
254
+ else shift = subtract(target.point, moved.point);
255
+ return {...pose, position: addVectors(pose.position, shift)};
256
+ }
257
+
258
+ /** Exact linear specialization for bound-only assemblies; geometric relations solve joint rigid poses. */
259
+ export function solveBodies(
260
+ bodies: readonly Body[],
261
+ ): readonly RigidTransform[] {
262
+ if (bodies.every(body => body.relations.every(r => r.kind === 'on')))
263
+ return solveBoundBodies(
264
+ bodies as readonly {
265
+ name: string;
266
+ relations: readonly (BoundRelation & {kind: 'on'})[];
267
+ }[],
268
+ );
269
+ for (const body of bodies)
270
+ for (const relation of body.relations)
271
+ if (relation.kind === 'align')
272
+ validateAlignment(relation.source.geometry, relation.target.geometry);
273
+ const active = bodies.flatMap((body, i) =>
274
+ body.relations.length ? [i] : [],
275
+ );
276
+ const flexible = bodies.map(body =>
277
+ body.relations.some(r => r.kind === 'align'),
278
+ );
279
+ // Stable ordering and deduplication keep repeated conditions from biasing seeds.
280
+ const relations = bodies.flatMap((body, owner) =>
281
+ body.relations.map(relation => ({owner, relation})),
282
+ );
283
+ const key = ({owner, relation}: (typeof relations)[number]) =>
284
+ JSON.stringify({owner, relation: {...relation, id: undefined}});
285
+ const unique = [
286
+ ...new Map(relations.map(entry => [key(entry), entry])).entries(),
287
+ ]
288
+ .sort(([a], [b]) => a.localeCompare(b))
289
+ .map(([, entry]) => entry);
290
+ let poses = bodies.map(() => identityRigidTransform);
291
+ for (const owner of active) {
292
+ const authored =
293
+ unique.find(
294
+ entry => entry.owner === owner && entry.relation.rotations.length,
295
+ )?.relation ??
296
+ unique.find(
297
+ entry =>
298
+ entry.owner === owner &&
299
+ entry.relation.kind === 'align' &&
300
+ entry.relation.offset,
301
+ )?.relation;
302
+ if (authored)
303
+ poses[owner] = afterRelation(
304
+ identityRigidTransform,
305
+ authored,
306
+ poses,
307
+ owner,
308
+ );
309
+ }
310
+ for (const {owner, relation} of unique) {
311
+ if (relation.kind !== 'align') continue;
312
+ const sourceSelf = relation.source.body === owner;
313
+ if (relation.target.body === relation.source.body) continue;
314
+ const baseline = beforeRelation(poses[owner], relation, poses, owner);
315
+ const source = transformGeometry(
316
+ relation.source.geometry,
317
+ relation.source.body === owner ? baseline : poses[relation.source.body],
318
+ ),
319
+ target = transformGeometry(
320
+ relation.target.geometry,
321
+ relation.target.body === owner ? baseline : poses[relation.target.body],
322
+ );
323
+ const seed = sourceSelf
324
+ ? alignmentSeed(source, target)
325
+ : alignmentSeed(target, source);
326
+ poses[owner] = afterRelation(
327
+ composeTransforms(seed, baseline),
328
+ relation,
329
+ poses,
330
+ owner,
331
+ );
332
+ }
333
+ const geometryScale = Math.max(
334
+ 1,
335
+ ...unique.flatMap(({relation: r}) =>
336
+ r.kind === 'align'
337
+ ? [r.source.geometry, r.target.geometry].map(g =>
338
+ 'radius' in g ? g.radius : 1,
339
+ )
340
+ : [],
341
+ ),
342
+ );
343
+ const variables = active.flatMap(body =>
344
+ Array.from({length: 6}, (_, axis) => ({body, axis})),
345
+ );
346
+ const residual = (candidate: readonly RigidTransform[]): number[] => [
347
+ ...unique.flatMap(({owner, relation: r}) => {
348
+ const baseline = beforeRelation(candidate[owner], r, candidate, owner);
349
+ const source =
350
+ r.source.body === owner ? baseline : candidate[r.source.body],
351
+ target = r.target.body === owner ? baseline : candidate[r.target.body];
352
+ if (r.kind === 'align')
353
+ return alignmentResidual(
354
+ transformGeometry(r.source.geometry, source),
355
+ transformGeometry(r.target.geometry, target),
356
+ );
357
+ const frame = composeTransforms(target, r.target.transform);
358
+ const relative = composeTransforms(
359
+ invertTransform(rotation(frame.quaternion)),
360
+ rotation(source.quaternion),
361
+ );
362
+ const bounds = r.source.bounds(relative.quaternion);
363
+ const center: Vec3 = [
364
+ (bounds[0][0] + bounds[1][0]) / 2,
365
+ bounds[r.target.facing === 1 ? 0 : 1][1],
366
+ (bounds[0][2] + bounds[1][2]) / 2,
367
+ ];
368
+ const a = addVectors(
369
+ source.position,
370
+ rotateVector(center, frame.quaternion),
371
+ );
372
+ const b = composeTransforms(
373
+ frame,
374
+ translation(r.offset ?? origin),
375
+ ).position;
376
+ const delta = rotateVector(
377
+ subtract(a, b),
378
+ invertTransform(frame).quaternion,
379
+ );
380
+ return r.offset ? [...delta] : [delta[1]];
381
+ }),
382
+ ...active.flatMap(owner => {
383
+ if (flexible[owner]) return [];
384
+ const rotations = bodies[owner].relations.filter(r => r.rotations.length);
385
+ return rotations.length
386
+ ? rotations.flatMap(r =>
387
+ beforeRelation(
388
+ candidate[owner],
389
+ r,
390
+ candidate,
391
+ owner,
392
+ ).quaternion.slice(0, 3),
393
+ )
394
+ : candidate[owner].quaternion.slice(0, 3);
395
+ }),
396
+ ];
397
+ const perturb = (
398
+ base: readonly RigidTransform[],
399
+ steps: readonly number[],
400
+ ) => {
401
+ const change = bodies.map(() => [0, 0, 0, 0, 0, 0]);
402
+ variables.forEach(({body, axis}, i) => {
403
+ change[body][axis] = steps[i];
404
+ });
405
+ return base.map((pose, i) => {
406
+ const d = change[i],
407
+ v: Vec3 = [d[3], d[4], d[5]],
408
+ angle = Math.hypot(...v);
409
+ const q: Quaternion =
410
+ angle < 1e-14
411
+ ? [0, 0, 0, 1]
412
+ : [...scale(v, Math.sin(angle / 2) / angle), Math.cos(angle / 2)];
413
+ return {
414
+ position: addVectors(
415
+ pose.position,
416
+ scale([d[0], d[1], d[2]], geometryScale),
417
+ ),
418
+ quaternion: composeTransforms(rotation(q), rotation(pose.quaternion))
419
+ .quaternion,
420
+ };
421
+ });
422
+ };
423
+ let values = residual(poses),
424
+ damping = 1e-5;
425
+ const norm = (values: readonly number[]) =>
426
+ values.reduce((sum, x) => sum + x * x, 0);
427
+ for (let iteration = 0; iteration < 160; iteration++) {
428
+ if (values.every(v => Math.abs(v) < 1e-8)) return poses;
429
+ const h = 1e-6;
430
+ const columns = variables.map((_, i) => {
431
+ const shifted = residual(
432
+ perturb(
433
+ poses,
434
+ variables.map((_, j) => (i === j ? h : 0)),
435
+ ),
436
+ );
437
+ return values.map((v, j) => (shifted[j] - v) / h);
438
+ });
439
+ const multiply = (a: readonly number[], b: readonly number[]) =>
440
+ a.reduce((sum, x, i) => sum + x * b[i], 0);
441
+ const matrix = columns.map((a, i) => [
442
+ ...columns.map((b, j) => multiply(a, b) + (i === j ? damping : 0)),
443
+ -multiply(a, values),
444
+ ]);
445
+ const step = eliminate(matrix);
446
+ const candidate = perturb(poses, step),
447
+ next = residual(candidate);
448
+ if (norm(next) < norm(values)) {
449
+ poses = candidate;
450
+ values = next;
451
+ damping = Math.max(1e-12, damping / 3);
452
+ } else damping = Math.min(1e12, damping * 10);
453
+ }
454
+ throw new Error(
455
+ `align() solver did not converge (${unique.map(({relation: r}) => r.id).join(', ')}; this does not prove geometric incompatibility. Add positioning information or check conflicting relations.`,
456
+ );
457
+ }
458
+
459
+ /** Pivoted elimination of the positive definite damped normal equations. */
460
+ function eliminate(matrix: number[][]): number[] {
461
+ const n = matrix.length;
462
+ for (let i = 0; i < n; i++) {
463
+ let pivot = i;
464
+ for (let j = i + 1; j < n; j++)
465
+ if (Math.abs(matrix[j][i]) > Math.abs(matrix[pivot][i])) pivot = j;
466
+ [matrix[i], matrix[pivot]] = [matrix[pivot], matrix[i]];
467
+ for (let j = i + 1; j < n; j++) {
468
+ const factor = matrix[j][i] / matrix[i][i];
469
+ for (let k = i; k <= n; k++) matrix[j][k] -= factor * matrix[i][k];
470
+ }
471
+ }
472
+ const result = Array.from({length: n}, () => 0);
473
+ for (let i = n - 1; i >= 0; i--) {
474
+ let value = matrix[i][n];
475
+ for (let j = i + 1; j < n; j++) value -= matrix[i][j] * result[j];
476
+ result[i] = value / matrix[i][i];
477
+ }
478
+ return result;
479
+ }
@@ -0,0 +1,40 @@
1
+ import * as replicadModule from 'replicad';
2
+ import {modelFromReplicadSolid} from './runtime.js';
3
+ import {castOwnedShape} from './kernel-shapes.js';
4
+ import type {SolidModel} from './index.js';
5
+ import type {Shape3D} from 'replicad';
6
+
7
+ export type * from 'replicad';
8
+
9
+ export type Replicad = Omit<typeof replicadModule, 'getOC' | 'setOC'>;
10
+
11
+ const {
12
+ getOC: _getOC,
13
+ setOC: _setOC,
14
+ deserializeShape: _deserializeShape,
15
+ ...authorReplicad
16
+ } = replicadModule;
17
+
18
+ /**
19
+ * The Replicad API bound to the OpenCascade runtime owned by code3d. Kernel
20
+ * installation remains a tooling responsibility and is intentionally omitted.
21
+ */
22
+ export const replicad: Replicad = Object.freeze({
23
+ ...authorReplicad,
24
+ deserializeShape(data: string) {
25
+ // The native reader owns a raw shape; casting acquires another handle.
26
+ return castOwnedShape(replicadModule.getOC().BRepToolsWrapper.Read(data));
27
+ },
28
+ });
29
+
30
+ /**
31
+ * Defines a synchronous model constructor with the builder's parameters.
32
+ * The builder runs on every call and transfers ownership of its returned solid
33
+ * to code3d. Intermediate shapes remain the builder's responsibility.
34
+ */
35
+ export function definePrimitive<
36
+ Builder extends (...arguments_: never[]) => Shape3D,
37
+ >(build: Builder): (...arguments_: Parameters<Builder>) => SolidModel {
38
+ return (...arguments_: Parameters<Builder>) =>
39
+ modelFromReplicadSolid(build(...arguments_));
40
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A conservative weight for retained plain JavaScript data. Typed-array backing
3
+ * storage is counted once within a value; object/string overhead is estimated.
4
+ * Native handles are excluded by callers and measured by the kernel allocator.
5
+ */
6
+ export function estimateRetainedBytes(value: unknown): number {
7
+ const seen = new Set<object>();
8
+ function visit(value: unknown): number {
9
+ if (typeof value === 'string') return 16 + value.length * 2;
10
+ if (value === null || typeof value !== 'object') return 8;
11
+ if (seen.has(value)) return 0;
12
+ seen.add(value);
13
+ if (ArrayBuffer.isView(value)) return 64 + visit(value.buffer);
14
+ if (value instanceof ArrayBuffer) return 32 + value.byteLength;
15
+ const values = Object.values(value);
16
+ return (
17
+ 32 +
18
+ values.length * 8 +
19
+ values.reduce<number>((sum, item) => sum + visit(item), 0)
20
+ );
21
+ }
22
+ return visit(value);
23
+ }