@things-factory/figure-ui 10.1.79 → 10.1.82
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/figure-assistant.ts +2 -2
- package/client/graphql/index.ts +99 -10
- package/client/graphql/reached.ts +25 -0
- package/client/graphql/store.ts +27 -4
- package/client/modeller/ai-proposal.ts +74 -9
- package/client/modeller/align.ts +2 -3
- package/client/modeller/canvas.ts +18 -5
- package/client/modeller/command-preview.ts +113 -0
- package/client/modeller/editor.ts +826 -54
- package/client/modeller/fields.ts +12 -4
- package/client/modeller/figure-facts.ts +2 -2
- package/client/modeller/figure-first-save.ts +36 -14
- package/client/modeller/figure-history-panel.ts +133 -14
- package/client/modeller/figure-links.ts +385 -68
- package/client/modeller/figure-parts.ts +56 -2
- package/client/modeller/figure-preview.ts +19 -1
- package/client/modeller/figure-properties.ts +3 -3
- package/client/modeller/figure-revert-choice.ts +49 -0
- package/client/modeller/first-save.ts +16 -0
- package/client/modeller/gizmo.ts +0 -16
- package/client/modeller/links.ts +142 -15
- package/client/modeller/motion-ends.ts +83 -0
- package/client/modeller/occupancy.ts +28 -2
- package/client/modeller/product-session.ts +154 -18
- package/client/modeller/refusals.ts +15 -0
- package/client/modeller/revert-request.ts +26 -0
- package/client/modeller/side-panel-styles.ts +12 -0
- package/client/modeller/stage-model.ts +25 -3
- package/client/pages/figure-modeller-page.ts +68 -29
- package/dist-client/figure-assistant.js +2 -2
- package/dist-client/figure-assistant.js.map +1 -1
- package/dist-client/graphql/index.d.ts +39 -2
- package/dist-client/graphql/index.js +63 -9
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/graphql/reached.d.ts +10 -0
- package/dist-client/graphql/reached.js +28 -0
- package/dist-client/graphql/reached.js.map +1 -0
- package/dist-client/graphql/store.js +27 -4
- package/dist-client/graphql/store.js.map +1 -1
- package/dist-client/modeller/ai-proposal.d.ts +31 -2
- package/dist-client/modeller/ai-proposal.js +65 -6
- package/dist-client/modeller/ai-proposal.js.map +1 -1
- package/dist-client/modeller/align.d.ts +1 -1
- package/dist-client/modeller/align.js +0 -2
- package/dist-client/modeller/align.js.map +1 -1
- package/dist-client/modeller/canvas.d.ts +11 -0
- package/dist-client/modeller/canvas.js +33 -4
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/clips.d.ts +1 -1
- package/dist-client/modeller/command-preview.d.ts +35 -0
- package/dist-client/modeller/command-preview.js +95 -0
- package/dist-client/modeller/command-preview.js.map +1 -0
- package/dist-client/modeller/editor.d.ts +113 -1
- package/dist-client/modeller/editor.js +850 -50
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fields.d.ts +3 -2
- package/dist-client/modeller/fields.js +10 -3
- package/dist-client/modeller/fields.js.map +1 -1
- package/dist-client/modeller/figure-facts.js +2 -2
- package/dist-client/modeller/figure-facts.js.map +1 -1
- package/dist-client/modeller/figure-first-save.d.ts +3 -4
- package/dist-client/modeller/figure-first-save.js +41 -14
- package/dist-client/modeller/figure-first-save.js.map +1 -1
- package/dist-client/modeller/figure-history-panel.d.ts +31 -0
- package/dist-client/modeller/figure-history-panel.js +130 -14
- package/dist-client/modeller/figure-history-panel.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +51 -1
- package/dist-client/modeller/figure-links.js +392 -72
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-parts.d.ts +7 -0
- package/dist-client/modeller/figure-parts.js +55 -2
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +6 -0
- package/dist-client/modeller/figure-preview.js +22 -1
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-properties.js +3 -3
- package/dist-client/modeller/figure-properties.js.map +1 -1
- package/dist-client/modeller/figure-revert-choice.d.ts +17 -0
- package/dist-client/modeller/figure-revert-choice.js +59 -0
- package/dist-client/modeller/figure-revert-choice.js.map +1 -0
- package/dist-client/modeller/first-save.d.ts +7 -0
- package/dist-client/modeller/first-save.js +17 -0
- package/dist-client/modeller/first-save.js.map +1 -1
- package/dist-client/modeller/gizmo.d.ts +0 -6
- package/dist-client/modeller/gizmo.js +0 -18
- package/dist-client/modeller/gizmo.js.map +1 -1
- package/dist-client/modeller/links.d.ts +49 -4
- package/dist-client/modeller/links.js +141 -15
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/motion-ends.d.ts +12 -0
- package/dist-client/modeller/motion-ends.js +65 -0
- package/dist-client/modeller/motion-ends.js.map +1 -0
- package/dist-client/modeller/occupancy.d.ts +14 -1
- package/dist-client/modeller/occupancy.js +14 -1
- package/dist-client/modeller/occupancy.js.map +1 -1
- package/dist-client/modeller/product-session.d.ts +43 -2
- package/dist-client/modeller/product-session.js +146 -12
- package/dist-client/modeller/product-session.js.map +1 -1
- package/dist-client/modeller/refusals.d.ts +13 -0
- package/dist-client/modeller/refusals.js +16 -0
- package/dist-client/modeller/refusals.js.map +1 -1
- package/dist-client/modeller/revert-request.d.ts +16 -0
- package/dist-client/modeller/revert-request.js +11 -0
- package/dist-client/modeller/revert-request.js.map +1 -0
- package/dist-client/modeller/side-panel-styles.js +12 -0
- package/dist-client/modeller/side-panel-styles.js.map +1 -1
- package/dist-client/modeller/stage-model.d.ts +18 -2
- package/dist-client/modeller/stage-model.js +21 -3
- package/dist-client/modeller/stage-model.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +8 -1
- package/dist-client/pages/figure-modeller-page.js +72 -34
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/test/ai-proposal.test.ts +2 -2
- package/test/align.test.ts +50 -5
- package/test/browser/acceptance-page.js +187 -0
- package/test/browser/build.mjs +5 -1
- package/test/browser/revert-page.js +47 -0
- package/test/browser/serve-acceptance.mjs +85 -0
- package/test/browser/serve-revert.mjs +119 -0
- package/test/clip-ai-proposal.test.ts +104 -0
- package/test/clip-consent.test.ts +88 -0
- package/test/command-preview.test.ts +288 -0
- package/test/fields.test.ts +4 -3
- package/test/figure-assistant-surface.test.ts +4 -1
- package/test/first-minute.test.ts +96 -0
- package/test/first-save-suggest.test.ts +42 -0
- package/test/gizmo.test.ts +20 -16
- package/test/joint-fan.test.ts +2 -1
- package/test/layout.test.ts +9 -2
- package/test/links.test.ts +7 -4
- package/test/proposal-record.test.ts +126 -0
- package/test/release-entry.test.ts +28 -0
- package/test/release-panel.test.ts +3 -2
- package/test/resize-with-placement.test.ts +225 -0
- package/test/revert-choice.test.ts +39 -0
- package/test/revert-keeping.test.ts +102 -0
- package/test/save-landed.test.ts +198 -0
- package/translations/en.json +222 -10
- package/translations/ja.json +223 -11
- package/translations/ko.json +223 -11
- package/translations/ms.json +222 -10
- package/translations/zh.json +223 -11
|
@@ -63,10 +63,4 @@ export interface GizmoResult {
|
|
|
63
63
|
* tenth (a drag otherwise stores 722.4180046035418 mm). A size never goes below 1 mm.
|
|
64
64
|
*/
|
|
65
65
|
export declare function gizmoChanges(part: string, reach: GizmoReach, result: GizmoResult, sizes: Record<string, number>): FigureInputChange[];
|
|
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>): FigureInputChange[];
|
|
72
66
|
export {};
|
|
@@ -90,22 +90,4 @@ export function gizmoChanges(part, reach, result, sizes) {
|
|
|
90
90
|
}
|
|
91
91
|
return changes;
|
|
92
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
93
|
//# sourceMappingURL=gizmo.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gizmo.js","sourceRoot":"","sources":["../../client/modeller/gizmo.ts"],"names":[],"mappings":"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","sourcesContent":["import { figureAttachmentsOf } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureInputChange } from '@hatiolab/figure-model'\n\n/**\n * What the gizmo may write for a part: move · turn · size, on a 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 * `addFigureBox`, `<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 figureAttachmentsOf(asset as unknown as FigureAsset)[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>): FigureInputChange[] {\n const changes: FigureInputChange[] = []\n const put = (input: string, unit: FigureInputChange['unit'], value: number) =>\n changes.push({ kind: 'set-design', input, unit, value } as FigureInputChange)\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>): FigureInputChange[] {\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 FigureInputChange]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"gizmo.js","sourceRoot":"","sources":["../../client/modeller/gizmo.ts"],"names":[],"mappings":"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","sourcesContent":["import { figureAttachmentsOf } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureInputChange } from '@hatiolab/figure-model'\n\n/**\n * What the gizmo may write for a part: move · turn · size, on a 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 * `addFigureBox`, `<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 figureAttachmentsOf(asset as unknown as FigureAsset)[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>): FigureInputChange[] {\n const changes: FigureInputChange[] = []\n const put = (input: string, unit: FigureInputChange['unit'], value: number) =>\n changes.push({ kind: 'set-design', input, unit, value } as FigureInputChange)\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"]}
|
|
@@ -28,12 +28,43 @@ export declare function heldBy(asset: FigureAsset, part: string): {
|
|
|
28
28
|
/** Whether a dimension of this part can follow something — box-shaped, whose three sizes are X, Y and Z. */
|
|
29
29
|
export declare function canFollow(asset: FigureAsset, part: string): boolean;
|
|
30
30
|
/**
|
|
31
|
-
* The axes whose size can follow something
|
|
32
|
-
*
|
|
31
|
+
* The axes whose size can follow something: figure-model's one list (`figureFollowableAxesOf`), which follow-size and
|
|
32
|
+
* the resize plan ask too -- the screen does not keep a list of its own (designer 2026-09-27).
|
|
33
33
|
*/
|
|
34
34
|
export declare function followableAxes(asset: FigureAsset, part: string): Axis[];
|
|
35
|
+
/**
|
|
36
|
+
* The directions "follow the size, attached" passes to figure-model (designer 2026-09-27):
|
|
37
|
+
* - `chosen`: what the author ticked, and the ones already following, which stay;
|
|
38
|
+
* - `suggested`: a part the author has not chosen for yet and that follows nothing -- every direction, said as
|
|
39
|
+
* "all three" on the button, never as a choice the author made;
|
|
40
|
+
* - `none`: the author set every direction back to fixed, or unticked them all -- nothing is taken for all.
|
|
41
|
+
* `pending` is the author's ticks for this part in this screen, when they have ticked or fixed anything.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resizeAxesOf(asset: FigureAsset, part: string, pending?: readonly Axis[]): {
|
|
44
|
+
axes: Axis[];
|
|
45
|
+
basis: 'chosen' | 'suggested' | 'none';
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The selected part in three short lines, for the head of its panel (designer 2026-09-27, B): what it is, how its
|
|
49
|
+
* size answers the figure's, and what holds it -- 「box · 상자 / 너비는 도형을 따라감 / 아랫면을 바닥에 유지 중」.
|
|
50
|
+
*/
|
|
51
|
+
export declare function partSummaryOf(asset: FigureAsset, part: string, say: (key: string, params?: Record<string, string>) => string): {
|
|
52
|
+
kind: string;
|
|
53
|
+
size: string;
|
|
54
|
+
relation: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* A part's own field in the words its row uses -- 「위치 Y」, 「크기 X」, and the turn as one 「회전」 -- so a field the plan
|
|
58
|
+
* replaces, or one only the room reads, is named as the author knows it, not by its id.
|
|
59
|
+
*/
|
|
60
|
+
export declare function fieldWordsOf(id: string, say: (key: string) => string): string;
|
|
35
61
|
/** What a part's own measure on an axis is now (a box's side, a cylinder's radius or length), at the design as it stands. */
|
|
36
62
|
export declare function dimensionNow(asset: FigureAsset, part: string, axis: Axis): number | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* How long a part is along one axis now, as it is drawn: the shape's own measure through figure-model's provider (a
|
|
65
|
+
* cylinder's width is twice its radius), read after any rule it follows -- so a linked measure still says its size.
|
|
66
|
+
*/
|
|
67
|
+
export declare function measureNow(asset: FigureAsset, part: string, axis: Axis): number | undefined;
|
|
37
68
|
/**
|
|
38
69
|
* The flat faces a part's shape declares, by name -- figure-model's shape definitions are the one place they are
|
|
39
70
|
* written (a portal's opening sides as well as its outer faces). A sphere has none.
|
|
@@ -49,6 +80,11 @@ export declare function canFasten(asset: FigureAsset, part: string): boolean;
|
|
|
49
80
|
* already repeats -- the command would refuse those, so they are not offered.
|
|
50
81
|
*/
|
|
51
82
|
export declare function rowAxesOf(asset: FigureAsset, part: string): Axis[];
|
|
83
|
+
/**
|
|
84
|
+
* Why a part cannot be made into a row here, in the terms `rowAxesOf` refuses it: said, not hidden (ADR-0097 §5.5).
|
|
85
|
+
* Nothing when it can, or when it already is a row (that row is shown where it stands).
|
|
86
|
+
*/
|
|
87
|
+
export declare function rowBlockOf(asset: FigureAsset, part: string): 'unfastened' | 'on-plane' | 'holds-parts' | undefined;
|
|
52
88
|
/** The row a part is made into, as its fastening's record keeps it. */
|
|
53
89
|
export declare const rowOf: (asset: FigureAsset, part: string) => {
|
|
54
90
|
axis: import("@hatiolab/figure-model").FigureJointAxis;
|
|
@@ -61,7 +97,8 @@ export declare const rowOf: (asset: FigureAsset, part: string) => {
|
|
|
61
97
|
} | undefined;
|
|
62
98
|
/** Where a part sits across the face it is fastened to, on one axis: centred, level with an edge, or a distance off centre. */
|
|
63
99
|
/** `share`: the distance as a share of the figure's size on that axis, so it keeps its place when the figure is resized. */
|
|
64
|
-
|
|
100
|
+
/** `input`: the place across the face follows one of the part's position fields -- as "follow size, attached" keeps it (ADR-0097). */
|
|
101
|
+
export type Alignment = 'centre' | 'min' | 'max' | 'mm' | 'share' | 'input';
|
|
65
102
|
/**
|
|
66
103
|
* The axes across the face a part is fastened to — the two a fastening does not set by itself. Edges to line up with
|
|
67
104
|
* are offered where both parts have their two outer faces on that axis; the plane the figure is mounted on has none,
|
|
@@ -125,18 +162,24 @@ export declare function placeKeptOf(asset: FigureAsset, part: string, target: {
|
|
|
125
162
|
} | 'plane', twisted?: boolean): {
|
|
126
163
|
align: Partial<Record<Axis, Alignment>>;
|
|
127
164
|
alignMm: Partial<Record<Axis, number>>;
|
|
165
|
+
alignInput: Partial<Record<Axis, string>>;
|
|
128
166
|
};
|
|
129
167
|
/** The `align` of an attach command from the form's choices: only the axes across that face, and centred ones left out. */
|
|
130
168
|
export declare function alignOf(asset: FigureAsset, part: string, target: {
|
|
131
169
|
part: string;
|
|
132
170
|
face: string;
|
|
133
|
-
} | 'plane', how: Partial<Record<Axis, Alignment>>, mm: Partial<Record<Axis, number>>, twisted?: boolean): Partial<Record<Axis, "min" | "max" | {
|
|
171
|
+
} | 'plane', how: Partial<Record<Axis, Alignment>>, mm: Partial<Record<Axis, number>>, twisted?: boolean, inputs?: Partial<Record<Axis, string>>): Partial<Record<Axis, "min" | "max" | {
|
|
134
172
|
mm: number | {
|
|
135
173
|
from: {
|
|
136
174
|
of: "instance";
|
|
137
175
|
axis: Axis;
|
|
138
176
|
};
|
|
139
177
|
times: number;
|
|
178
|
+
} | {
|
|
179
|
+
from: {
|
|
180
|
+
of: "input";
|
|
181
|
+
input: string;
|
|
182
|
+
};
|
|
140
183
|
};
|
|
141
184
|
}>> | undefined;
|
|
142
185
|
/**
|
|
@@ -265,3 +308,5 @@ export declare function rangeDiffers(a: FigureSizeRange, b: FigureSizeRange): bo
|
|
|
265
308
|
* architect's ruling 2026-09-24); the modeller proposes it, the author declares it.
|
|
266
309
|
*/
|
|
267
310
|
export declare function turnsAboutAJoint(asset: FigureAsset): boolean;
|
|
311
|
+
/** The joint made still: its fastening, its place and its row kept, its motion gone. */
|
|
312
|
+
export declare function fixedJointOf(joint: FigureJoint): FigureJoint;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FIGURE_FACES, figureShapeFaces, figurePartsExtentOf, compileFigureAsset, compileFigureGraph, figureFollowOf, proposeFigureProportion, defaultInstanceSizeRange, foldInstanceSizeRange, figureAttachmentsOf, figureBindingsOf, figureInstanceSizeLimitsOf } from '@hatiolab/figure-model';
|
|
1
|
+
import { FIGURE_FACES, figureShapeFaces, figurePartsExtentOf, compileFigureAsset, compileFigureGraph, figureFollowOf, proposeFigureProportion, defaultInstanceSizeRange, foldInstanceSizeRange, figureAttachmentsOf, figureBindingsOf, figureInstanceSizeLimitsOf, figureFollowableAxesOf, figureShapeProviderOf } from '@hatiolab/figure-model';
|
|
2
2
|
export const AXES = ['x', 'y', 'z'];
|
|
3
3
|
const modelOf = (asset) => asset.document.model;
|
|
4
4
|
/**
|
|
@@ -33,19 +33,88 @@ export function canFollow(asset, part) {
|
|
|
33
33
|
return followableAxes(asset, part).length > 0;
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
* The axes whose size can follow something
|
|
37
|
-
*
|
|
36
|
+
* The axes whose size can follow something: figure-model's one list (`figureFollowableAxesOf`), which follow-size and
|
|
37
|
+
* the resize plan ask too -- the screen does not keep a list of its own (designer 2026-09-27).
|
|
38
38
|
*/
|
|
39
39
|
export function followableAxes(asset, part) {
|
|
40
40
|
const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
return shape ? figureFollowableAxesOf(shape.op) : [];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The directions "follow the size, attached" passes to figure-model (designer 2026-09-27):
|
|
45
|
+
* - `chosen`: what the author ticked, and the ones already following, which stay;
|
|
46
|
+
* - `suggested`: a part the author has not chosen for yet and that follows nothing -- every direction, said as
|
|
47
|
+
* "all three" on the button, never as a choice the author made;
|
|
48
|
+
* - `none`: the author set every direction back to fixed, or unticked them all -- nothing is taken for all.
|
|
49
|
+
* `pending` is the author's ticks for this part in this screen, when they have ticked or fixed anything.
|
|
50
|
+
*/
|
|
51
|
+
export function resizeAxesOf(asset, part, pending) {
|
|
52
|
+
const editable = followableAxes(asset, part);
|
|
53
|
+
const following = editable.filter(axis => {
|
|
54
|
+
const now = followOf(asset, part, axis);
|
|
55
|
+
return now.of === 'instance' && now.axis === axis;
|
|
56
|
+
});
|
|
57
|
+
if (pending) {
|
|
58
|
+
const axes = editable.filter(axis => pending.includes(axis) || following.includes(axis));
|
|
59
|
+
return { axes, basis: axes.length ? 'chosen' : 'none' };
|
|
60
|
+
}
|
|
61
|
+
if (following.length)
|
|
62
|
+
return { axes: following, basis: 'chosen' };
|
|
63
|
+
return { axes: editable, basis: editable.length ? 'suggested' : 'none' };
|
|
64
|
+
}
|
|
65
|
+
const KIND_OF_SHAPE = {
|
|
66
|
+
'box-shape@1': 'cube',
|
|
67
|
+
'rounded-box@1': 'rounded-box',
|
|
68
|
+
'rounded-box@2': 'rounded-box',
|
|
69
|
+
'hollow-box@1': 'hollow-box',
|
|
70
|
+
'hollow-box@2': 'hollow-box',
|
|
71
|
+
'portal-profile@2': 'portal',
|
|
72
|
+
'cylinder-shape@1': 'cylinder',
|
|
73
|
+
'cylinder-shape@2': 'cylinder',
|
|
74
|
+
'sphere-shape@1': 'sphere',
|
|
75
|
+
'frustum-shape@1': 'frustum',
|
|
76
|
+
'polygon-shape@1': 'polygon'
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The selected part in three short lines, for the head of its panel (designer 2026-09-27, B): what it is, how its
|
|
80
|
+
* size answers the figure's, and what holds it -- 「box · 상자 / 너비는 도형을 따라감 / 아랫면을 바닥에 유지 중」.
|
|
81
|
+
*/
|
|
82
|
+
export function partSummaryOf(asset, part, say) {
|
|
83
|
+
const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`);
|
|
84
|
+
const kind = shape && KIND_OF_SHAPE[shape.op] ? say(`figure.label.primitive-${KIND_OF_SHAPE[shape.op]}`) : '';
|
|
85
|
+
const editable = followableAxes(asset, part);
|
|
86
|
+
const round = editable.length === 2;
|
|
87
|
+
const word = (axis) => say(round && axis === 'x' ? 'figure.label.resize-diameter' : `figure.label.resize-${axis}`);
|
|
88
|
+
const following = editable.filter(axis => {
|
|
89
|
+
const now = followOf(asset, part, axis);
|
|
90
|
+
return now.of === 'instance' && now.axis === axis;
|
|
91
|
+
});
|
|
92
|
+
const ruled = editable.filter(axis => followOf(asset, part, axis).of !== 'own' && !following.includes(axis));
|
|
93
|
+
const size = [
|
|
94
|
+
following.length ? say('figure.text.summary-follows', { axes: following.map(word).join(' · ') }) : '',
|
|
95
|
+
ruled.length ? say('figure.text.summary-ruled', { axes: ruled.map(word).join(' · ') }) : ''
|
|
96
|
+
].filter(Boolean).join(' / ') || say('figure.text.summary-size-fixed');
|
|
97
|
+
const joint = asset.joints?.find(j => j.body1.part === part);
|
|
98
|
+
let relation = say('figure.text.summary-free');
|
|
99
|
+
if (joint) {
|
|
100
|
+
const face = say(`figure.label.face-${joint.body1.face}`);
|
|
101
|
+
const gap = typeof joint.origin?.gap === 'number' ? joint.origin.gap : 0;
|
|
102
|
+
relation =
|
|
103
|
+
'plane' in joint.body0
|
|
104
|
+
? say('figure.text.held-on-plane', { face, place: say(`figure.label.resize-place-${asset.placement ?? 'floor'}`), gap: String(gap) })
|
|
105
|
+
: say('figure.text.held-on-part', { face, other: joint.body0.part, otherFace: say(`figure.label.face-${joint.body0.face}`) });
|
|
106
|
+
}
|
|
107
|
+
return { kind, size, relation };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A part's own field in the words its row uses -- 「위치 Y」, 「크기 X」, and the turn as one 「회전」 -- so a field the plan
|
|
111
|
+
* replaces, or one only the room reads, is named as the author knows it, not by its id.
|
|
112
|
+
*/
|
|
113
|
+
export function fieldWordsOf(id, say) {
|
|
114
|
+
const own = /\.(position|rotation|size)\.([xyz])$/.exec(id);
|
|
115
|
+
if (!own)
|
|
116
|
+
return id;
|
|
117
|
+
return own[1] === 'rotation' ? say('figure.label.group-rotation') : `${say(`figure.label.group-${own[1]}`)} ${own[2].toUpperCase()}`;
|
|
49
118
|
}
|
|
50
119
|
/** What a part's own measure on an axis is now (a box's side, a cylinder's radius or length), at the design as it stands. */
|
|
51
120
|
export function dimensionNow(asset, part, axis) {
|
|
@@ -62,6 +131,24 @@ export function dimensionNow(asset, part, axis) {
|
|
|
62
131
|
return undefined;
|
|
63
132
|
}
|
|
64
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* How long a part is along one axis now, as it is drawn: the shape's own measure through figure-model's provider (a
|
|
136
|
+
* cylinder's width is twice its radius), read after any rule it follows -- so a linked measure still says its size.
|
|
137
|
+
*/
|
|
138
|
+
export function measureNow(asset, part, axis) {
|
|
139
|
+
const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`);
|
|
140
|
+
const edit = shape && figureShapeProviderOf(shape.op)?.edit[axis];
|
|
141
|
+
if (!shape || !edit || 'refused' in edit)
|
|
142
|
+
return undefined;
|
|
143
|
+
try {
|
|
144
|
+
const values = compileFigureGraph(asset.document.model).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
|
|
145
|
+
const value = values[shape.args[edit.arg]];
|
|
146
|
+
return typeof value === 'number' && Number.isFinite(value) ? value * edit.k : undefined;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
65
152
|
/**
|
|
66
153
|
* The flat faces a part's shape declares, by name -- figure-model's shape definitions are the one place they are
|
|
67
154
|
* written (a portal's opening sides as well as its outer faces). A sphere has none.
|
|
@@ -88,12 +175,30 @@ const OUTER = { x: ['left', 'right'], y: ['bottom', 'top'], z: ['back', 'front']
|
|
|
88
175
|
export function rowAxesOf(asset, part) {
|
|
89
176
|
const joints = asset.joints ?? [];
|
|
90
177
|
const j = joints.find(o => o.body1.part === part);
|
|
91
|
-
|
|
178
|
+
/* A part that moves may be a row too: every copy on the one control, from its own place (designer 2026-09-27). */
|
|
179
|
+
if (!j || j.repeat || !('part' in j.body0))
|
|
92
180
|
return [];
|
|
93
181
|
if (joints.some(o => 'part' in o.body0 && o.body0.part === part))
|
|
94
182
|
return [];
|
|
95
183
|
return AXES.filter(axis => axis !== FIGURE_FACES[j.body1.face]);
|
|
96
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Why a part cannot be made into a row here, in the terms `rowAxesOf` refuses it: said, not hidden (ADR-0097 §5.5).
|
|
187
|
+
* Nothing when it can, or when it already is a row (that row is shown where it stands).
|
|
188
|
+
*/
|
|
189
|
+
export function rowBlockOf(asset, part) {
|
|
190
|
+
const joints = asset.joints ?? [];
|
|
191
|
+
const j = joints.find(o => o.body1.part === part);
|
|
192
|
+
if (!j)
|
|
193
|
+
return 'unfastened';
|
|
194
|
+
if (j.repeat)
|
|
195
|
+
return undefined;
|
|
196
|
+
if (!('part' in j.body0))
|
|
197
|
+
return 'on-plane';
|
|
198
|
+
if (joints.some(o => 'part' in o.body0 && o.body0.part === part))
|
|
199
|
+
return 'holds-parts';
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
97
202
|
/** The row a part is made into, as its fastening's record keeps it. */
|
|
98
203
|
export const rowOf = (asset, part) => asset.joints?.find(o => o.body1.part === part)?.repeat;
|
|
99
204
|
/**
|
|
@@ -106,7 +211,7 @@ export const rowOf = (asset, part) => asset.joints?.find(o => o.body1.part === p
|
|
|
106
211
|
* on it by its bottom or hangs from it by its top; a wall is Z = 0 and a part is fastened to it by its back.
|
|
107
212
|
*/
|
|
108
213
|
export function mountingPlaneOf(asset) {
|
|
109
|
-
return asset.
|
|
214
|
+
return asset.placement === 'wall' ? { normal: 'z', faces: ['back'] } : { normal: 'y', faces: ['bottom', 'top'] };
|
|
110
215
|
}
|
|
111
216
|
export function alignAxesOf(asset, part, target, twisted = false) {
|
|
112
217
|
/* The plane has no edges, but a figure that declares its size has sides on it (figure-model 0.1.64). */
|
|
@@ -175,6 +280,12 @@ export function fasteningOf(asset, part) {
|
|
|
175
280
|
kept.align[axis] = 'mm';
|
|
176
281
|
kept.alignMm[axis] = how.mm;
|
|
177
282
|
}
|
|
283
|
+
else if (how?.mm?.from?.of === 'input') {
|
|
284
|
+
/* Following a position field: read as that, so fastening again keeps the field and does not fix its number. */
|
|
285
|
+
kept.align[axis] = 'input';
|
|
286
|
+
kept.alignInput[axis] = how.mm.from.input;
|
|
287
|
+
delete kept.alignMm[axis];
|
|
288
|
+
}
|
|
178
289
|
}
|
|
179
290
|
const gap = record.origin?.gap;
|
|
180
291
|
return {
|
|
@@ -194,7 +305,7 @@ export function fasteningOf(asset, part) {
|
|
|
194
305
|
* its frame slid into it (2026-09-26). Left centred where it already is, and where the target stands turned.
|
|
195
306
|
*/
|
|
196
307
|
export function placeKeptOf(asset, part, target, twisted = false) {
|
|
197
|
-
const kept = { align: {}, alignMm: {} };
|
|
308
|
+
const kept = { align: {}, alignMm: {}, alignInput: {} };
|
|
198
309
|
let geometry;
|
|
199
310
|
try {
|
|
200
311
|
geometry = compileFigureAsset(asset).evaluate().geometry;
|
|
@@ -225,7 +336,7 @@ export function placeKeptOf(asset, part, target, twisted = false) {
|
|
|
225
336
|
return kept;
|
|
226
337
|
}
|
|
227
338
|
/** The `align` of an attach command from the form's choices: only the axes across that face, and centred ones left out. */
|
|
228
|
-
export function alignOf(asset, part, target, how, mm, twisted = false) {
|
|
339
|
+
export function alignOf(asset, part, target, how, mm, twisted = false, inputs = {}) {
|
|
229
340
|
const align = {};
|
|
230
341
|
for (const { axis, edges } of alignAxesOf(asset, part, target, twisted)) {
|
|
231
342
|
const one = how[axis] ?? 'centre';
|
|
@@ -236,6 +347,8 @@ export function alignOf(asset, part, target, how, mm, twisted = false) {
|
|
|
236
347
|
align[axis] = { mm: mm[axis] };
|
|
237
348
|
else if ((one === 'min' || one === 'max') && edges)
|
|
238
349
|
align[axis] = one;
|
|
350
|
+
else if (one === 'input' && inputs[axis])
|
|
351
|
+
align[axis] = { mm: { from: { of: 'input', input: inputs[axis] } } };
|
|
239
352
|
}
|
|
240
353
|
return Object.keys(align).length ? align : undefined;
|
|
241
354
|
}
|
|
@@ -331,6 +444,8 @@ export function motionJoint(joint, m, part) {
|
|
|
331
444
|
body0: joint.body0,
|
|
332
445
|
body1: joint.body1,
|
|
333
446
|
...(joint.origin ? { origin: joint.origin } : {}),
|
|
447
|
+
/* A row keeps being a row when it is given a motion: dropped here, the row went with it (designer 2026-09-27). */
|
|
448
|
+
...(joint.repeat ? { repeat: joint.repeat } : {}),
|
|
334
449
|
type: m.kind === 'slide' ? 'prismatic' : 'revolute',
|
|
335
450
|
axis: m.axis.toUpperCase()
|
|
336
451
|
};
|
|
@@ -477,4 +592,15 @@ export function rangeDiffers(a, b) {
|
|
|
477
592
|
export function turnsAboutAJoint(asset) {
|
|
478
593
|
return modelOf(asset).nodes.some(n => n.op === 'axis-turn@1');
|
|
479
594
|
}
|
|
595
|
+
/** The joint made still: its fastening, its place and its row kept, its motion gone. */
|
|
596
|
+
export function fixedJointOf(joint) {
|
|
597
|
+
return {
|
|
598
|
+
id: joint.id,
|
|
599
|
+
type: 'fixed',
|
|
600
|
+
body0: joint.body0,
|
|
601
|
+
body1: joint.body1,
|
|
602
|
+
...(joint.origin ? { origin: joint.origin } : {}),
|
|
603
|
+
...(joint.repeat ? { repeat: joint.repeat } : {})
|
|
604
|
+
};
|
|
605
|
+
}
|
|
480
606
|
//# sourceMappingURL=links.js.map
|