@things-factory/figure-ui 10.1.76 → 10.1.77

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 (125) hide show
  1. package/dist-client/tsconfig.tsbuildinfo +1 -1
  2. package/package.json +2 -2
  3. package/dist-client/graphql/v3-store.d.ts +0 -3
  4. package/dist-client/graphql/v3-store.js +0 -39
  5. package/dist-client/graphql/v3-store.js.map +0 -1
  6. package/dist-client/modeller/figure-v3-capability.d.ts +0 -22
  7. package/dist-client/modeller/figure-v3-capability.js +0 -141
  8. package/dist-client/modeller/figure-v3-capability.js.map +0 -1
  9. package/dist-client/modeller/figure-v3-clips.d.ts +0 -31
  10. package/dist-client/modeller/figure-v3-clips.js +0 -268
  11. package/dist-client/modeller/figure-v3-clips.js.map +0 -1
  12. package/dist-client/modeller/figure-v3-controls.d.ts +0 -36
  13. package/dist-client/modeller/figure-v3-controls.js +0 -226
  14. package/dist-client/modeller/figure-v3-controls.js.map +0 -1
  15. package/dist-client/modeller/figure-v3-first-save.d.ts +0 -20
  16. package/dist-client/modeller/figure-v3-first-save.js +0 -118
  17. package/dist-client/modeller/figure-v3-first-save.js.map +0 -1
  18. package/dist-client/modeller/figure-v3-links.d.ts +0 -77
  19. package/dist-client/modeller/figure-v3-links.js +0 -649
  20. package/dist-client/modeller/figure-v3-links.js.map +0 -1
  21. package/dist-client/modeller/figure-v3-material.d.ts +0 -39
  22. package/dist-client/modeller/figure-v3-material.js +0 -323
  23. package/dist-client/modeller/figure-v3-material.js.map +0 -1
  24. package/dist-client/modeller/figure-v3-parts.d.ts +0 -69
  25. package/dist-client/modeller/figure-v3-parts.js +0 -559
  26. package/dist-client/modeller/figure-v3-parts.js.map +0 -1
  27. package/dist-client/modeller/figure-v3-preview.d.ts +0 -34
  28. package/dist-client/modeller/figure-v3-preview.js +0 -346
  29. package/dist-client/modeller/figure-v3-preview.js.map +0 -1
  30. package/dist-client/modeller/figure-v3-properties.d.ts +0 -45
  31. package/dist-client/modeller/figure-v3-properties.js +0 -239
  32. package/dist-client/modeller/figure-v3-properties.js.map +0 -1
  33. package/dist-client/modeller/figure-v3-recipe-catalog.d.ts +0 -38
  34. package/dist-client/modeller/figure-v3-recipe-catalog.js +0 -399
  35. package/dist-client/modeller/figure-v3-recipe-catalog.js.map +0 -1
  36. package/dist-client/modeller/figure-v3-verdict.d.ts +0 -64
  37. package/dist-client/modeller/figure-v3-verdict.js +0 -787
  38. package/dist-client/modeller/figure-v3-verdict.js.map +0 -1
  39. package/dist-client/modeller/v3-ai-proposal.d.ts +0 -51
  40. package/dist-client/modeller/v3-ai-proposal.js +0 -64
  41. package/dist-client/modeller/v3-ai-proposal.js.map +0 -1
  42. package/dist-client/modeller/v3-align.d.ts +0 -30
  43. package/dist-client/modeller/v3-align.js +0 -75
  44. package/dist-client/modeller/v3-align.js.map +0 -1
  45. package/dist-client/modeller/v3-canvas.d.ts +0 -72
  46. package/dist-client/modeller/v3-canvas.js +0 -379
  47. package/dist-client/modeller/v3-canvas.js.map +0 -1
  48. package/dist-client/modeller/v3-clips.d.ts +0 -39
  49. package/dist-client/modeller/v3-clips.js +0 -58
  50. package/dist-client/modeller/v3-clips.js.map +0 -1
  51. package/dist-client/modeller/v3-controls.d.ts +0 -28
  52. package/dist-client/modeller/v3-controls.js +0 -28
  53. package/dist-client/modeller/v3-controls.js.map +0 -1
  54. package/dist-client/modeller/v3-editor.d.ts +0 -203
  55. package/dist-client/modeller/v3-editor.js +0 -1526
  56. package/dist-client/modeller/v3-editor.js.map +0 -1
  57. package/dist-client/modeller/v3-fields.d.ts +0 -45
  58. package/dist-client/modeller/v3-fields.js +0 -83
  59. package/dist-client/modeller/v3-fields.js.map +0 -1
  60. package/dist-client/modeller/v3-figure-facts.d.ts +0 -35
  61. package/dist-client/modeller/v3-figure-facts.js +0 -55
  62. package/dist-client/modeller/v3-figure-facts.js.map +0 -1
  63. package/dist-client/modeller/v3-first-save.d.ts +0 -17
  64. package/dist-client/modeller/v3-first-save.js +0 -26
  65. package/dist-client/modeller/v3-first-save.js.map +0 -1
  66. package/dist-client/modeller/v3-gizmo.d.ts +0 -72
  67. package/dist-client/modeller/v3-gizmo.js +0 -111
  68. package/dist-client/modeller/v3-gizmo.js.map +0 -1
  69. package/dist-client/modeller/v3-help.d.ts +0 -18
  70. package/dist-client/modeller/v3-help.js +0 -37
  71. package/dist-client/modeller/v3-help.js.map +0 -1
  72. package/dist-client/modeller/v3-joint-drag.d.ts +0 -19
  73. package/dist-client/modeller/v3-joint-drag.js +0 -97
  74. package/dist-client/modeller/v3-joint-drag.js.map +0 -1
  75. package/dist-client/modeller/v3-joint-fan.d.ts +0 -9
  76. package/dist-client/modeller/v3-joint-fan.js +0 -276
  77. package/dist-client/modeller/v3-joint-fan.js.map +0 -1
  78. package/dist-client/modeller/v3-keys.d.ts +0 -22
  79. package/dist-client/modeller/v3-keys.js +0 -26
  80. package/dist-client/modeller/v3-keys.js.map +0 -1
  81. package/dist-client/modeller/v3-links.d.ts +0 -267
  82. package/dist-client/modeller/v3-links.js +0 -480
  83. package/dist-client/modeller/v3-links.js.map +0 -1
  84. package/dist-client/modeller/v3-live.d.ts +0 -37
  85. package/dist-client/modeller/v3-live.js +0 -68
  86. package/dist-client/modeller/v3-live.js.map +0 -1
  87. package/dist-client/modeller/v3-maturity.d.ts +0 -120
  88. package/dist-client/modeller/v3-maturity.js +0 -313
  89. package/dist-client/modeller/v3-maturity.js.map +0 -1
  90. package/dist-client/modeller/v3-occupancy.d.ts +0 -18
  91. package/dist-client/modeller/v3-occupancy.js +0 -24
  92. package/dist-client/modeller/v3-occupancy.js.map +0 -1
  93. package/dist-client/modeller/v3-palette.d.ts +0 -7
  94. package/dist-client/modeller/v3-palette.js +0 -46
  95. package/dist-client/modeller/v3-palette.js.map +0 -1
  96. package/dist-client/modeller/v3-preview-model.d.ts +0 -47
  97. package/dist-client/modeller/v3-preview-model.js +0 -68
  98. package/dist-client/modeller/v3-preview-model.js.map +0 -1
  99. package/dist-client/modeller/v3-product-session.d.ts +0 -510
  100. package/dist-client/modeller/v3-product-session.js +0 -157
  101. package/dist-client/modeller/v3-product-session.js.map +0 -1
  102. package/dist-client/modeller/v3-recipe-source.d.ts +0 -8
  103. package/dist-client/modeller/v3-recipe-source.js +0 -14
  104. package/dist-client/modeller/v3-recipe-source.js.map +0 -1
  105. package/dist-client/modeller/v3-recipes.d.ts +0 -37
  106. package/dist-client/modeller/v3-recipes.js +0 -103
  107. package/dist-client/modeller/v3-recipes.js.map +0 -1
  108. package/dist-client/modeller/v3-refusals.d.ts +0 -68
  109. package/dist-client/modeller/v3-refusals.js +0 -142
  110. package/dist-client/modeller/v3-refusals.js.map +0 -1
  111. package/dist-client/modeller/v3-renderer.d.ts +0 -40
  112. package/dist-client/modeller/v3-renderer.js +0 -73
  113. package/dist-client/modeller/v3-renderer.js.map +0 -1
  114. package/dist-client/modeller/v3-stage-model.d.ts +0 -39
  115. package/dist-client/modeller/v3-stage-model.js +0 -147
  116. package/dist-client/modeller/v3-stage-model.js.map +0 -1
  117. package/dist-client/modeller/v3-thumbnail.d.ts +0 -3
  118. package/dist-client/modeller/v3-thumbnail.js +0 -75
  119. package/dist-client/modeller/v3-thumbnail.js.map +0 -1
  120. package/dist-client/pages/figure-v3-modeller-page.d.ts +0 -102
  121. package/dist-client/pages/figure-v3-modeller-page.js +0 -548
  122. package/dist-client/pages/figure-v3-modeller-page.js.map +0 -1
  123. package/dist-client/tsconfig.chk.tsbuildinfo +0 -1
  124. package/dist-client/tsconfig.fui.tsbuildinfo +0 -1
  125. package/dist-client/tsconfig.scratch-fm.tsbuildinfo +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"v3-first-save.js","sourceRoot":"","sources":["../../client/modeller/v3-first-save.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAErF;;;;GAIG;AAEH,oGAAoG;AACpG,MAAM,UAAU,MAAM,CAAC,GAAW;IAChC,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAA;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC,GAAG,CAAA;AAChI,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,KAAwB;IACvE,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,GAAG,EAAE,+BAA+B,EAAE,CAAA;IAC1D,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,GAAG,EAAE,qCAAqC,EAAE,IAAI,EAAE,CAAA;IACrF,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import { isPlaceholderType, TYPE_LENGTH, TYPE_PATTERN } from '@hatiolab/figure-model'\n\n/*\n * The first save of a new figure asks for its type and name. The type is the name a board places\n * the figure by; it freezes when the figure is released (ADR-0063 decision 4). The modeller once saved a new figure\n * under a temporary machine name and asked nothing -- the author had to find the field afterwards (user, 2026-09-25).\n */\n\n/** A type as typed, made into the shape a type has: letters, digits and underscores, upper case. */\nexport function typeOf(raw: string): string {\n return raw.trim().replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()\n}\n\n/**\n * Whether this name could be released, by the format's own rules (`type-not-identifier`, `type-is-placeholder`).\n * Saving under it is not blocked -- it can still change before release -- but the author hears it now.\n */\nexport function releasableType(type: string): boolean {\n return !isPlaceholderType(type) && TYPE_PATTERN.test(type) && type.length >= TYPE_LENGTH.min && type.length <= TYPE_LENGTH.max\n}\n\n/** Why the first save cannot go on with this type, as an i18n key, or null when it can. */\nexport function firstSaveRefusalOf(type: string, taken: readonly string[]): { key: string; type?: string } | null {\n if (!type) return { key: 'figure.text.enter-a-type-name' }\n if (taken.includes(type)) return { key: 'figure.text.type-name-already-taken', type }\n return null\n}\n\n/** What the first save asks for. */\nexport interface FirstSave {\n type: string\n name: string\n}\n"]}
