@things-factory/figure-ui 10.1.70 → 10.1.71
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/client/graphql/index.ts +28 -69
- package/client/modeller/figure-v3-parts.ts +16 -136
- package/client/modeller/figure-v3-recipe-catalog.ts +348 -0
- package/client/modeller/v3-editor.ts +25 -5
- package/client/modeller/v3-fields.ts +21 -16
- package/client/modeller/v3-gizmo.ts +1 -1
- package/client/modeller/v3-links.ts +7 -66
- package/client/modeller/v3-palette.ts +3 -0
- package/client/modeller/v3-recipe-source.ts +15 -0
- package/client/modeller/v3-recipes.ts +50 -27
- package/client/pages/figure-v3-modeller-page.ts +16 -13
- package/client/route.ts +0 -4
- package/dist-client/graphql/index.d.ts +11 -18
- package/dist-client/graphql/index.js +18 -59
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-v3-parts.d.ts +5 -12
- package/dist-client/modeller/figure-v3-parts.js +17 -147
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/figure-v3-recipe-catalog.d.ts +36 -0
- package/dist-client/modeller/figure-v3-recipe-catalog.js +382 -0
- package/dist-client/modeller/figure-v3-recipe-catalog.js.map +1 -0
- package/dist-client/modeller/v3-editor.d.ts +8 -3
- package/dist-client/modeller/v3-editor.js +35 -6
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-fields.d.ts +2 -2
- package/dist-client/modeller/v3-fields.js +20 -18
- package/dist-client/modeller/v3-fields.js.map +1 -1
- package/dist-client/modeller/v3-gizmo.js +1 -1
- package/dist-client/modeller/v3-gizmo.js.map +1 -1
- package/dist-client/modeller/v3-links.d.ts +8 -25
- package/dist-client/modeller/v3-links.js +4 -66
- package/dist-client/modeller/v3-links.js.map +1 -1
- package/dist-client/modeller/v3-palette.js +3 -0
- package/dist-client/modeller/v3-palette.js.map +1 -1
- package/dist-client/modeller/v3-recipe-source.d.ts +8 -0
- package/dist-client/modeller/v3-recipe-source.js +14 -0
- package/dist-client/modeller/v3-recipe-source.js.map +1 -0
- package/dist-client/modeller/v3-recipes.d.ts +25 -14
- package/dist-client/modeller/v3-recipes.js +42 -28
- package/dist-client/modeller/v3-recipes.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +5 -3
- package/dist-client/pages/figure-v3-modeller-page.js +16 -13
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +0 -3
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.fui.tsbuildinfo +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/scripts/template-thumbnails.mjs +53 -0
- package/test/browser/build.mjs +3 -3
- package/test/browser/modeller-page.js +25 -3
- package/test/browser/modeller.check.mjs +57 -47
- package/test/browser/thumbnail-page.js +14 -0
- package/test/v3-fields.test.ts +5 -1
- package/test/v3-links.test.ts +12 -0
- package/test/v3-recipes.test.ts +42 -25
- package/things-factory.config.js +0 -1
- package/translations/en.json +38 -23
- package/translations/ja.json +38 -23
- package/translations/ko.json +38 -23
- package/translations/ms.json +38 -23
- package/translations/zh.json +38 -23
- package/client/pages/figure-recipe-page.ts +0 -338
- package/dist-client/pages/figure-recipe-page.d.ts +0 -41
- package/dist-client/pages/figure-recipe-page.js +0 -352
- package/dist-client/pages/figure-recipe-page.js.map +0 -1
|
@@ -23,25 +23,25 @@ export function groupFields(fields, part) {
|
|
|
23
23
|
const singles = own.filter(f => !used.has(f.id)).map(field => ({ kind: 'single', name: nameOf(field.id.slice(prefix.length)), field }));
|
|
24
24
|
return { vectors, singles };
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* The part a copy copies: the member at the end of its path. A row repeats its part and what is fastened to it
|
|
28
|
+
* (ADR-0093, 2026-09-26), so the copies of one row are of several parts -- a roller's and the mark on it.
|
|
29
|
+
*/
|
|
30
|
+
const memberOf = (path) => {
|
|
31
|
+
const last = [...path].reverse().find((step) => typeof step === 'string');
|
|
32
|
+
return last.replace(/\.(placed|value)$/, '').replace(/\.repeat$/, '');
|
|
33
|
+
};
|
|
26
34
|
/**
|
|
27
35
|
* The name a person reads for a body. A repeated body's id is its path through the repeats (`["roller.repeat",0,
|
|
28
|
-
* "roller.
|
|
29
|
-
*
|
|
36
|
+
* "roller.placed"]`), which the list showed as it was; it reads as the part it copies and its place counted from 1 --
|
|
37
|
+
* `roller 1`, and `mark 1` for the mark the first roller carries. Any other id is its own name.
|
|
30
38
|
*/
|
|
31
39
|
export function partLabelOf(id) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let path;
|
|
35
|
-
try {
|
|
36
|
-
path = JSON.parse(id);
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
return id;
|
|
40
|
-
}
|
|
41
|
-
if (!Array.isArray(path) || typeof path[0] !== 'string')
|
|
40
|
+
const path = pathOf(id);
|
|
41
|
+
if (!path)
|
|
42
42
|
return id;
|
|
43
43
|
const places = path.filter((step) => typeof step === 'number').map(n => n + 1);
|
|
44
|
-
return places.length ? `${path
|
|
44
|
+
return places.length ? `${memberOf(path)} ${places.join('·')}` : id;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* The repeat a body is a copy of: its name, how many copies it makes, and which copy this is (counted from 1). Nothing
|
|
@@ -49,14 +49,16 @@ export function partLabelOf(id) {
|
|
|
49
49
|
*/
|
|
50
50
|
export function repeatOf(id, geometry) {
|
|
51
51
|
const path = pathOf(id);
|
|
52
|
+
const paths = (geometry ?? []).map(g => pathOf(g.id ?? '')).filter((p) => !!p);
|
|
52
53
|
if (!path) {
|
|
53
|
-
/* The
|
|
54
|
-
const copies =
|
|
54
|
+
/* The part itself, by its name: how many copies of it the row makes now. */
|
|
55
|
+
const copies = paths.filter(p => memberOf(p) === id).length;
|
|
55
56
|
return copies ? { name: id, count: copies, index: 0 } : null;
|
|
56
57
|
}
|
|
57
|
-
const
|
|
58
|
+
const name = memberOf(path);
|
|
59
|
+
const count = paths.filter(p => p[0] === path[0] && memberOf(p) === name).length;
|
|
58
60
|
const index = path.find((step) => typeof step === 'number');
|
|
59
|
-
return { name
|
|
61
|
+
return { name, count, index: (index ?? 0) + 1 };
|
|
60
62
|
}
|
|
61
63
|
function pathOf(id) {
|
|
62
64
|
if (!id.startsWith('['))
|
|
@@ -76,6 +78,6 @@ function pathOf(id) {
|
|
|
76
78
|
*/
|
|
77
79
|
export function ownerOf(id) {
|
|
78
80
|
const path = pathOf(id);
|
|
79
|
-
return path ?
|
|
81
|
+
return path ? memberOf(path) : id;
|
|
80
82
|
}
|
|
81
83
|
//# sourceMappingURL=v3-fields.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v3-fields.js","sourceRoot":"","sources":["../../client/modeller/v3-fields.ts"],"names":[],"mappings":"AAaA,MAAM,IAAI,GAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;AAM9C,+GAA+G;AAC/G,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAA;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAA4B,EAAE,IAAa;IACrE,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;IACrC,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACpH,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IAC7C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,OAAO,GAAiB,EAAE,CAAA;IAChC,KAAK,MAAM,IAAI,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,MAAM,CAAU,EAAE,CAAC;QAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,MAAM,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CAAA;QACxD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAAE,SAAQ;QAC5C,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;QAC/B,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAE,CAAE,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAE,CAAE,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAE,CAAE,EAAE,EAAE,CAAC,CAAA;IACxH,CAAC;IACD,MAAM,OAAO,GAAiB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAA;IACrJ,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;AAC7B,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"v3-fields.js","sourceRoot":"","sources":["../../client/modeller/v3-fields.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The values a part or the figure is edited by, grouped: position, rotation and size\n * as one row of three cells each (X · Y · Z read together), and the shape's own measures one to a line under their\n * own title. Kept apart from the editor so the grouping is tested without a browser.\n */\nexport interface FieldLike {\n id: string\n unit: string\n role?: string\n value: number\n}\n\nexport type Axis3 = 'x' | 'y' | 'z'\nconst AXES: readonly Axis3[] = ['x', 'y', 'z']\n\nexport type FieldGroup =\n | { kind: 'vector'; name: 'position' | 'rotation' | 'size'; cells: Record<Axis3, FieldLike> }\n | { kind: 'single'; name: string; field: FieldLike }\n\n/** The name a single value goes by: its id without the part, `dimension.` and corner numbers read as words. */\nfunction nameOf(rest: string): string {\n return rest.replace(/^dimension\\./, '')\n}\n\n/**\n * The part's own values (`<part>.…`), or with no part the figure's (`size.x` …). Position, rotation and size come\n * first, each only when all three of its axes are there; the rest follow in the order they are declared.\n */\nexport function groupFields(fields: readonly FieldLike[], part?: string): { vectors: FieldGroup[]; singles: FieldGroup[] } {\n const prefix = part ? `${part}.` : ''\n const own = fields.filter(f => (part ? f.id.startsWith(prefix) : !f.id.includes('.') || /^size\\.[xyz]$/.test(f.id)))\n const byId = new Map(own.map(f => [f.id, f]))\n const used = new Set<string>()\n const vectors: FieldGroup[] = []\n for (const name of ['position', 'rotation', 'size'] as const) {\n const ids = AXES.map(axis => `${prefix}${name}.${axis}`)\n if (!ids.every(id => byId.has(id))) continue\n ids.forEach(id => used.add(id))\n vectors.push({ kind: 'vector', name, cells: { x: byId.get(ids[0]!)!, y: byId.get(ids[1]!)!, z: byId.get(ids[2]!)! } })\n }\n const singles: FieldGroup[] = own.filter(f => !used.has(f.id)).map(field => ({ kind: 'single', name: nameOf(field.id.slice(prefix.length)), field }))\n return { vectors, singles }\n}\n\n/**\n * The part a copy copies: the member at the end of its path. A row repeats its part and what is fastened to it\n * (ADR-0093, 2026-09-26), so the copies of one row are of several parts -- a roller's and the mark on it.\n */\nconst memberOf = (path: unknown[]): string => {\n const last = [...path].reverse().find((step): step is string => typeof step === 'string')!\n return last.replace(/\\.(placed|value)$/, '').replace(/\\.repeat$/, '')\n}\n\n/**\n * The name a person reads for a body. A repeated body's id is its path through the repeats (`[\"roller.repeat\",0,\n * \"roller.placed\"]`), which the list showed as it was; it reads as the part it copies and its place counted from 1 --\n * `roller 1`, and `mark 1` for the mark the first roller carries. Any other id is its own name.\n */\nexport function partLabelOf(id: string): string {\n const path = pathOf(id)\n if (!path) return id\n const places = path.filter((step): step is number => typeof step === 'number').map(n => n + 1)\n return places.length ? `${memberOf(path)} ${places.join('·')}` : id\n}\n\n/**\n * The repeat a body is a copy of: its name, how many copies it makes, and which copy this is (counted from 1). Nothing\n * for a body that is not a copy.\n */\nexport function repeatOf(id: string, geometry: readonly unknown[] | undefined): { name: string; count: number; index: number } | null {\n const path = pathOf(id)\n const paths = (geometry ?? []).map(g => pathOf((g as { id?: string }).id ?? '')).filter((p): p is unknown[] => !!p)\n if (!path) {\n /* The part itself, by its name: how many copies of it the row makes now. */\n const copies = paths.filter(p => memberOf(p) === id).length\n return copies ? { name: id, count: copies, index: 0 } : null\n }\n const name = memberOf(path)\n const count = paths.filter(p => p[0] === path[0] && memberOf(p) === name).length\n const index = path.find((step): step is number => typeof step === 'number')\n return { name, count, index: (index ?? 0) + 1 }\n}\n\nfunction pathOf(id: string): unknown[] | null {\n if (!id.startsWith('[')) return null\n try {\n const path = JSON.parse(id)\n return Array.isArray(path) && typeof path[0] === 'string' ? path : null\n } catch {\n return null\n }\n}\n\n/**\n * The authored part a body belongs to: a repeat's copy belongs to its repeat (`roller`), any other body is its own. The\n * editor selects, lists, hides and names parts by this -- what the author made, not every body the kernel draws\n * (user, 2026-09-24: the conveyor's sixteen rollers were sixteen rows).\n */\nexport function ownerOf(id: string): string {\n const path = pathOf(id)\n return path ? memberOf(path) : id\n}\n\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { v3AttachmentsOf } from '@hatiolab/figure-model';
|
|
2
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']);
|
|
3
|
+
const BOXES = new Set(['box-shape@1', 'rounded-box@1', 'portal-profile@2', 'hollow-box@2']);
|
|
4
4
|
const AXES = ['x', 'y', 'z'];
|
|
5
5
|
export function gizmoReachOf(asset, part) {
|
|
6
6
|
const { nodes, inputs } = asset.document.model;
|
|
@@ -1 +1 @@
|
|
|
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'])\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
|
+
{"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,36 +1,15 @@
|
|
|
1
|
-
import type { V3SizeRange } from '@hatiolab/figure-model';
|
|
1
|
+
import type { V3Follow, V3SizeRange } from '@hatiolab/figure-model';
|
|
2
2
|
import type { V3Asset, V3BindingPlace, V3Face, V3Joint } from '@hatiolab/figure-model';
|
|
3
3
|
export type Axis = 'x' | 'y' | 'z';
|
|
4
4
|
export declare const AXES: readonly Axis[];
|
|
5
|
-
/** What one dimension of a part is worked out from. */
|
|
6
|
-
export type Follow =
|
|
7
|
-
of: 'own';
|
|
8
|
-
}
|
|
9
|
-
/** `times` 1 is the instance size itself; any other is a share of it. */
|
|
10
|
-
| {
|
|
11
|
-
of: 'instance';
|
|
12
|
-
axis: Axis;
|
|
13
|
-
times: number;
|
|
14
|
-
plus?: number;
|
|
15
|
-
} | {
|
|
16
|
-
of: 'part';
|
|
17
|
-
part: string;
|
|
18
|
-
axis: Axis;
|
|
19
|
-
times?: number;
|
|
20
|
-
plus?: number;
|
|
21
|
-
} | {
|
|
22
|
-
of: 'input';
|
|
23
|
-
input: string;
|
|
24
|
-
}
|
|
25
|
-
/** Worked out some other way (an expression) — shown, not offered as a choice. */
|
|
26
|
-
| {
|
|
27
|
-
of: 'expression';
|
|
28
|
-
};
|
|
5
|
+
/** What one dimension of a part is worked out from (figure-model's `V3Follow`). */
|
|
6
|
+
export type Follow = V3Follow;
|
|
29
7
|
/**
|
|
30
8
|
* `link-dimension` routes a part's dimension through `<part>.dimension.<axis>` and points its first argument at the
|
|
31
9
|
* source, directly or through the nodes that scale it. Walking back through those nodes finds the source: the
|
|
32
10
|
* instance size (`size.<axis>`), another part's dimension (`<part>.dimension.<axis>.value`) or a declared input.
|
|
33
11
|
*/
|
|
12
|
+
/** What one measure of a part is worked out from, read back from the graph (figure-model's reader, one copy). */
|
|
34
13
|
export declare function followOf(asset: V3Asset, part: string, axis: Axis): Follow;
|
|
35
14
|
/** Whether the asset declares an instance size on an axis — only then can a dimension follow it. */
|
|
36
15
|
export declare const hasInstanceSize: (asset: V3Asset, axis: Axis) => boolean;
|
|
@@ -75,6 +54,10 @@ export declare const rowOf: (asset: V3Asset, part: string) => {
|
|
|
75
54
|
axis: import("@hatiolab/figure-model").V3JointAxis;
|
|
76
55
|
pitch: import("@hatiolab/figure-model").V3Measure;
|
|
77
56
|
count?: number;
|
|
57
|
+
rise?: {
|
|
58
|
+
axis: import("@hatiolab/figure-model").V3JointAxis;
|
|
59
|
+
mm: number;
|
|
60
|
+
};
|
|
78
61
|
} | undefined;
|
|
79
62
|
/** Where a part sits across the face it is fastened to, on one axis: centred, level with an edge, or a distance off centre. */
|
|
80
63
|
/** `share`: the distance as a share of the figure's size on that axis, so it keeps its place when the figure is resized. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { V3_FACES, v3ShapeFaces, v3PartsExtentOf, compileV3Asset, compileV3Graph, proposeV3Proportion, defaultInstanceSizeRange, foldInstanceSizeRange, v3AttachmentsOf, v3BindingsOf, v3InstanceSizeLimitsOf } from '@hatiolab/figure-model';
|
|
1
|
+
import { V3_FACES, v3ShapeFaces, v3PartsExtentOf, compileV3Asset, compileV3Graph, v3FollowOf, proposeV3Proportion, defaultInstanceSizeRange, foldInstanceSizeRange, v3AttachmentsOf, v3BindingsOf, v3InstanceSizeLimitsOf } from '@hatiolab/figure-model';
|
|
2
2
|
export const AXES = ['x', 'y', 'z'];
|
|
3
3
|
const modelOf = (asset) => asset.document.model;
|
|
4
4
|
/**
|
|
@@ -6,71 +6,9 @@ const modelOf = (asset) => asset.document.model;
|
|
|
6
6
|
* source, directly or through the nodes that scale it. Walking back through those nodes finds the source: the
|
|
7
7
|
* instance size (`size.<axis>`), another part's dimension (`<part>.dimension.<axis>.value`) or a declared input.
|
|
8
8
|
*/
|
|
9
|
+
/** What one measure of a part is worked out from, read back from the graph (figure-model's reader, one copy). */
|
|
9
10
|
export function followOf(asset, part, axis) {
|
|
10
|
-
|
|
11
|
-
/*
|
|
12
|
-
A link routes the dimension through `<part>.dimension.<axis>`; before any link the shape reads its measure directly,
|
|
13
|
-
and that may already be worked out (ROBOT_ARM's flange fills what is left).
|
|
14
|
-
*/
|
|
15
|
-
const node = m.nodes.find(n => n.id === `${part}.dimension.${axis}`);
|
|
16
|
-
const start = node ? node.args[0] : m.nodes.find(n => n.id === `${part}.shape`)?.args[AXES.indexOf(axis)];
|
|
17
|
-
if (!start || m.constants.some(c => c.id === start))
|
|
18
|
-
return { of: 'own' };
|
|
19
|
-
const writerOf = (ref) => m.nodes.find(n => Object.values(n.outputs).includes(ref));
|
|
20
|
-
const seen = new Set();
|
|
21
|
-
const constant = (ref) => m.constants.find(c => c.id === ref);
|
|
22
|
-
/* The constant factors met on the way back: a share of the instance size is `size × share`. */
|
|
23
|
-
let times = 1;
|
|
24
|
-
/*
|
|
25
|
-
A link the author made is `source × times + plus` (link-dimension): its outermost sum with a number is the plus --
|
|
26
|
-
a leaf the opening's width less its gaps. Deeper sums, or a measure no link routes, read as worked out elsewhere.
|
|
27
|
-
*/
|
|
28
|
-
let plus = 0;
|
|
29
|
-
let at = start;
|
|
30
|
-
const outer = node && writerOf(start);
|
|
31
|
-
if (outer?.op === 'add@1') {
|
|
32
|
-
const numbers = outer.args.filter(arg => constant(arg));
|
|
33
|
-
const onward = outer.args.filter(arg => !constant(arg));
|
|
34
|
-
if (numbers.length === 1 && onward.length === 1) {
|
|
35
|
-
plus = constant(numbers[0]).value;
|
|
36
|
-
at = onward[0];
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
const withPlus = (f) => (plus ? { ...f, plus } : f);
|
|
40
|
-
const walk = (ref) => {
|
|
41
|
-
if (seen.has(ref))
|
|
42
|
-
return { of: 'expression' };
|
|
43
|
-
seen.add(ref);
|
|
44
|
-
const instance = /^size\.([xyz])$/.exec(ref);
|
|
45
|
-
if (instance)
|
|
46
|
-
return withPlus({ of: 'instance', axis: instance[1], times: Math.round(times * 1e6) / 1e6 });
|
|
47
|
-
const other = /^(.+)\.dimension\.([xyz])\.value$/.exec(ref);
|
|
48
|
-
if (other)
|
|
49
|
-
return withPlus({ of: 'part', part: other[1], axis: other[2], ...(times !== 1 ? { times: Math.round(times * 1e6) / 1e6 } : {}) });
|
|
50
|
-
const input = m.inputs.find(i => i.id === ref);
|
|
51
|
-
if (input)
|
|
52
|
-
return ref.startsWith(`${part}.`) ? { of: 'own' } : { of: 'input', input: ref };
|
|
53
|
-
const writer = writerOf(ref);
|
|
54
|
-
if (!writer)
|
|
55
|
-
return { of: 'expression' };
|
|
56
|
-
/* Follow the one argument that is not a constant factor. */
|
|
57
|
-
const onward = writer.args.filter(arg => !constant(arg));
|
|
58
|
-
if (onward.length !== 1)
|
|
59
|
-
return { of: 'expression' };
|
|
60
|
-
/*
|
|
61
|
-
Only a product is a share of its source. A sum with a number is not (「fill what is left」 is the size plus a negative
|
|
62
|
-
gap -- ROBOT_ARM's tool flange), nor anything else; those read as worked out some other way.
|
|
63
|
-
*/
|
|
64
|
-
const factors = writer.args.filter(arg => constant(arg)).map(arg => constant(arg).value);
|
|
65
|
-
if (writer.op === 'mul@1')
|
|
66
|
-
times *= factors.reduce((a, b) => a * b, 1);
|
|
67
|
-
else if (writer.op === 'div@1' && writer.args[0] === onward[0] && factors.length === 1 && factors[0] !== 0)
|
|
68
|
-
times /= factors[0];
|
|
69
|
-
else if (factors.some(v => v !== 0) || !['mul@1', 'div@1', 'add@1', 'sub@1'].includes(writer.op))
|
|
70
|
-
return { of: 'expression' };
|
|
71
|
-
return walk(onward[0]);
|
|
72
|
-
};
|
|
73
|
-
return walk(at);
|
|
11
|
+
return v3FollowOf(asset, part, axis);
|
|
74
12
|
}
|
|
75
13
|
/** Whether the asset declares an instance size on an axis — only then can a dimension follow it. */
|
|
76
14
|
export const hasInstanceSize = (asset, axis) => modelOf(asset).inputs.some(i => i.id === `size.${axis}` && i.role === 'design');
|
|
@@ -103,7 +41,7 @@ export function followableAxes(asset, part) {
|
|
|
103
41
|
if (!shape)
|
|
104
42
|
return [];
|
|
105
43
|
/* A door frame's first three measures are its width, height and depth, as a box's (figure-model portal-profile@2). */
|
|
106
|
-
if (shape.op === 'box-shape@1' || shape.op === 'rounded-box@1' || shape.op === 'portal-profile@2')
|
|
44
|
+
if (shape.op === 'box-shape@1' || shape.op === 'rounded-box@1' || shape.op === 'portal-profile@2' || shape.op === 'hollow-box@2')
|
|
107
45
|
return [...AXES];
|
|
108
46
|
if (shape.op === 'cylinder-shape@1')
|
|
109
47
|
return ['x', 'y'];
|