@code3d/core 0.0.1-alpha.0 → 0.0.1-alpha.11
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.
- package/README.md +43 -807
- package/THIRD_PARTY.md +40 -0
- package/bld/THIRD_PARTY_NOTICES.txt +182 -0
- package/bld/chunks/chunk-7FIFPADJ.js +1 -0
- package/bld/chunks/chunk-7FIFPADJ.js.map +7 -0
- package/bld/chunks/chunk-CL3E2DE4.js +19 -0
- package/bld/chunks/chunk-CL3E2DE4.js.map +7 -0
- package/bld/chunks/chunk-HZRQUHM2.js +30 -0
- package/bld/chunks/chunk-HZRQUHM2.js.map +7 -0
- package/bld/chunks/chunk-JBVIAMJ6.js +26474 -0
- package/bld/chunks/chunk-JBVIAMJ6.js.map +7 -0
- package/bld/chunks/chunk-QK6ZET47.js +31 -0
- package/bld/chunks/chunk-QK6ZET47.js.map +7 -0
- package/bld/library/bound-solver.d.ts +29 -5
- package/bld/library/bound-solver.d.ts.map +1 -1
- package/bld/library/cached.d.ts +35 -0
- package/bld/library/cached.d.ts.map +1 -0
- package/bld/library/font.d.ts +39 -0
- package/bld/library/font.d.ts.map +1 -0
- package/bld/library/google-font.d.ts +14 -0
- package/bld/library/google-font.d.ts.map +1 -0
- package/bld/library/index.d.ts +10 -2
- package/bld/library/index.d.ts.map +1 -1
- package/bld/library/index.js +94 -3
- package/bld/library/index.js.map +7 -1
- package/bld/library/inspect.d.ts +100 -0
- package/bld/library/inspect.d.ts.map +1 -0
- package/bld/library/kernel-artifact-codec.d.ts.map +1 -1
- package/bld/library/kernel-cache.d.ts +28 -7
- package/bld/library/kernel-cache.d.ts.map +1 -1
- package/bld/library/loft.d.ts.map +1 -1
- package/bld/library/material.d.ts +1 -1
- package/bld/library/material.d.ts.map +1 -1
- package/bld/library/open-cascade.d.ts +4 -0
- package/bld/library/open-cascade.d.ts.map +1 -0
- package/bld/library/relation-solver.d.ts +19 -8
- package/bld/library/relation-solver.d.ts.map +1 -1
- package/bld/library/replicad.d.ts +3 -2
- package/bld/library/replicad.d.ts.map +1 -1
- package/bld/library/replicad.js +10 -24
- package/bld/library/replicad.js.map +7 -1
- package/bld/library/retained-memory.d.ts.map +1 -1
- package/bld/library/runtime.d.ts +518 -155
- package/bld/library/runtime.d.ts.map +1 -1
- package/bld/library/sketch-solver.d.ts +1 -1
- package/bld/library/sketch-solver.d.ts.map +1 -1
- package/bld/library/sketch.d.ts +21 -1
- package/bld/library/sketch.d.ts.map +1 -1
- package/bld/library/text.d.ts +53 -0
- package/bld/library/text.d.ts.map +1 -0
- package/bld/library/three.js +3 -3
- package/bld/library/three.js.map +7 -1
- package/bld/library/topology.d.ts +6 -2
- package/bld/library/topology.d.ts.map +1 -1
- package/bld/node/index.d.ts.map +1 -1
- package/bld/node/index.js +96 -14
- package/bld/node/index.js.map +7 -1
- package/bld/node/replicad.js +13 -3
- package/bld/node/replicad.js.map +7 -1
- package/bld/tooling/index.d.ts +18 -15
- package/bld/tooling/index.d.ts.map +1 -1
- package/bld/tooling/index.js +165 -19
- package/bld/tooling/index.js.map +7 -1
- package/docs/api.md +764 -0
- package/docs/custom-primitives.mdx +58 -0
- package/docs/local-coordinates.md +187 -0
- package/docs/origins-and-rotation.mdx +105 -0
- package/docs/relations.mdx +450 -0
- package/docs/runtime.md +297 -0
- package/docs/shells.mdx +70 -0
- package/docs/sketches.md +83 -0
- package/docs/text.md +23 -0
- package/docs/topology.md +152 -0
- package/docs/values.md +119 -0
- package/package.json +20 -7
- package/src/library/bound-solver.ts +103 -73
- package/src/library/cached.ts +153 -0
- package/src/library/font.ts +186 -0
- package/src/library/google-font.ts +97 -0
- package/src/library/index.ts +53 -5
- package/src/library/inspect.ts +178 -0
- package/src/library/kernel-artifact-codec.ts +129 -20
- package/src/library/kernel-cache.ts +175 -39
- package/src/library/loft.ts +9 -1
- package/src/library/material.ts +3 -4
- package/src/library/open-cascade.ts +13 -0
- package/src/library/relation-solver.ts +230 -113
- package/src/library/replicad.ts +5 -5
- package/src/library/retained-memory.ts +6 -1
- package/src/library/runtime.ts +3769 -1178
- package/src/library/shell.ts +3 -3
- package/src/library/sketch-solver.ts +3 -1
- package/src/library/sketch.ts +112 -13
- package/src/library/text.ts +477 -0
- package/src/library/topology.ts +61 -17
- package/src/library/tsconfig.json +2 -1
- package/src/node/index.ts +14 -3
- package/src/node/tsconfig.json +2 -1
- package/src/tooling/index.ts +64 -51
- package/src/tooling/tsconfig.json +2 -1
- package/bld/library/alignment-geometry.js +0 -322
- package/bld/library/alignment-geometry.js.map +0 -1
- package/bld/library/bound-solver.js +0 -252
- package/bld/library/bound-solver.js.map +0 -1
- package/bld/library/extrude.js +0 -28
- package/bld/library/extrude.js.map +0 -1
- package/bld/library/kernel-artifact-codec.js +0 -148
- package/bld/library/kernel-artifact-codec.js.map +0 -1
- package/bld/library/kernel-cache.js +0 -245
- package/bld/library/kernel-cache.js.map +0 -1
- package/bld/library/kernel-shapes.js +0 -108
- package/bld/library/kernel-shapes.js.map +0 -1
- package/bld/library/loft.js +0 -161
- package/bld/library/loft.js.map +0 -1
- package/bld/library/material.js +0 -176
- package/bld/library/material.js.map +0 -1
- package/bld/library/model-color.js +0 -36
- package/bld/library/model-color.js.map +0 -1
- package/bld/library/open-cascade-error.js +0 -22
- package/bld/library/open-cascade-error.js.map +0 -1
- package/bld/library/relation-solver.js +0 -258
- package/bld/library/relation-solver.js.map +0 -1
- package/bld/library/retained-memory.js +0 -27
- package/bld/library/retained-memory.js.map +0 -1
- package/bld/library/runtime.js +0 -2954
- package/bld/library/runtime.js.map +0 -1
- package/bld/library/shell.js +0 -221
- package/bld/library/shell.js.map +0 -1
- package/bld/library/sketch-curve-intersections.js +0 -117
- package/bld/library/sketch-curve-intersections.js.map +0 -1
- package/bld/library/sketch-curves.js +0 -89
- package/bld/library/sketch-curves.js.map +0 -1
- package/bld/library/sketch-drag-rules.js +0 -522
- package/bld/library/sketch-drag-rules.js.map +0 -1
- package/bld/library/sketch-face.js +0 -72
- package/bld/library/sketch-face.js.map +0 -1
- package/bld/library/sketch-incidence.js +0 -114
- package/bld/library/sketch-incidence.js.map +0 -1
- package/bld/library/sketch-precision.js +0 -55
- package/bld/library/sketch-precision.js.map +0 -1
- package/bld/library/sketch-regions.js +0 -193
- package/bld/library/sketch-regions.js.map +0 -1
- package/bld/library/sketch-solver.js +0 -578
- package/bld/library/sketch-solver.js.map +0 -1
- package/bld/library/sketch.js +0 -650
- package/bld/library/sketch.js.map +0 -1
- package/bld/library/spatial.js +0 -195
- package/bld/library/spatial.js.map +0 -1
- package/bld/library/topology-id.js +0 -67
- package/bld/library/topology-id.js.map +0 -1
- package/bld/library/topology-inspection.js +0 -267
- package/bld/library/topology-inspection.js.map +0 -1
- package/bld/library/topology.js +0 -455
- package/bld/library/topology.js.map +0 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
axisRotation,
|
|
3
|
+
externalRotationFrame,
|
|
3
4
|
solveBodies as solveBoundBodies,
|
|
4
5
|
type BodyRelation as BoundRelation,
|
|
5
|
-
type
|
|
6
|
+
type BodyAction,
|
|
7
|
+
solveTranslations,
|
|
6
8
|
} from './bound-solver.js';
|
|
7
9
|
import {
|
|
8
10
|
alignmentResidual,
|
|
@@ -24,16 +26,14 @@ import {
|
|
|
24
26
|
composeTransforms,
|
|
25
27
|
identityRigidTransform,
|
|
26
28
|
invertTransform,
|
|
27
|
-
origin,
|
|
28
29
|
rotateVector,
|
|
29
30
|
rotation,
|
|
30
|
-
translation,
|
|
31
31
|
type Quaternion,
|
|
32
32
|
type RigidTransform,
|
|
33
33
|
type Vec3,
|
|
34
34
|
} from './spatial.js';
|
|
35
35
|
|
|
36
|
-
export {axisRotation, type
|
|
36
|
+
export {axisRotation, type BodyAction};
|
|
37
37
|
type AlignEndpoint = Readonly<{
|
|
38
38
|
body: number;
|
|
39
39
|
geometry: AlignmentGeometry;
|
|
@@ -44,76 +44,76 @@ type AlignRelation = Readonly<{
|
|
|
44
44
|
id: string;
|
|
45
45
|
source: AlignEndpoint;
|
|
46
46
|
target: AlignEndpoint;
|
|
47
|
-
offset: Vec3 | undefined;
|
|
48
|
-
rotations: readonly BodyRotation[];
|
|
49
47
|
}>;
|
|
50
|
-
type
|
|
51
|
-
|
|
48
|
+
type FrameRelation = Readonly<{
|
|
49
|
+
kind: 'frame';
|
|
50
|
+
id: string;
|
|
51
|
+
source: Omit<AlignEndpoint, 'geometry'>;
|
|
52
|
+
target: Omit<AlignEndpoint, 'geometry'>;
|
|
53
|
+
}>;
|
|
54
|
+
type Relation = (BoundRelation & {kind: 'on'}) | AlignRelation | FrameRelation;
|
|
55
|
+
export type Body = Readonly<{
|
|
56
|
+
name: string;
|
|
57
|
+
relations: readonly Relation[];
|
|
58
|
+
initial?: RigidTransform;
|
|
59
|
+
transformations?: readonly BodyAction[];
|
|
60
|
+
}>;
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
export function beforeRelation(
|
|
62
|
+
function applyExternalRotation(
|
|
55
63
|
pose: RigidTransform,
|
|
56
|
-
|
|
64
|
+
action: Exclude<BodyAction, {offset: Vec3} | {local: RigidTransform}>,
|
|
65
|
+
reference: RigidTransform,
|
|
66
|
+
inverse = false,
|
|
67
|
+
): RigidTransform {
|
|
68
|
+
const {center, quaternion} = externalRotationFrame(
|
|
69
|
+
action,
|
|
70
|
+
pose.quaternion,
|
|
71
|
+
reference.quaternion,
|
|
72
|
+
inverse,
|
|
73
|
+
);
|
|
74
|
+
const point = addVectors(reference.position, center);
|
|
75
|
+
return composeTransforms(
|
|
76
|
+
{quaternion, position: subtract(point, rotateVector(point, quaternion))},
|
|
77
|
+
pose,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Undo exactly the authored prefix, in reverse call order. */
|
|
82
|
+
export function beforeTransformations(
|
|
83
|
+
pose: RigidTransform,
|
|
84
|
+
relation: {
|
|
85
|
+
actions: readonly BodyAction[];
|
|
86
|
+
},
|
|
57
87
|
poses: readonly RigidTransform[],
|
|
58
|
-
owner: number,
|
|
59
88
|
): RigidTransform {
|
|
60
|
-
for (const action of [...relation.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
};
|
|
89
|
+
for (const action of [...relation.actions].reverse()) {
|
|
90
|
+
if ('offset' in action) {
|
|
91
|
+
pose = {...pose, position: subtract(pose.position, action.offset)};
|
|
92
|
+
} else
|
|
93
|
+
pose =
|
|
94
|
+
'local' in action
|
|
95
|
+
? composeTransforms(pose, invertTransform(action.local))
|
|
96
|
+
: applyExternalRotation(pose, action, poses[action.body], true);
|
|
82
97
|
}
|
|
83
98
|
return pose;
|
|
84
99
|
}
|
|
85
100
|
|
|
86
|
-
function
|
|
101
|
+
export function afterTransformations(
|
|
87
102
|
pose: RigidTransform,
|
|
88
|
-
relation:
|
|
103
|
+
relation: {
|
|
104
|
+
actions: readonly BodyAction[];
|
|
105
|
+
},
|
|
89
106
|
poses: readonly RigidTransform[],
|
|
90
|
-
owner: number,
|
|
91
107
|
): RigidTransform {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
relation.offset,
|
|
101
|
-
composeTransforms(target, relation.target.transform).quaternion,
|
|
102
|
-
),
|
|
103
|
-
),
|
|
104
|
-
};
|
|
108
|
+
for (const action of relation.actions) {
|
|
109
|
+
if ('offset' in action) {
|
|
110
|
+
pose = {...pose, position: addVectors(pose.position, action.offset)};
|
|
111
|
+
} else
|
|
112
|
+
pose =
|
|
113
|
+
'local' in action
|
|
114
|
+
? composeTransforms(pose, action.local)
|
|
115
|
+
: applyExternalRotation(pose, action, poses[action.body]);
|
|
105
116
|
}
|
|
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
117
|
return pose;
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -271,10 +271,35 @@ export function solveBodies(
|
|
|
271
271
|
if (relation.kind === 'align')
|
|
272
272
|
validateAlignment(relation.source.geometry, relation.target.geometry);
|
|
273
273
|
const active = bodies.flatMap((body, i) =>
|
|
274
|
-
body.relations.length ? [i] : [],
|
|
274
|
+
body.relations.length || body.transformations?.length ? [i] : [],
|
|
275
275
|
);
|
|
276
|
+
const unplace = (
|
|
277
|
+
pose: RigidTransform,
|
|
278
|
+
owner: number,
|
|
279
|
+
poses: readonly RigidTransform[],
|
|
280
|
+
) =>
|
|
281
|
+
beforeTransformations(
|
|
282
|
+
pose,
|
|
283
|
+
{actions: bodies[owner].transformations ?? []},
|
|
284
|
+
poses,
|
|
285
|
+
);
|
|
286
|
+
const place = (
|
|
287
|
+
pose: RigidTransform,
|
|
288
|
+
owner: number,
|
|
289
|
+
poses: readonly RigidTransform[],
|
|
290
|
+
) =>
|
|
291
|
+
afterTransformations(
|
|
292
|
+
pose,
|
|
293
|
+
{actions: bodies[owner].transformations ?? []},
|
|
294
|
+
poses,
|
|
295
|
+
);
|
|
296
|
+
const baselinePose = (
|
|
297
|
+
pose: RigidTransform,
|
|
298
|
+
poses: readonly RigidTransform[],
|
|
299
|
+
owner: number,
|
|
300
|
+
) => unplace(pose, owner, poses);
|
|
276
301
|
const flexible = bodies.map(body =>
|
|
277
|
-
body.relations.some(r => r.kind
|
|
302
|
+
body.relations.some(r => r.kind !== 'on'),
|
|
278
303
|
);
|
|
279
304
|
// Stable ordering and deduplication keep repeated conditions from biasing seeds.
|
|
280
305
|
const relations = bodies.flatMap((body, owner) =>
|
|
@@ -287,31 +312,28 @@ export function solveBodies(
|
|
|
287
312
|
]
|
|
288
313
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
289
314
|
.map(([, entry]) => entry);
|
|
290
|
-
let poses = bodies.map(
|
|
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
|
-
}
|
|
315
|
+
let poses = bodies.map(body => body.initial ?? identityRigidTransform);
|
|
316
|
+
for (const owner of active) poses[owner] = place(poses[owner], owner, poses);
|
|
310
317
|
for (const {owner, relation} of unique) {
|
|
311
|
-
if (relation.kind
|
|
318
|
+
if (relation.kind === 'on') continue;
|
|
312
319
|
const sourceSelf = relation.source.body === owner;
|
|
313
320
|
if (relation.target.body === relation.source.body) continue;
|
|
314
|
-
const baseline =
|
|
321
|
+
const baseline = baselinePose(poses[owner], poses, owner);
|
|
322
|
+
if (relation.kind === 'frame') {
|
|
323
|
+
const source = composeTransforms(
|
|
324
|
+
sourceSelf ? baseline : poses[relation.source.body],
|
|
325
|
+
relation.source.transform,
|
|
326
|
+
);
|
|
327
|
+
const target = composeTransforms(
|
|
328
|
+
relation.target.body === owner ? baseline : poses[relation.target.body],
|
|
329
|
+
relation.target.transform,
|
|
330
|
+
);
|
|
331
|
+
const seed = sourceSelf
|
|
332
|
+
? composeTransforms(target, invertTransform(source))
|
|
333
|
+
: composeTransforms(source, invertTransform(target));
|
|
334
|
+
poses[owner] = place(composeTransforms(seed, baseline), owner, poses);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
315
337
|
const source = transformGeometry(
|
|
316
338
|
relation.source.geometry,
|
|
317
339
|
relation.source.body === owner ? baseline : poses[relation.source.body],
|
|
@@ -323,12 +345,7 @@ export function solveBodies(
|
|
|
323
345
|
const seed = sourceSelf
|
|
324
346
|
? alignmentSeed(source, target)
|
|
325
347
|
: alignmentSeed(target, source);
|
|
326
|
-
poses[owner] =
|
|
327
|
-
composeTransforms(seed, baseline),
|
|
328
|
-
relation,
|
|
329
|
-
poses,
|
|
330
|
-
owner,
|
|
331
|
-
);
|
|
348
|
+
poses[owner] = place(composeTransforms(seed, baseline), owner, poses);
|
|
332
349
|
}
|
|
333
350
|
const geometryScale = Math.max(
|
|
334
351
|
1,
|
|
@@ -340,12 +357,25 @@ export function solveBodies(
|
|
|
340
357
|
: [],
|
|
341
358
|
),
|
|
342
359
|
);
|
|
343
|
-
const
|
|
344
|
-
|
|
360
|
+
const fullVariables = active.flatMap(body =>
|
|
361
|
+
// Bound-only bodies without authored rotations have exactly identity
|
|
362
|
+
// orientation. Do not numerically perturb those fixed axes: native bound
|
|
363
|
+
// queries can introduce noise that prevents convergence at the identity.
|
|
364
|
+
Array.from(
|
|
365
|
+
{
|
|
366
|
+
length:
|
|
367
|
+
flexible[body] ||
|
|
368
|
+
bodies[body].transformations?.some(action => !('offset' in action))
|
|
369
|
+
? 6
|
|
370
|
+
: 3,
|
|
371
|
+
},
|
|
372
|
+
(_, axis) => ({body, axis}),
|
|
373
|
+
),
|
|
345
374
|
);
|
|
375
|
+
let variables = fullVariables.filter(variable => variable.axis < 3);
|
|
346
376
|
const residual = (candidate: readonly RigidTransform[]): number[] => [
|
|
347
377
|
...unique.flatMap(({owner, relation: r}) => {
|
|
348
|
-
const baseline =
|
|
378
|
+
const baseline = baselinePose(candidate[owner], candidate, owner);
|
|
349
379
|
const source =
|
|
350
380
|
r.source.body === owner ? baseline : candidate[r.source.body],
|
|
351
381
|
target = r.target.body === owner ? baseline : candidate[r.target.body];
|
|
@@ -354,6 +384,18 @@ export function solveBodies(
|
|
|
354
384
|
transformGeometry(r.source.geometry, source),
|
|
355
385
|
transformGeometry(r.target.geometry, target),
|
|
356
386
|
);
|
|
387
|
+
if (r.kind === 'frame') {
|
|
388
|
+
const a = composeTransforms(source, r.source.transform);
|
|
389
|
+
const b = composeTransforms(target, r.target.transform);
|
|
390
|
+
const relative = composeTransforms(invertTransform(b), a);
|
|
391
|
+
// q and -q denote the same orientation. The local rotation error has
|
|
392
|
+
// three independent components; the exact seed also handles half turns.
|
|
393
|
+
const sign = relative.quaternion[3] < 0 ? -1 : 1;
|
|
394
|
+
return [
|
|
395
|
+
...subtract(a.position, b.position),
|
|
396
|
+
...relative.quaternion.slice(0, 3).map(value => 2 * sign * value),
|
|
397
|
+
];
|
|
398
|
+
}
|
|
357
399
|
const frame = composeTransforms(target, r.target.transform);
|
|
358
400
|
const relative = composeTransforms(
|
|
359
401
|
invertTransform(rotation(frame.quaternion)),
|
|
@@ -369,29 +411,28 @@ export function solveBodies(
|
|
|
369
411
|
source.position,
|
|
370
412
|
rotateVector(center, frame.quaternion),
|
|
371
413
|
);
|
|
372
|
-
const b =
|
|
373
|
-
frame,
|
|
374
|
-
translation(r.offset ?? origin),
|
|
375
|
-
).position;
|
|
414
|
+
const b = frame.position;
|
|
376
415
|
const delta = rotateVector(
|
|
377
416
|
subtract(a, b),
|
|
378
417
|
invertTransform(frame).quaternion,
|
|
379
418
|
);
|
|
380
|
-
return
|
|
419
|
+
return [delta[1]];
|
|
381
420
|
}),
|
|
382
421
|
...active.flatMap(owner => {
|
|
383
422
|
if (flexible[owner]) return [];
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
423
|
+
const initial = bodies[owner].initial ?? identityRigidTransform;
|
|
424
|
+
const unplaced = unplace(candidate[owner], owner, candidate);
|
|
425
|
+
const orientationResidual = (pose: RigidTransform) =>
|
|
426
|
+
composeTransforms(
|
|
427
|
+
invertTransform(rotation(initial.quaternion)),
|
|
428
|
+
rotation(pose.quaternion),
|
|
429
|
+
).quaternion.slice(0, 3);
|
|
430
|
+
return [
|
|
431
|
+
...(bodies[owner].relations.length
|
|
432
|
+
? []
|
|
433
|
+
: subtract(unplaced.position, initial.position)),
|
|
434
|
+
...orientationResidual(unplaced),
|
|
435
|
+
];
|
|
395
436
|
}),
|
|
396
437
|
];
|
|
397
438
|
const perturb = (
|
|
@@ -420,12 +461,80 @@ export function solveBodies(
|
|
|
420
461
|
};
|
|
421
462
|
});
|
|
422
463
|
};
|
|
464
|
+
// Restore bound-only bodies' free translations without breaking alignments to
|
|
465
|
+
// dependent bodies. Every active translation participates in the nullspace;
|
|
466
|
+
// only bound-only bodies contribute preferences for their incoming pose.
|
|
467
|
+
const settleBoundTranslations = (): readonly RigidTransform[] => {
|
|
468
|
+
if (!active.some(owner => !flexible[owner])) return poses;
|
|
469
|
+
const free = active.flatMap(body => [0, 1, 2].map(axis => ({body, axis})));
|
|
470
|
+
if (!free.length) return poses;
|
|
471
|
+
const shift = (steps: readonly number[]) => {
|
|
472
|
+
const result = poses.map(pose => ({
|
|
473
|
+
...pose,
|
|
474
|
+
position: [...pose.position] as [number, number, number],
|
|
475
|
+
}));
|
|
476
|
+
free.forEach(({body, axis}, i) => {
|
|
477
|
+
result[body].position[axis] += steps[i];
|
|
478
|
+
});
|
|
479
|
+
return result;
|
|
480
|
+
};
|
|
481
|
+
const preferences = (candidate: readonly RigidTransform[]) =>
|
|
482
|
+
unique
|
|
483
|
+
.filter(({owner}) => !flexible[owner])
|
|
484
|
+
.flatMap(({owner}) =>
|
|
485
|
+
subtract(
|
|
486
|
+
baselinePose(candidate[owner], candidate, owner).position,
|
|
487
|
+
bodies[owner].initial?.position ?? [0, 0, 0],
|
|
488
|
+
),
|
|
489
|
+
);
|
|
490
|
+
const h = 1e-5;
|
|
491
|
+
const initial = residual(poses),
|
|
492
|
+
preference = preferences(poses);
|
|
493
|
+
const columns = free.map((_, i) => {
|
|
494
|
+
const plus = shift(free.map((_, j) => (i === j ? h : 0)));
|
|
495
|
+
const minus = shift(free.map((_, j) => (i === j ? -h : 0)));
|
|
496
|
+
const a = residual(plus),
|
|
497
|
+
b = residual(minus),
|
|
498
|
+
p = preferences(plus),
|
|
499
|
+
q = preferences(minus);
|
|
500
|
+
return {
|
|
501
|
+
constraints: a.map((v, j) => (v - b[j]) / (2 * h)),
|
|
502
|
+
preferences: p.map((v, j) => (v - q[j]) / (2 * h)),
|
|
503
|
+
};
|
|
504
|
+
});
|
|
505
|
+
const step = solveTranslations(
|
|
506
|
+
initial.map((value, i) => ({
|
|
507
|
+
id: 'bound translation',
|
|
508
|
+
value: -value,
|
|
509
|
+
coefficients: columns.map(column => column.constraints[i]),
|
|
510
|
+
})),
|
|
511
|
+
preference.map((value, i) => ({
|
|
512
|
+
id: 'pre-action position',
|
|
513
|
+
value: -value,
|
|
514
|
+
coefficients: columns.map(column => column.preferences[i]),
|
|
515
|
+
})),
|
|
516
|
+
free.length,
|
|
517
|
+
);
|
|
518
|
+
const candidate = shift(step);
|
|
519
|
+
// A nonlinear geometric locus need not admit its linearized free step.
|
|
520
|
+
// Preserve the valid geometric solution when that direction is not free.
|
|
521
|
+
return residual(candidate).every(value => Math.abs(value) < 1e-8)
|
|
522
|
+
? candidate
|
|
523
|
+
: poses;
|
|
524
|
+
};
|
|
423
525
|
let values = residual(poses),
|
|
424
526
|
damping = 1e-5;
|
|
425
527
|
const norm = (values: readonly number[]) =>
|
|
426
528
|
values.reduce((sum, x) => sum + x * x, 0);
|
|
427
|
-
for (let iteration = 0; iteration <
|
|
428
|
-
|
|
529
|
+
for (let iteration = 0; iteration < 168; iteration++) {
|
|
530
|
+
// First settle translation along the seeded orientations. This prevents
|
|
531
|
+
// unconstrained rotations from tilting a contact chain merely to move it.
|
|
532
|
+
// Relations that genuinely require rotation then use the full rigid solve.
|
|
533
|
+
if (iteration === 8) {
|
|
534
|
+
variables = fullVariables;
|
|
535
|
+
damping = 1e-5;
|
|
536
|
+
}
|
|
537
|
+
if (values.every(v => Math.abs(v) < 1e-8)) return settleBoundTranslations();
|
|
429
538
|
const h = 1e-6;
|
|
430
539
|
const columns = variables.map((_, i) => {
|
|
431
540
|
const shifted = residual(
|
|
@@ -434,7 +543,15 @@ export function solveBodies(
|
|
|
434
543
|
variables.map((_, j) => (i === j ? h : 0)),
|
|
435
544
|
),
|
|
436
545
|
);
|
|
437
|
-
|
|
546
|
+
const opposite = residual(
|
|
547
|
+
perturb(
|
|
548
|
+
poses,
|
|
549
|
+
variables.map((_, j) => (i === j ? -h : 0)),
|
|
550
|
+
),
|
|
551
|
+
);
|
|
552
|
+
// A directional bound has a cusp at an axis-aligned pose. A forward
|
|
553
|
+
// difference invents a rotation gradient there; use both sides.
|
|
554
|
+
return values.map((_, j) => (shifted[j] - opposite[j]) / (2 * h));
|
|
438
555
|
});
|
|
439
556
|
const multiply = (a: readonly number[], b: readonly number[]) =>
|
|
440
557
|
a.reduce((sum, x, i) => sum + x * b[i], 0);
|
package/src/library/replicad.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as replicadModule from 'replicad';
|
|
2
|
-
import {
|
|
2
|
+
import {primitiveConstructor} from './runtime.js';
|
|
3
3
|
import {castOwnedShape} from './kernel-shapes.js';
|
|
4
4
|
import type {SolidModel} from './index.js';
|
|
5
5
|
import type {Shape3D} from 'replicad';
|
|
@@ -29,12 +29,12 @@ export const replicad: Replicad = Object.freeze({
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Defines a synchronous model constructor with the builder's parameters.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* Reuses deterministic geometry by arguments; pass changing captured state as
|
|
33
|
+
* explicit parameters. Each invocation creates a fresh model. The builder
|
|
34
|
+
* transfers its returned solid to code3d and owns its intermediate shapes.
|
|
34
35
|
*/
|
|
35
36
|
export function definePrimitive<
|
|
36
37
|
Builder extends (...arguments_: never[]) => Shape3D,
|
|
37
38
|
>(build: Builder): (...arguments_: Parameters<Builder>) => SolidModel {
|
|
38
|
-
return (
|
|
39
|
-
modelFromReplicadSolid(build(...arguments_));
|
|
39
|
+
return primitiveConstructor(build);
|
|
40
40
|
}
|
|
@@ -12,7 +12,12 @@ export function estimateRetainedBytes(value: unknown): number {
|
|
|
12
12
|
seen.add(value);
|
|
13
13
|
if (ArrayBuffer.isView(value)) return 64 + visit(value.buffer);
|
|
14
14
|
if (value instanceof ArrayBuffer) return 32 + value.byteLength;
|
|
15
|
-
const values =
|
|
15
|
+
const values =
|
|
16
|
+
value instanceof Map
|
|
17
|
+
? [...value].flat()
|
|
18
|
+
: value instanceof Set
|
|
19
|
+
? [...value]
|
|
20
|
+
: Object.values(value);
|
|
16
21
|
return (
|
|
17
22
|
32 +
|
|
18
23
|
values.length * 8 +
|