@@ -1,72 +0,0 @@
1
- import type { V3InputChange } from '@hatiolab/figure-model';
2
- /**
3
- * What the gizmo may write for a part: move · turn · size, on a V3 asset.
4
- *
5
- * A drag can only be written back where the part is placed by its own numbers. A part the create commands made is:
6
- * its pose is `rigid@1` into the asset frame from `<part>.position.*` and `<part>.rotation.*`, and a box's shape
7
- * (`box-shape@1` or `rounded-box@1`) takes its width, height and depth from inputs of its own — `<part>.size.*` from
8
- * `addV3Box`, `<part>.dimension.*` from the editor's shape palette. A part fastened to another, following an instance size or placed by
9
- * expressions has no such numbers; the gizmo does not offer what it could not write back, rather
10
- * than move something the next evaluation would put back.
11
- */
12
- export interface GizmoReach {
13
- translate: boolean;
14
- rotate: boolean;
15
- scale: boolean;
16
- /** The inputs sizing the part along X, Y and Z, when it can be sized. */
17
- sizes?: [string, string, string];
18
- /**
19
- * Per axis, the part's own measure, or null where the measure follows another (a square section's depth following
20
- * its width): a scale drag writes the own ones and leaves a followed one to what it follows.
21
- */
22
- ownSizes?: (string | null)[];
23
- /**
24
- * A round part keeps its section round: the radii across (X and Z) take the one factor
25
- * the drag gave most; the length along Y takes its own. A sphere's three radii take one factor together.
26
- */
27
- round?: {
28
- across: string[];
29
- along: string[];
30
- };
31
- }
32
- interface Node {
33
- id: string;
34
- op: string;
35
- args: string[];
36
- outputs?: Record<string, string>;
37
- params?: Record<string, unknown>;
38
- }
39
- interface AssetLike {
40
- document: {
41
- model: {
42
- nodes: Node[];
43
- inputs: Array<{
44
- id: string;
45
- role?: string;
46
- }>;
47
- constants?: Array<{
48
- id: string;
49
- value: number;
50
- }>;
51
- };
52
- };
53
- }
54
- export declare function gizmoReachOf(asset: AssetLike, part: string): GizmoReach;
55
- /** Where a drag left the part, in the asset frame: millimetres, degrees and per-axis scale. */
56
- export interface GizmoResult {
57
- position?: [number, number, number];
58
- rotation?: [number, number, number];
59
- scale?: [number, number, number];
60
- }
61
- /**
62
- * The input changes a drag makes — one list, applied as one step. Millimetres to the millimetre and degrees to a
63
- * tenth (a drag otherwise stores 722.4180046035418 mm). A size never goes below 1 mm.
64
- */
65
- export declare function gizmoChanges(part: string, reach: GizmoReach, result: GizmoResult, sizes: Record<string, number>): V3InputChange[];
66
- /**
67
- * A height typed into its field keeps the part's bottom where it is, as the scale gizmo does (it anchors the opposite
68
- * face): the part's own Y position moves by half the change. A post typed 2200 tall sank 933 mm under the floor
69
- * (2026-09-26). Only for a part placed by its own numbers and standing unturned; otherwise its height is not up and down.
70
- */
71
- export declare function keepBottomChanges(part: string, reach: GizmoReach, input: string, value: number, values: Record<string, number>): V3InputChange[];
72
- export {};
@@ -1,111 +0,0 @@
1
- import { v3AttachmentsOf } from '@hatiolab/figure-model';
2
- /** Shapes whose first three arguments are the extent along X, Y and Z: a door frame's too (its width, height, depth). */
3
- const BOXES = new Set(['box-shape@1', 'rounded-box@1', 'portal-profile@2', 'hollow-box@2']);
4
- const AXES = ['x', 'y', 'z'];
5
- export function gizmoReachOf(asset, part) {
6
- const { nodes, inputs } = asset.document.model;
7
- const design = new Set(inputs.filter(input => input.role === 'design').map(input => input.id));
8
- const own = (name) => AXES.map(axis => `${part}.${name}.${axis}`);
9
- const allDesign = (ids) => ids.every(id => design.has(id));
10
- const pose = nodes.find(node => node.id === `${part}.pose`);
11
- const placed = nodes.find(node => node.id === part && node.op === 'place@1');
12
- const direct = !!pose && pose.op === 'rigid@1' && pose.params?.to === 'asset' && placed?.args[1] === `${part}.poseValue`;
13
- /*
14
- A part taken off another (detach) stands on a seat into the asset frame with its own position numbers back. It
15
- can be moved like a part never fastened — unless it moves by itself, when where it is drawn is not its position.
16
- */
17
- const reseated = !!pose &&
18
- pose.op === 'rigid@1' &&
19
- !nodes.some(node => node.id === `${part}.motion`) &&
20
- v3AttachmentsOf(asset)[part] === null;
21
- const posed = direct || reseated;
22
- const shape = nodes.find(node => node.id === `${part}.shape`);
23
- const mine = (ids) => ids.every(id => id.startsWith(`${part}.`) && design.has(id));
24
- const round = shape?.op === 'cylinder-shape@1' && mine(shape.args.slice(0, 2))
25
- ? { across: [shape.args[0]], along: [shape.args[1]] }
26
- : shape?.op === 'frustum-shape@1' && mine(shape.args.slice(0, 3))
27
- ? { across: shape.args.slice(0, 2), along: [shape.args[2]] }
28
- : shape?.op === 'sphere-shape@1' && mine(shape.args.slice(0, 3))
29
- ? { across: shape.args.slice(0, 3), along: [] }
30
- : undefined;
31
- const extent = shape && BOXES.has(shape.op) ? shape.args.slice(0, 3) : [];
32
- const sizes = extent.length === 3 && extent.every(id => id.startsWith(`${part}.`) && design.has(id)) ? extent : undefined;
33
- /*
34
- The part's own measure, also when another reads it: a measure some other part follows is read through a node of its
35
- own times 1 (`link-dimension`), and it is still the author's number -- the upper arm's width, which the forearm
36
- follows, is the upper arm's to drag.
37
- */
38
- const constants = asset.document.model.constants ?? [];
39
- const ownMeasure = (id) => {
40
- if (id.startsWith(`${part}.`) && design.has(id))
41
- return id;
42
- const writer = nodes.find(node => Object.values(node.outputs ?? {}).includes(id));
43
- const one = writer?.op === 'mul@1' && writer.args.length === 2 && constants.find(c => c.id === writer.args[1])?.value === 1;
44
- return one && writer.args[0].startsWith(`${part}.`) && design.has(writer.args[0]) ? writer.args[0] : null;
45
- };
46
- const ownSizes = extent.length === 3 && !sizes ? extent.map(ownMeasure) : undefined;
47
- const someOwn = !!ownSizes?.some(Boolean);
48
- return {
49
- translate: posed && allDesign(own('position')) && own('position').every((id, i) => pose.args[i] === id),
50
- rotate: posed && allDesign(own('rotation')) && own('rotation').every((id, i) => pose.args[3 + i] === id),
51
- scale: !!sizes || !!round || someOwn,
52
- ...(sizes ? { sizes } : {}),
53
- ...(someOwn ? { ownSizes } : {}),
54
- ...(round ? { round } : {})
55
- };
56
- }
57
- /**
58
- * The input changes a drag makes — one list, applied as one step. Millimetres to the millimetre and degrees to a
59
- * tenth (a drag otherwise stores 722.4180046035418 mm). A size never goes below 1 mm.
60
- */
61
- export function gizmoChanges(part, reach, result, sizes) {
62
- const changes = [];
63
- const put = (input, unit, value) => changes.push({ kind: 'set-design', input, unit, value });
64
- if (result.position && reach.translate)
65
- AXES.forEach((axis, i) => put(`${part}.position.${axis}`, 'mm', Math.round(result.position[i])));
66
- if (result.rotation && reach.rotate)
67
- AXES.forEach((axis, i) => put(`${part}.rotation.${axis}`, 'deg', Math.round(result.rotation[i] * 10) / 10));
68
- if (result.scale && reach.scale && reach.sizes)
69
- reach.sizes.forEach((id, i) => {
70
- if (sizes[id] !== undefined)
71
- put(id, 'mm', Math.max(1, Math.round(sizes[id] * result.scale[i])));
72
- });
73
- if (result.scale && reach.scale && reach.ownSizes)
74
- reach.ownSizes.forEach((id, i) => {
75
- if (id && sizes[id] !== undefined && Math.abs(result.scale[i] - 1) > 1e-4)
76
- put(id, 'mm', Math.max(1, Math.round(sizes[id] * result.scale[i])));
77
- });
78
- if (result.scale && reach.scale && reach.round) {
79
- const [sx, sy, sz] = result.scale;
80
- /* The factor the drag moved most: across for a cylinder (X or Z), any axis for a sphere. */
81
- const most = (factors) => factors.reduce((a, b) => (Math.abs(b - 1) > Math.abs(a - 1) ? b : a), 1);
82
- const sphere = reach.round.along.length === 0;
83
- const across = sphere ? most([sx, sy, sz]) : most([sx, sz]);
84
- for (const id of reach.round.across)
85
- if (sizes[id] !== undefined)
86
- put(id, 'mm', Math.max(1, Math.round(sizes[id] * across)));
87
- for (const id of reach.round.along)
88
- if (sizes[id] !== undefined)
89
- put(id, 'mm', Math.max(1, Math.round(sizes[id] * sy)));
90
- }
91
- return changes;
92
- }
93
- /**
94
- * A height typed into its field keeps the part's bottom where it is, as the scale gizmo does (it anchors the opposite
95
- * face): the part's own Y position moves by half the change. A post typed 2200 tall sank 933 mm under the floor
96
- * (2026-09-26). Only for a part placed by its own numbers and standing unturned; otherwise its height is not up and down.
97
- */
98
- export function keepBottomChanges(part, reach, input, value, values) {
99
- if (!reach.translate)
100
- return [];
101
- const height = reach.sizes?.[1] ?? reach.ownSizes?.[1] ?? reach.round?.along[0];
102
- if (!height || height !== input)
103
- return [];
104
- if (AXES.some(axis => (values[`${part}.rotation.${axis}`] ?? 0) % 360 !== 0))
105
- return [];
106
- const before = values[input], y = values[`${part}.position.y`];
107
- if (before === undefined || y === undefined || !Number.isFinite(value))
108
- return [];
109
- return [{ kind: 'set-design', input: `${part}.position.y`, unit: 'mm', value: Math.round((y + (value - before) / 2) * 10) / 10 }];
110
- }
111
- //# sourceMappingURL=v3-gizmo.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"v3-gizmo.js","sourceRoot":"","sources":["../../client/modeller/v3-gizmo.ts"],"names":[],"mappings":"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","sourcesContent":["import { v3AttachmentsOf } from '@hatiolab/figure-model'\nimport type { V3Asset, V3InputChange } from '@hatiolab/figure-model'\n\n/**\n * What the gizmo may write for a part: move · turn · size, on a V3 asset.\n *\n * A drag can only be written back where the part is placed by its own numbers. A part the create commands made is:\n * its pose is `rigid@1` into the asset frame from `<part>.position.*` and `<part>.rotation.*`, and a box's shape\n * (`box-shape@1` or `rounded-box@1`) takes its width, height and depth from inputs of its own — `<part>.size.*` from\n * `addV3Box`, `<part>.dimension.*` from the editor's shape palette. A part fastened to another, following an instance size or placed by\n * expressions has no such numbers; the gizmo does not offer what it could not write back, rather\n * than move something the next evaluation would put back.\n */\nexport interface GizmoReach {\n translate: boolean\n rotate: boolean\n scale: boolean\n /** The inputs sizing the part along X, Y and Z, when it can be sized. */\n sizes?: [string, string, string]\n /**\n * Per axis, the part's own measure, or null where the measure follows another (a square section's depth following\n * its width): a scale drag writes the own ones and leaves a followed one to what it follows.\n */\n ownSizes?: (string | null)[]\n /**\n * A round part keeps its section round: the radii across (X and Z) take the one factor\n * the drag gave most; the length along Y takes its own. A sphere's three radii take one factor together.\n */\n round?: { across: string[]; along: string[] }\n}\n\n/** Shapes whose first three arguments are the extent along X, Y and Z: a door frame's too (its width, height, depth). */\nconst BOXES = new Set(['box-shape@1', 'rounded-box@1', 'portal-profile@2', 'hollow-box@2'])\n\ninterface Node {\n id: string\n op: string\n args: string[]\n outputs?: Record<string, string>\n params?: Record<string, unknown>\n}\ninterface AssetLike {\n document: { model: { nodes: Node[]; inputs: Array<{ id: string; role?: string }>; constants?: Array<{ id: string; value: number }> } }\n}\n\nconst AXES = ['x', 'y', 'z'] as const\n\nexport function gizmoReachOf(asset: AssetLike, part: string): GizmoReach {\n const { nodes, inputs } = asset.document.model\n const design = new Set(inputs.filter(input => input.role === 'design').map(input => input.id))\n const own = (name: string) => AXES.map(axis => `${part}.${name}.${axis}`)\n const allDesign = (ids: string[]) => ids.every(id => design.has(id))\n const pose = nodes.find(node => node.id === `${part}.pose`)\n const placed = nodes.find(node => node.id === part && node.op === 'place@1')\n const direct = !!pose && pose.op === 'rigid@1' && pose.params?.to === 'asset' && placed?.args[1] === `${part}.poseValue`\n /*\n A part taken off another (detach) stands on a seat into the asset frame with its own position numbers back. It\n can be moved like a part never fastened — unless it moves by itself, when where it is drawn is not its position.\n */\n const reseated =\n !!pose &&\n pose.op === 'rigid@1' &&\n !nodes.some(node => node.id === `${part}.motion`) &&\n v3AttachmentsOf(asset as unknown as V3Asset)[part] === null\n const posed = direct || reseated\n const shape = nodes.find(node => node.id === `${part}.shape`)\n const mine = (ids: string[]) => ids.every(id => id.startsWith(`${part}.`) && design.has(id))\n const round =\n shape?.op === 'cylinder-shape@1' && mine(shape.args.slice(0, 2))\n ? { across: [shape.args[0]!], along: [shape.args[1]!] }\n : shape?.op === 'frustum-shape@1' && mine(shape.args.slice(0, 3))\n ? { across: shape.args.slice(0, 2), along: [shape.args[2]!] }\n : shape?.op === 'sphere-shape@1' && mine(shape.args.slice(0, 3))\n ? { across: shape.args.slice(0, 3), along: [] }\n : undefined\n const extent = shape && BOXES.has(shape.op) ? shape.args.slice(0, 3) : []\n const sizes =\n extent.length === 3 && extent.every(id => id.startsWith(`${part}.`) && design.has(id)) ? (extent as [string, string, string]) : undefined\n /*\n The part's own measure, also when another reads it: a measure some other part follows is read through a node of its\n own times 1 (`link-dimension`), and it is still the author's number -- the upper arm's width, which the forearm\n follows, is the upper arm's to drag.\n */\n const constants = asset.document.model.constants ?? []\n const ownMeasure = (id: string): string | null => {\n if (id.startsWith(`${part}.`) && design.has(id)) return id\n const writer = nodes.find(node => Object.values(node.outputs ?? {}).includes(id))\n const one = writer?.op === 'mul@1' && writer.args.length === 2 && constants.find(c => c.id === writer.args[1])?.value === 1\n return one && writer!.args[0]!.startsWith(`${part}.`) && design.has(writer!.args[0]!) ? writer!.args[0]! : null\n }\n const ownSizes = extent.length === 3 && !sizes ? extent.map(ownMeasure) : undefined\n const someOwn = !!ownSizes?.some(Boolean)\n return {\n translate: posed && allDesign(own('position')) && own('position').every((id, i) => pose!.args[i] === id),\n rotate: posed && allDesign(own('rotation')) && own('rotation').every((id, i) => pose!.args[3 + i] === id),\n scale: !!sizes || !!round || someOwn,\n ...(sizes ? { sizes } : {}),\n ...(someOwn ? { ownSizes } : {}),\n ...(round ? { round } : {})\n }\n}\n\n/** Where a drag left the part, in the asset frame: millimetres, degrees and per-axis scale. */\nexport interface GizmoResult {\n position?: [number, number, number]\n rotation?: [number, number, number]\n scale?: [number, number, number]\n}\n\n/**\n * The input changes a drag makes — one list, applied as one step. Millimetres to the millimetre and degrees to a\n * tenth (a drag otherwise stores 722.4180046035418 mm). A size never goes below 1 mm.\n */\nexport function gizmoChanges(part: string, reach: GizmoReach, result: GizmoResult, sizes: Record<string, number>): V3InputChange[] {\n const changes: V3InputChange[] = []\n const put = (input: string, unit: V3InputChange['unit'], value: number) =>\n changes.push({ kind: 'set-design', input, unit, value } as V3InputChange)\n if (result.position && reach.translate)\n AXES.forEach((axis, i) => put(`${part}.position.${axis}`, 'mm', Math.round(result.position![i]!)))\n if (result.rotation && reach.rotate)\n AXES.forEach((axis, i) => put(`${part}.rotation.${axis}`, 'deg', Math.round(result.rotation![i]! * 10) / 10))\n if (result.scale && reach.scale && reach.sizes)\n reach.sizes.forEach((id, i) => {\n if (sizes[id] !== undefined) put(id, 'mm', Math.max(1, Math.round(sizes[id]! * result.scale![i]!)))\n })\n if (result.scale && reach.scale && reach.ownSizes)\n reach.ownSizes.forEach((id, i) => {\n if (id && sizes[id] !== undefined && Math.abs(result.scale![i]! - 1) > 1e-4) put(id, 'mm', Math.max(1, Math.round(sizes[id]! * result.scale![i]!)))\n })\n if (result.scale && reach.scale && reach.round) {\n const [sx, sy, sz] = result.scale\n /* The factor the drag moved most: across for a cylinder (X or Z), any axis for a sphere. */\n const most = (factors: number[]) => factors.reduce((a, b) => (Math.abs(b - 1) > Math.abs(a - 1) ? b : a), 1)\n const sphere = reach.round.along.length === 0\n const across = sphere ? most([sx!, sy!, sz!]) : most([sx!, sz!])\n for (const id of reach.round.across) if (sizes[id] !== undefined) put(id, 'mm', Math.max(1, Math.round(sizes[id]! * across)))\n for (const id of reach.round.along) if (sizes[id] !== undefined) put(id, 'mm', Math.max(1, Math.round(sizes[id]! * sy!)))\n }\n return changes\n}\n\n/**\n * A height typed into its field keeps the part's bottom where it is, as the scale gizmo does (it anchors the opposite\n * face): the part's own Y position moves by half the change. A post typed 2200 tall sank 933 mm under the floor\n * (2026-09-26). Only for a part placed by its own numbers and standing unturned; otherwise its height is not up and down.\n */\nexport function keepBottomChanges(part: string, reach: GizmoReach, input: string, value: number, values: Record<string, number>): V3InputChange[] {\n if (!reach.translate) return []\n const height = reach.sizes?.[1] ?? reach.ownSizes?.[1] ?? reach.round?.along[0]\n if (!height || height !== input) return []\n if (AXES.some(axis => (values[`${part}.rotation.${axis}`] ?? 0) % 360 !== 0)) return []\n const before = values[input],\n y = values[`${part}.position.y`]\n if (before === undefined || y === undefined || !Number.isFinite(value)) return []\n return [{ kind: 'set-design', input: `${part}.position.y`, unit: 'mm', value: Math.round((y + (value - before) / 2) * 10) / 10 } as V3InputChange]\n}\n"]}
@@ -1,18 +0,0 @@
1
- import '@material/web/icon/icon.js';
2
- import { nothing, type ReactiveController, type ReactiveControllerHost } from 'lit';
3
- /**
4
- * Help toggles: a small 「?」 beside a title opens the longer explanation under it. Explanations left open on every
5
- * section made the side read as clutter (user, 2026-09-24); the author opens the one they want. What was opened stays
6
- * open while the author moves between parts.
7
- */
8
- export declare class HelpToggles implements ReactiveController {
9
- private readonly host;
10
- private opened;
11
- constructor(host: ReactiveControllerHost);
12
- hostConnected(): void;
13
- isOpen(key: string): boolean;
14
- toggle(key: string): void;
15
- /** The 「?」: it takes no line of its own, it sits in the title's spare width. */
16
- button(key: string): import("lit-html").TemplateResult<1>;
17
- hint(key: string): import("lit-html").TemplateResult<1> | typeof nothing;
18
- }
@@ -1,37 +0,0 @@
1
- import '@material/web/icon/icon.js';
2
- import { html, nothing } from 'lit';
3
- import { i18next } from '@operato/i18n';
4
- /**
5
- * Help toggles: a small 「?」 beside a title opens the longer explanation under it. Explanations left open on every
6
- * section made the side read as clutter (user, 2026-09-24); the author opens the one they want. What was opened stays
7
- * open while the author moves between parts.
8
- */
9
- export class HelpToggles {
10
- constructor(host) {
11
- this.host = host;
12
- this.opened = new Set();
13
- host.addController(this);
14
- }
15
- hostConnected() { }
16
- isOpen(key) {
17
- return this.opened.has(key);
18
- }
19
- toggle(key) {
20
- if (this.opened.has(key))
21
- this.opened.delete(key);
22
- else
23
- this.opened.add(key);
24
- this.host.requestUpdate();
25
- }
26
- /** The 「?」: it takes no line of its own, it sits in the title's spare width. */
27
- button(key) {
28
- const shown = this.opened.has(key);
29
- return html `<button help aria-label=${i18next.t('figure.label.help')} aria-expanded=${shown ? 'true' : 'false'} @click=${() => this.toggle(key)}>
30
- <md-icon>${shown ? 'help' : 'help_outline'}</md-icon>
31
- </button>`;
32
- }
33
- hint(key) {
34
- return this.opened.has(key) ? html `<p hint>${i18next.t(key)}</p>` : nothing;
35
- }
36
- }
37
- //# sourceMappingURL=v3-help.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"v3-help.js","sourceRoot":"","sources":["../../client/modeller/v3-help.ts"],"names":[],"mappings":"AAAA,OAAO,4BAA4B,CAAA;AAEnC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAwD,MAAM,KAAK,CAAA;AACzF,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC;;;;GAIG;AACH,MAAM,OAAO,WAAW;IAGtB,YAA6B,IAA4B;QAA5B,SAAI,GAAJ,IAAI,CAAwB;QAFjD,WAAM,GAAG,IAAI,GAAG,EAAU,CAAA;QAGhC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAA;IAC1B,CAAC;IAED,aAAa,KAAI,CAAC;IAElB,MAAM,CAAC,GAAW;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC7B,CAAC;IAED,MAAM,CAAC,GAAW;QAChB,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;;YAC5C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACzB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAA;IAC3B,CAAC;IAED,gFAAgF;IAChF,MAAM,CAAC,GAAW;QAChB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAClC,OAAO,IAAI,CAAA,2BAA2B,OAAO,CAAC,CAAC,CAAC,mBAAmB,CAAC,kBAAkB,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,WAAW,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;iBAClI,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc;cAClC,CAAA;IACZ,CAAC;IAED,IAAI,CAAC,GAAW;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA,WAAW,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAA;IAC7E,CAAC;CACF","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport { html, nothing, type ReactiveController, type ReactiveControllerHost } from 'lit'\nimport { i18next } from '@operato/i18n'\n\n/**\n * Help toggles: a small 「?」 beside a title opens the longer explanation under it. Explanations left open on every\n * section made the side read as clutter (user, 2026-09-24); the author opens the one they want. What was opened stays\n * open while the author moves between parts.\n */\nexport class HelpToggles implements ReactiveController {\n private opened = new Set<string>()\n\n constructor(private readonly host: ReactiveControllerHost) {\n host.addController(this)\n }\n\n hostConnected() {}\n\n isOpen(key: string) {\n return this.opened.has(key)\n }\n\n toggle(key: string) {\n if (this.opened.has(key)) this.opened.delete(key)\n else this.opened.add(key)\n this.host.requestUpdate()\n }\n\n /** The 「?」: it takes no line of its own, it sits in the title's spare width. */\n button(key: string) {\n const shown = this.opened.has(key)\n return html`<button help aria-label=${i18next.t('figure.label.help')} aria-expanded=${shown ? 'true' : 'false'} @click=${() => this.toggle(key)}>\n <md-icon>${shown ? 'help' : 'help_outline'}</md-icon>\n </button>`\n }\n\n hint(key: string) {\n return this.opened.has(key) ? html`<p hint>${i18next.t(key)}</p>` : nothing\n }\n}\n"]}
@@ -1,19 +0,0 @@
1
- import type { V3Asset, V3Joint } from '@hatiolab/figure-model';
2
- import type { GizmoResult } from './v3-gizmo.js';
3
- import type { V3RenderPart } from './v3-renderer.js';
4
- export type JointDrag = {
5
- kind: 'value';
6
- input: string;
7
- unit: 'deg' | 'mm' | 'ratio';
8
- value: number;
9
- } | {
10
- kind: 'joint';
11
- joint: V3Joint;
12
- } | {
13
- kind: 'refused';
14
- reason: 'mimic' | 'fixed-turn' | 'origin-follows' | 'turned-slide' | 'off-axis';
15
- joint: string;
16
- leader?: string;
17
- axis?: string;
18
- };
19
- export declare function jointDragOf(asset: V3Asset, id: string, part: V3RenderPart, result: GizmoResult, values: Record<string, number>): JointDrag | null;
@@ -1,97 +0,0 @@
1
- import * as THREE from 'three';
2
- import { compileV3Asset } from '@hatiolab/figure-model';
3
- const AXES = ['x', 'y', 'z'];
4
- /** The axis each face faces along, and which way (as figure-model's `V3_FACES`). */
5
- const FACE_AXIS = { left: 'x', right: 'x', bottom: 'y', top: 'y', back: 'z', front: 'z' };
6
- const FACE_SIGN = { left: -1, right: 1, bottom: -1, top: 1, back: -1, front: 1 };
7
- const matrixOf = (r) => new THREE.Matrix4().set(r[0][0], r[0][1], r[0][2], 0, r[1][0], r[1][1], r[1][2], 0, r[2][0], r[2][1], r[2][2], 0, 0, 0, 0, 1);
8
- const eulerMatrix = (deg) => new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(...deg.map(THREE.MathUtils.degToRad), 'XYZ'));
9
- const unit = (axis) => new THREE.Vector3(/x/i.test(axis) ? 1 : 0, /y/i.test(axis) ? 1 : 0, /z/i.test(axis) ? 1 : 0);
10
- const round = (v, step) => Number((Math.round(v / step) * step).toFixed(Math.max(0, -Math.floor(Math.log10(step)))));
11
- /** The part's own quarter turn in the joint frame, as the record says it. */
12
- const turnMatrix = (j) => eulerMatrix(AXES.map(a => j.origin?.turn?.[a] ?? 0));
13
- /** The joint frame's rotation in the figure: the part's rotation now, less its own turn and less the joint's. */
14
- function jointFrameRotation(j, part, angle) {
15
- const now = matrixOf(part.pose.r);
16
- const own = turnMatrix(j);
17
- const turn = j.type === 'revolute' ? new THREE.Matrix4().makeRotationAxis(unit(j.axis), THREE.MathUtils.degToRad(angle)) : new THREE.Matrix4();
18
- return now.multiply(own.invert()).multiply(turn.invert());
19
- }
20
- export function jointDragOf(asset, id, part, result, values) {
21
- const j = asset.joints?.find(joint => joint.body1.part === id);
22
- /* A size is the part's own, not the joint's: a scale drag goes to the part's measures (the editor drops the shift). */
23
- if (!j || result.scale || (!result.rotation && !result.position))
24
- return null;
25
- const stateId = j.state?.id ?? j.id;
26
- const now = values[stateId] ?? asset.stateDefaults[stateId] ?? 0;
27
- const turns = j.type === 'revolute';
28
- /* A revolute joint without limits turns without end (USD). */
29
- const endless = turns && j.lowerLimit === undefined && j.upperLimit === undefined;
30
- if (result.rotation) {
31
- if (!turns)
32
- return { kind: 'refused', reason: j.type === 'prismatic' ? 'turned-slide' : 'fixed-turn', joint: j.id };
33
- if (j.mimic)
34
- return { kind: 'refused', reason: 'mimic', joint: j.id, leader: j.mimic.joint };
35
- /* The turn about the joint's axis (the twist of the change): the only turn the joint can make. */
36
- const before = new THREE.Quaternion().setFromRotationMatrix(matrixOf(part.pose.r));
37
- const after = new THREE.Quaternion().setFromRotationMatrix(eulerMatrix(result.rotation));
38
- const change = after.multiply(before.invert());
39
- const axis = unit(j.axis).applyMatrix4(jointFrameRotation(j, part, now)).normalize();
40
- const twist = 2 * Math.atan2(change.x * axis.x + change.y * axis.y + change.z * axis.z, change.w);
41
- /* Turned about another axis: the joint cannot make that turn, and nothing is written for it. */
42
- const whole = 2 * Math.acos(Math.min(1, Math.abs(change.w)));
43
- if (Math.abs(twist) < whole * 0.5)
44
- return { kind: 'refused', reason: 'off-axis', joint: j.id, axis: j.axis };
45
- let value = now + THREE.MathUtils.radToDeg(twist);
46
- if (endless)
47
- value = ((((value + 180) % 360) + 360) % 360) - 180;
48
- else
49
- value = Math.min(Math.max(value, j.lowerLimit), j.upperLimit);
50
- return { kind: 'value', input: stateId, unit: 'deg', value: round(value, 0.1) };
51
- }
52
- const moved = new THREE.Vector3(...result.position).sub(new THREE.Vector3(...part.pose.t));
53
- if (moved.length() < 0.5)
54
- return null;
55
- const frame = jointFrameRotation(j, part, turns ? now : 0);
56
- if (j.type === 'prismatic') {
57
- if (j.mimic)
58
- return { kind: 'refused', reason: 'mimic', joint: j.id, leader: j.mimic.joint };
59
- /* How far one unit of the value moves the part along its axis, measured on the figure itself (a ratio travels a length). */
60
- const along = unit(j.axis).applyMatrix4(frame).normalize();
61
- const at = (v) => {
62
- const g = compileV3Asset(asset).evaluate({ ...values, [stateId]: v }).geometry.find(g => g.id === id && 'pose' in g);
63
- return g ? new THREE.Vector3(...g.pose.t).dot(along) : 0;
64
- };
65
- const step = j.travel ? 0.01 : 1;
66
- const rate = (at(now + step) - at(now)) / step;
67
- if (Math.abs(rate) > 1e-9 && Math.abs(moved.dot(along)) >= moved.length() * 0.7) {
68
- const value = Math.min(Math.max(now + moved.dot(along) / rate, j.lowerLimit), j.upperLimit);
69
- return { kind: 'value', input: stateId, unit: j.travel ? 'ratio' : 'mm', value: round(value, j.travel ? 0.001 : 1) };
70
- }
71
- }
72
- /* Moving the joint's origin: the change in the frame the joint sits in. */
73
- const local = moved.clone().applyMatrix4(frame.clone().invert());
74
- const normal = 'plane' in j.body0 ? 'y' : FACE_AXIS[j.body0.face];
75
- /* Which way a larger gap moves the part: out of the face it meets (on the plane: up when it stands, down when it hangs). */
76
- const outward = 'plane' in j.body0 ? (j.body1.face === 'bottom' ? 1 : -1) : ((j.origin?.facing ?? 'meet') === 'meet' ? 1 : -1) * FACE_SIGN[j.body0.face];
77
- const origin = { ...(j.origin ?? {}) };
78
- const along = local[normal];
79
- if (Math.abs(along) >= 0.5) {
80
- const gap = origin.gap ?? 0;
81
- if (typeof gap !== 'number')
82
- return { kind: 'refused', reason: 'origin-follows', joint: j.id };
83
- origin.gap = round(gap + outward * along, 1);
84
- }
85
- for (const axis of AXES.filter(a => a !== normal)) {
86
- const d = local[axis];
87
- if (Math.abs(d) < 0.5)
88
- continue;
89
- const how = origin.align?.[axis] ?? 'centre';
90
- const was = how === 'centre' ? 0 : typeof how === 'object' && typeof how.mm === 'number' ? how.mm : null;
91
- if (was === null)
92
- return { kind: 'refused', reason: 'origin-follows', joint: j.id };
93
- origin.align = { ...(origin.align ?? {}), [axis]: { mm: round(was + d, 1) } };
94
- }
95
- return { kind: 'joint', joint: { ...j, origin } };
96
- }
97
- //# sourceMappingURL=v3-joint-drag.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"v3-joint-drag.js","sourceRoot":"","sources":["../../client/modeller/v3-joint-drag.ts"],"names":[],"mappings":"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","sourcesContent":["import * as THREE from 'three'\nimport { compileV3Asset } from '@hatiolab/figure-model'\nimport type { V3Asset, V3Joint } from '@hatiolab/figure-model'\n\nimport type { GizmoResult } from './v3-gizmo.js'\nimport type { V3RenderPart } from './v3-renderer.js'\n\n/*\n * A gizmo drag on a part that hangs from a joint edits the joint, not the part (ADR-0093; r3 §3: the modeller changes\n * the inputs of the relation, and never breaks it because a computed place was moved):\n *\n * - turning a part on a revolute or continuous joint sets the joint's value -- the turn about the joint's axis;\n * - sliding a part on a prismatic joint along its axis sets its value;\n * - moving a part any other way moves the joint's origin: the gap along the face it sits on, the offset across it.\n *\n * What would break a relation is refused and said: an origin that follows another part's size, a joint that mimics\n * another, a turn on a joint that does not turn.\n */\n\nexport type JointDrag =\n | { kind: 'value'; input: string; unit: 'deg' | 'mm' | 'ratio'; value: number }\n | { kind: 'joint'; joint: V3Joint }\n | { kind: 'refused'; reason: 'mimic' | 'fixed-turn' | 'origin-follows' | 'turned-slide' | 'off-axis'; joint: string; leader?: string; axis?: string }\n\ntype Axis = 'x' | 'y' | 'z'\nconst AXES: Axis[] = ['x', 'y', 'z']\n/** The axis each face faces along, and which way (as figure-model's `V3_FACES`). */\nconst FACE_AXIS: Record<string, Axis> = { left: 'x', right: 'x', bottom: 'y', top: 'y', back: 'z', front: 'z' }\nconst FACE_SIGN: Record<string, number> = { left: -1, right: 1, bottom: -1, top: 1, back: -1, front: 1 }\n\nconst matrixOf = (r: number[][]) => new THREE.Matrix4().set(r[0]![0]!, r[0]![1]!, r[0]![2]!, 0, r[1]![0]!, r[1]![1]!, r[1]![2]!, 0, r[2]![0]!, r[2]![1]!, r[2]![2]!, 0, 0, 0, 0, 1)\nconst eulerMatrix = (deg: number[]) => new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(...(deg.map(THREE.MathUtils.degToRad) as [number, number, number]), 'XYZ'))\nconst unit = (axis: string) => new THREE.Vector3(/x/i.test(axis) ? 1 : 0, /y/i.test(axis) ? 1 : 0, /z/i.test(axis) ? 1 : 0)\nconst round = (v: number, step: number) => Number((Math.round(v / step) * step).toFixed(Math.max(0, -Math.floor(Math.log10(step)))))\n\n/** The part's own quarter turn in the joint frame, as the record says it. */\nconst turnMatrix = (j: V3Joint) => eulerMatrix(AXES.map(a => j.origin?.turn?.[a] ?? 0))\n\n/** The joint frame's rotation in the figure: the part's rotation now, less its own turn and less the joint's. */\nfunction jointFrameRotation(j: V3Joint, part: V3RenderPart, angle: number): THREE.Matrix4 {\n const now = matrixOf(part.pose.r as number[][])\n const own = turnMatrix(j)\n const turn = j.type === 'revolute' ? new THREE.Matrix4().makeRotationAxis(unit(j.axis!), THREE.MathUtils.degToRad(angle)) : new THREE.Matrix4()\n return now.multiply(own.invert()).multiply(turn.invert())\n}\n\nexport function jointDragOf(asset: V3Asset, id: string, part: V3RenderPart, result: GizmoResult, values: Record<string, number>): JointDrag | null {\n const j = asset.joints?.find(joint => joint.body1.part === id)\n /* A size is the part's own, not the joint's: a scale drag goes to the part's measures (the editor drops the shift). */\n if (!j || result.scale || (!result.rotation && !result.position)) return null\n const stateId = j.state?.id ?? j.id\n const now = values[stateId] ?? asset.stateDefaults[stateId] ?? 0\n const turns = j.type === 'revolute'\n /* A revolute joint without limits turns without end (USD). */\n const endless = turns && j.lowerLimit === undefined && j.upperLimit === undefined\n\n if (result.rotation) {\n if (!turns) return { kind: 'refused', reason: j.type === 'prismatic' ? 'turned-slide' : 'fixed-turn', joint: j.id }\n if (j.mimic) return { kind: 'refused', reason: 'mimic', joint: j.id, leader: j.mimic.joint }\n /* The turn about the joint's axis (the twist of the change): the only turn the joint can make. */\n const before = new THREE.Quaternion().setFromRotationMatrix(matrixOf(part.pose.r as number[][]))\n const after = new THREE.Quaternion().setFromRotationMatrix(eulerMatrix(result.rotation))\n const change = after.multiply(before.invert())\n const axis = unit(j.axis!).applyMatrix4(jointFrameRotation(j, part, now)).normalize()\n const twist = 2 * Math.atan2(change.x * axis.x + change.y * axis.y + change.z * axis.z, change.w)\n /* Turned about another axis: the joint cannot make that turn, and nothing is written for it. */\n const whole = 2 * Math.acos(Math.min(1, Math.abs(change.w)))\n if (Math.abs(twist) < whole * 0.5) return { kind: 'refused', reason: 'off-axis', joint: j.id, axis: j.axis! }\n let value = now + THREE.MathUtils.radToDeg(twist)\n if (endless) value = ((((value + 180) % 360) + 360) % 360) - 180\n else value = Math.min(Math.max(value, j.lowerLimit!), j.upperLimit!)\n return { kind: 'value', input: stateId, unit: 'deg', value: round(value, 0.1) }\n }\n\n const moved = new THREE.Vector3(...result.position!).sub(new THREE.Vector3(...(part.pose.t as [number, number, number])))\n if (moved.length() < 0.5) return null\n const frame = jointFrameRotation(j, part, turns ? now : 0)\n\n if (j.type === 'prismatic') {\n if (j.mimic) return { kind: 'refused', reason: 'mimic', joint: j.id, leader: j.mimic.joint }\n /* How far one unit of the value moves the part along its axis, measured on the figure itself (a ratio travels a length). */\n const along = unit(j.axis!).applyMatrix4(frame).normalize()\n const at = (v: number) => {\n const g = compileV3Asset(asset).evaluate({ ...values, [stateId]: v }).geometry.find(g => g.id === id && 'pose' in g) as { pose: { t: number[] } } | undefined\n return g ? new THREE.Vector3(...(g.pose.t as [number, number, number])).dot(along) : 0\n }\n const step = j.travel ? 0.01 : 1\n const rate = (at(now + step) - at(now)) / step\n if (Math.abs(rate) > 1e-9 && Math.abs(moved.dot(along)) >= moved.length() * 0.7) {\n const value = Math.min(Math.max(now + moved.dot(along) / rate, j.lowerLimit!), j.upperLimit!)\n return { kind: 'value', input: stateId, unit: j.travel ? 'ratio' : 'mm', value: round(value, j.travel ? 0.001 : 1) }\n }\n }\n\n /* Moving the joint's origin: the change in the frame the joint sits in. */\n const local = moved.clone().applyMatrix4(frame.clone().invert())\n const normal: Axis = 'plane' in j.body0 ? 'y' : FACE_AXIS[j.body0.face]!\n /* Which way a larger gap moves the part: out of the face it meets (on the plane: up when it stands, down when it hangs). */\n const outward = 'plane' in j.body0 ? (j.body1.face === 'bottom' ? 1 : -1) : ((j.origin?.facing ?? 'meet') === 'meet' ? 1 : -1) * FACE_SIGN[j.body0.face]!\n const origin: NonNullable<V3Joint['origin']> = { ...(j.origin ?? {}) }\n const along = local[normal]\n if (Math.abs(along) >= 0.5) {\n const gap = origin.gap ?? 0\n if (typeof gap !== 'number') return { kind: 'refused', reason: 'origin-follows', joint: j.id }\n origin.gap = round(gap + outward * along, 1)\n }\n for (const axis of AXES.filter(a => a !== normal)) {\n const d = local[axis]\n if (Math.abs(d) < 0.5) continue\n const how = origin.align?.[axis] ?? 'centre'\n const was = how === 'centre' ? 0 : typeof how === 'object' && typeof how.mm === 'number' ? how.mm : null\n if (was === null) return { kind: 'refused', reason: 'origin-follows', joint: j.id }\n origin.align = { ...(origin.align ?? {}), [axis]: { mm: round(was + d, 1) } }\n }\n return { kind: 'joint', joint: { ...j, origin } }\n}\n"]}
@@ -1,9 +0,0 @@
1
- import type { V3Asset } from '@hatiolab/figure-model';
2
- import type { V3RenderPart } from './v3-renderer.js';
3
- /** The fan's name on the board. It is not a part: not in the list, not saved, not picked. */
4
- export declare const JOINT_FAN = "__joint-fan";
5
- /**
6
- * The fan for the part, as a body to stand on the board, or null when the part does not turn. `values` are the
7
- * inputs the figure is drawn with now (a pose in hand included), so the fan follows the arm as it is posed.
8
- */
9
- export declare function jointFanOf(asset: V3Asset, part: string, values?: Record<string, number>): V3RenderPart | null;