@things-factory/figure-ui 10.1.272 → 10.1.273

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 (56) hide show
  1. package/client/modeller/data-interface.ts +30 -18
  2. package/client/modeller/editor-styles.ts +17 -6
  3. package/client/modeller/editor.ts +2 -0
  4. package/client/modeller/fasten-form.ts +12 -2
  5. package/client/modeller/fasten-geometry.ts +5 -3
  6. package/client/modeller/figure-data-panel.ts +384 -200
  7. package/client/modeller/figure-fasten-drawing.ts +21 -13
  8. package/client/modeller/figure-links.ts +24 -9
  9. package/client/modeller/figure-parts.ts +7 -11
  10. package/client/modeller/figure-preview.ts +258 -76
  11. package/dist-client/modeller/clips.d.ts +1 -4
  12. package/dist-client/modeller/data-interface.d.ts +11 -8
  13. package/dist-client/modeller/data-interface.js +27 -17
  14. package/dist-client/modeller/data-interface.js.map +1 -1
  15. package/dist-client/modeller/editor-styles.js +17 -6
  16. package/dist-client/modeller/editor-styles.js.map +1 -1
  17. package/dist-client/modeller/editor.js +2 -0
  18. package/dist-client/modeller/editor.js.map +1 -1
  19. package/dist-client/modeller/fasten-form.d.ts +5 -0
  20. package/dist-client/modeller/fasten-form.js +8 -2
  21. package/dist-client/modeller/fasten-form.js.map +1 -1
  22. package/dist-client/modeller/fasten-geometry.d.ts +3 -2
  23. package/dist-client/modeller/fasten-geometry.js +5 -3
  24. package/dist-client/modeller/fasten-geometry.js.map +1 -1
  25. package/dist-client/modeller/figure-data-panel.d.ts +10 -19
  26. package/dist-client/modeller/figure-data-panel.js +382 -190
  27. package/dist-client/modeller/figure-data-panel.js.map +1 -1
  28. package/dist-client/modeller/figure-fasten-drawing.js +21 -13
  29. package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
  30. package/dist-client/modeller/figure-links.js +31 -16
  31. package/dist-client/modeller/figure-links.js.map +1 -1
  32. package/dist-client/modeller/figure-parts.js +7 -11
  33. package/dist-client/modeller/figure-parts.js.map +1 -1
  34. package/dist-client/modeller/figure-preview.d.ts +13 -0
  35. package/dist-client/modeller/figure-preview.js +257 -77
  36. package/dist-client/modeller/figure-preview.js.map +1 -1
  37. package/dist-client/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +8 -7
  39. package/test/browser/editing-page.js +1 -1
  40. package/test/browser/links.check.mjs +30 -0
  41. package/test/browser/modeller.check.mjs +2 -2
  42. package/test/browser/parts-page.js +4 -0
  43. package/test/browser/parts.check.mjs +16 -10
  44. package/test/browser/screen-page.js +12 -1
  45. package/test/browser/screen.check.mjs +142 -11
  46. package/test/browser/serve-response.mjs +5 -2
  47. package/test/browser/side-panel-sweep.check.mjs +171 -10
  48. package/test/data-motion.test.ts +39 -8
  49. package/test/fasten-form.test.ts +25 -0
  50. package/test/fasten-geometry.test.ts +13 -0
  51. package/translations/en.json +23 -11
  52. package/translations/es.json +22 -10
  53. package/translations/ja.json +22 -10
  54. package/translations/ko.json +24 -12
  55. package/translations/ms.json +22 -10
  56. package/translations/zh.json +22 -10
@@ -23,6 +23,11 @@ export interface FastenForm {
23
23
  alignInput: Partial<Record<Axis, string>>;
24
24
  /** On a point of the target's curved surface instead of a face (ADR-0140 3.9): where, as the record says it. */
25
25
  surface?: FigureSurfacePlace;
26
+ /**
27
+ * Opened on the part's own fastening: aimed again, it goes there with that fastening's gap and place across the face
28
+ * (tutorial 2026-11-12). 「현재 상태로 붙이기」 -- where it stands kept -- is a loose part's.
29
+ */
30
+ fastened?: boolean;
26
31
  }
27
32
  export declare const freshFasten: () => FastenForm;
28
33
  /** The part's own fastening as a form, or a fresh one: a window opens on what the part has (walk 1, 2026-09-26). */
@@ -28,8 +28,12 @@ export function fastenFormOf(revision, part) {
28
28
  /* A part still where it was added was put nowhere by the author: fastened, it goes on the face, no gap to keep. */
29
29
  return addedUnplaced(revision, part) ? { ...freshFasten(), gapFrom: 'pressed' } : freshFasten();
30
30
  }
31
- /* A gap the record works out is the author's; a number is where the part stands, so aiming again keeps it there. */
32
- return { ...freshFasten(), ...own, gapFrom: own.gapKept !== undefined ? 'typed' : 'kept' };
31
+ /*
32
+ Its fastening is the author's, gap and place: aimed at another face or part through 「바꾸기」 it goes there with them
33
+ (tutorial 2026-11-12: a pillar copied from the front one and turned to the body's back face stayed in front, its gap
34
+ worked out as the whole body's depth).
35
+ */
36
+ return { ...freshFasten(), ...own, gapFrom: 'typed', fastened: true };
33
37
  }
34
38
  /** A part fastened to nothing that still stands where the palette put it: the author has put it nowhere to keep. */
35
39
  function addedUnplaced(revision, part) {
@@ -140,6 +144,8 @@ export function aimFasten(revision, part, form, to) {
140
144
  * arm aimed at the body's side stood off the face, out of reach of the drawing).
141
145
  */
142
146
  const centred = { align: {}, alignMm: {}, alignInput: {} };
147
+ if (f.fastened)
148
+ return f;
143
149
  return { ...f, ...(aim && !addedUnplaced(revision, part) ? placeKeptOf(revision, part, aim) : centred) };
144
150
  }
145
151
  /** A change the drawing asks: a face, a way, a gap or a place across the face. */
@@ -1 +1 @@
1
- {"version":3,"file":"fasten-form.js","sourceRoot":"","sources":["../../client/modeller/fasten-form.ts"],"names":[],"mappings":"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","sourcesContent":["import { FIGURE_FACES, figureFastenKeptGapOf, figureFastenTargetAt, figurePlaneFacingOf, figureSurfaceKeptPlaceOf } from '@hatiolab/figure-model'\nimport type { FigureAuthoringAction, FigureFace, FigureSurfacePlace } from '@hatiolab/figure-model'\n\nimport { faceViewOf, facesAsDrawnOf, type FaceView, type FastenMode, type FastenTarget } from './fasten-geometry.js'\nimport type { FastenChange } from './figure-fasten-drawing.js'\nimport {\n alignOf,\n alignAxesOf,\n alignRowsOf,\n canFasten,\n facesOf,\n fasteningOf,\n fastenGapOf,\n hangsFrom,\n heldBy,\n mountingPlaneOf,\n placeKeptOf,\n surfaceOf,\n type Alignment,\n type Axis\n} from './links.js'\nimport type { FigureRevision } from './revision.js'\nimport { standsWhereAdded } from './palette.js'\nimport { ownerOf } from './fields.js'\n\n/*\n * A fastening as it is chosen, before it is done: to what, which face, how, where across the face. The fastening window\n * (`figure-fasten-dialog.ts`) holds one and changes it as the author chooses; the attach command is worked out from it\n * only here, so the window, its preview and the panel's 「정렬 고치기」 send the same command for the same choice.\n */\n\nconst FACES = Object.keys(FIGURE_FACES) as FigureFace[]\n\nexport interface FastenForm {\n toPlane: boolean\n target: string\n targetFace: FigureFace\n /** How it meets the face: its near end on it, its far end on it (flush), or its middle (ADR-0140 3.11). */\n facing: FastenMode\n gap: number\n /** The record's gap when it is worked out (a length that follows something): kept until a number is typed. */\n gapKept: unknown\n /**\n * Where the gap came from (ADR-0140 stage 4, 「현재 상태로 붙이기」): `kept` -- the distance the part stands at, worked\n * out again whenever where or how it is fastened changes, so fastening does not move it; `typed` -- the author's\n * number, left as it is; `pressed` -- 0, the part put where the canvas was pressed.\n */\n gapFrom: 'kept' | 'typed' | 'pressed'\n align: Partial<Record<Axis, Alignment>>\n alignMm: Partial<Record<Axis, number>>\n alignInput: Partial<Record<Axis, string>>\n /** On a point of the target's curved surface instead of a face (ADR-0140 3.9): where, as the record says it. */\n surface?: FigureSurfacePlace\n}\n\nexport const freshFasten = (): FastenForm => ({\n toPlane: false,\n target: '',\n targetFace: 'top',\n facing: 'meet',\n gap: 0,\n gapKept: undefined,\n gapFrom: 'kept',\n align: {},\n alignMm: {},\n alignInput: {}\n})\n\n/** The part's own fastening as a form, or a fresh one: a window opens on what the part has (walk 1, 2026-09-26). */\nexport function fastenFormOf(revision: FigureRevision, part: string): FastenForm {\n const own = fasteningOf(revision, part)\n if (!own || own.atOrigin) {\n /* A part still where it was added was put nowhere by the author: fastened, it goes on the face, no gap to keep. */\n return addedUnplaced(revision, part) ? { ...freshFasten(), gapFrom: 'pressed' } : freshFasten()\n }\n /* A gap the record works out is the author's; a number is where the part stands, so aiming again keeps it there. */\n return { ...freshFasten(), ...own, gapFrom: own.gapKept !== undefined ? 'typed' : 'kept' } as FastenForm\n}\n\n/** A part fastened to nothing that still stands where the palette put it: the author has put it nowhere to keep. */\nfunction addedUnplaced(revision: FigureRevision, part: string): boolean {\n if (fasteningOf(revision, part)) return false\n const pose = (revision.geometry.find(g => (g as { id: string }).id === part) as { pose?: { r: number[][]; t: number[] } } | undefined)?.pose\n return standsWhereAdded(pose)\n}\n\n/**\n * The face a part just added starts on when aimed at `target`: the one asked, unless another part is fastened there\n * already -- then the first face of the target nothing is fastened to (review 2026-11-11: a thigh aimed at a pelvis\n * started on its top, where the body stands, and went inside the body until the bottom was chosen). A part the author\n * has placed keeps the face it stands by: a face holds many parts (rollers and a lid on one frame).\n */\nfunction freeFaceOf(revision: FigureRevision, part: string, target: string, asked: FigureFace): FigureFace {\n const on = (end: unknown) => end as { part?: string; face?: string }\n const joints = revision.asset.joints ?? []\n const taken = new Set(joints.filter(j => on(j.body0).part === target && on(j.body1).part !== part).map(j => on(j.body0).face))\n /* The face the target itself stands on its own parent by: a thigh hung under a pelvis touches it with its top. */\n let hungBy: FigureFace | undefined\n for (const j of joints)\n if (on(j.body1).part === target && on(j.body0).face && (j as { facing?: string }).facing !== 'flush') {\n hungBy = OPPOSITE[on(j.body0).face as FigureFace]\n taken.add(hungBy)\n }\n /*\n A tall part hung by its side -- an upper arm on a torso -- goes on downward: what is fastened to it starts under its\n bottom end, not on its top beside the shoulder (tutorial run 2026-11-12: a forearm started on the upper arm's top\n and its bottom had to be picked by hand).\n */\n if (hungBy && FIGURE_FACES[hungBy] !== 'y' && !taken.has('bottom') && tallOf(revision, target)) return 'bottom'\n if (!taken.has(asked)) return asked\n /* The face across from the one taken first: a body on the top, legs under the bottom. */\n const across = OPPOSITE[asked]\n const faces = facesOf(revision.asset, target)\n if (faces.includes(across) && !taken.has(across)) return across\n return (faces.find(face => !taken.has(face)) as FigureFace | undefined) ?? asked\n}\n\n/** Whether a part as drawn is at least half again as tall as it is wide and deep, read off its own faces. */\nfunction tallOf(revision: FigureRevision, part: string): boolean {\n const faces = (revision.geometry.find(g => (g as { id: string }).id === part) as { shape?: { faces?: Record<string, { at: number }> } } | undefined)?.shape?.faces\n const span = (lo: string, hi: string) => (faces?.[hi] && faces?.[lo] ? Math.abs(faces[hi]!.at - faces[lo]!.at) : undefined)\n const [x, y, z] = [span('left', 'right'), span('bottom', 'top'), span('back', 'front')]\n return x !== undefined && y !== undefined && z !== undefined && y >= 1.5 * Math.max(x, z)\n}\n\nconst OPPOSITE: Record<FigureFace, FigureFace> = { top: 'bottom', bottom: 'top', left: 'right', right: 'left', front: 'back', back: 'front' }\n\n/** What a part may be fastened to: every other part that can be, each said whether it would close a loop. */\nexport function fastenTargetsOf(revision: FigureRevision, part: string): Array<{ part: string; loops: boolean }> {\n const asset = revision.asset\n return (\n asset.appearance\n .map(look => look.target)\n .filter(one => one !== part && canFasten(asset, one))\n /* Not a part that hangs from this one: fastening a frame to its own roller would close a loop. */\n .map(one => ({ part: one, loops: hangsFrom(asset, one, part) }))\n )\n}\n\n/**\n * The point of a target's curved surface on the side the part stands on now (ADR-0140 3.9): the kernel's\n * (`figureSurfaceKeptPlaceOf`), or, the part standing on its axis, the surface's first point.\n */\nexport function surfaceKeptOf(revision: FigureRevision, part: string, target: string): FigureSurfacePlace | undefined {\n const surface = surfaceOf(revision.asset, target)\n if (!surface) return undefined\n return (\n figureSurfaceKeptPlaceOf(revision.asset, part, target, { ...revision.inputs }) ??\n (surface.form === 'around' ? { around: 0, along: 'centre' } : { toward: { x: 0, y: 1, z: 0 } })\n )\n}\n\n/**\n * A new aim: the plane, another part, another face of the same part, or a point of its curved surface. Where on the\n * face -- or the surface -- is filled in as where the part stands now, so fastening it keeps it there (`placeKeptOf`,\n * `surfaceKeptOf`); the author changes it from there. A part aimed at a sphere, or beside a cylinder's side within its\n * length, is aimed at the surface: it is fastened as it stands (ADR-0140 3.6).\n */\nexport function aimFasten(\n revision: FigureRevision,\n part: string,\n form: FastenForm,\n to: { plane: true } | { part: string } | { targetFace: FigureFace } | { surface: true }\n): FastenForm {\n if ('surface' in to) return { ...form, surface: surfaceKeptOf(revision, part, form.target), align: {}, alignMm: {}, alignInput: {} }\n if ('part' in to) {\n /* Beside its curved surface -- a sphere's anywhere, a cylinder's side within its length -- or with no face at all. */\n const beside = figureSurfaceKeptPlaceOf(revision.asset, part, to.part, { ...revision.inputs })\n const kept = beside ?? (!facesOf(revision.asset, to.part).length ? surfaceKeptOf(revision, part, to.part) : undefined)\n if (kept) return { ...form, toPlane: false, target: to.part, surface: kept, align: {}, alignMm: {}, alignInput: {} }\n }\n form = { ...form, surface: undefined }\n /* Aimed at the plane, it meets it toward the side the placement uses -- below a ceiling -- until the author says (ADR-0140 3.11). */\n const f: FastenForm =\n 'plane' in to\n ? { ...form, toPlane: true, target: '', facing: form.toPlane ? form.facing : (figurePlaneFacingOf(revision.asset) ?? form.facing) }\n : 'part' in to\n ? {\n ...form,\n toPlane: false,\n target: to.part,\n targetFace: to.part !== form.target && addedUnplaced(revision, part) ? freeFaceOf(revision, part, to.part, form.targetFace) : form.targetFace\n }\n : { ...form, targetFace: to.targetFace }\n const theirFaces = f.target ? facesOf(revision.asset, f.target) : FACES\n const targetFace = (theirFaces.includes(f.targetFace) ? f.targetFace : theirFaces[0]) as FigureFace\n const aim: FastenTarget | undefined = f.toPlane\n ? 'plane'\n : f.target\n ? { part: f.target, face: targetFace }\n : undefined\n /*\n * A part still where the palette put it has no place of the author's to keep: it goes to the face's centre (review\n * 2026-11-11: a shin added at the origin and aimed at a thigh 85 mm off it was put 85 mm off the thigh's centre; an\n * arm aimed at the body's side stood off the face, out of reach of the drawing).\n */\n const centred = { align: {}, alignMm: {}, alignInput: {} }\n return { ...f, ...(aim && !addedUnplaced(revision, part) ? placeKeptOf(revision, part, aim) : centred) }\n}\n\n/** A change the drawing asks: a face, a way, a gap or a place across the face. */\nexport function fastenChanged(\n revision: FigureRevision,\n part: string,\n form: FastenForm,\n change: FastenChange\n): FastenForm {\n const f = form\n if ('targetFace' in change) return keptGapOf(revision, part, aimFasten(revision, part, f, { targetFace: change.targetFace as FigureFace }))\n if ('onSurface' in change) return keptGapOf(revision, part, aimFasten(revision, part, f, { surface: true }))\n if ('surface' in change) return keptGapOf(revision, part, { ...f, surface: change.surface })\n if ('mode' in change) return keptGapOf(revision, part, { ...f, facing: change.mode })\n if ('gap' in change) return { ...f, gap: change.gap, gapKept: undefined, gapFrom: 'typed' }\n return {\n ...f,\n align: { ...f.align, [change.axis]: change.how },\n alignMm: change.how === 'mm' ? { ...f.alignMm, [change.axis]: change.mm ?? 0 } : f.alignMm\n }\n}\n\n/**\n * The form with the gap that keeps the part where it stands (ADR-0140 stage 4, user 2026-10-20 「현재 상태로 붙이기」):\n * figure-model's `figureFastenKeptGapOf` for what it is aimed at and how it meets it. A gap the author typed, or the 0 a\n * canvas press puts, is left as it is. Nothing aimed at yet, the form is as it was.\n */\nexport function keptGapOf(revision: FigureRevision, part: string, form: FastenForm): FastenForm {\n if (form.gapFrom !== 'kept') return form\n const asset = revision.asset\n const theirFaces = form.target ? facesOf(asset, form.target) : FACES\n const targetFace = (theirFaces.includes(form.targetFace) ? form.targetFace : theirFaces[0]) as FigureFace\n const planeWay = figurePlaneFacingOf(asset)\n const mode: FastenMode = form.toPlane ? (mountingPlaneOf(asset).facings.includes(form.facing) ? form.facing : (planeWay ?? 'meet')) : form.facing\n const to = form.toPlane\n ? ('plane' as const)\n : form.target && form.surface && surfaceOf(asset, form.target)\n ? { part: form.target, surface: form.surface }\n : form.target\n ? { part: form.target, face: targetFace }\n : undefined\n if (!to) return form\n return { ...form, gap: figureFastenKeptGapOf(asset, part, to, mode, { ...revision.inputs }), gapKept: undefined }\n}\n\n/** What a form comes to: the face aimed at, the way, the drawing of the face and the command. */\nexport interface FastenPlan {\n /** Nothing is aimed at until a part or the plane is chosen. */\n aim?: FastenTarget\n theirFaces: string[]\n targetFace: FigureFace\n mode: FastenMode\n view?: FaceView\n /** The axes across the face asked in rows: where the drawing cannot set the place. */\n alignRows: { axis: Axis; edges: boolean }[]\n /**\n * Every axis across the face, the drawing's too: the details ask each by a number as well (review 2026-11-11: a thigh\n * dragged to a pelvis's edge overshot by 47 mm, and dragged back by eye).\n */\n alignAxes?: { axis: Axis; edges: boolean }[]\n action?: FigureAuthoringAction\n /** The face the command would meet, lit on the canvas while it is tried. */\n face?: { part: string; face: string }\n /** On a point of the target's curved surface: which surface, about which axis, and where (ADR-0140 3.9). */\n surface?: { form: 'around' | 'toward'; axis: Axis; place: FigureSurfacePlace }\n /** The target's own face at each of the drawing's places, as the target is drawn (\\`facesAsDrawnOf\\`). */\n drawnFaces?: Record<FigureFace, FigureFace>\n}\n\nexport function fastenPlanOf(revision: FigureRevision, part: string, f: FastenForm): FastenPlan {\n const plan = fastenPlanWithout(revision, part, f)\n return f.target && !f.toPlane ? { ...plan, drawnFaces: facesAsDrawnOf(revision, f.target) } : plan\n}\n\nfunction fastenPlanWithout(revision: FigureRevision, part: string, f: FastenForm): FastenPlan {\n const asset = revision.asset\n const theirFaces = f.target ? facesOf(asset, f.target) : FACES\n const targetFace = (theirFaces.includes(f.targetFace) ? f.targetFace : theirFaces[0]) as FigureFace\n const aim: FastenTarget | undefined = f.toPlane\n ? 'plane'\n : f.target\n ? { part: f.target, face: targetFace }\n : undefined\n /* The plane takes a part standing on it or hanging below it, a wall only one standing out of it (ADR-0140 3.11). */\n const planeWay = figurePlaneFacingOf(asset)\n const mode: FastenMode = f.toPlane ? (mountingPlaneOf(asset).facings.includes(f.facing) ? f.facing : (planeWay ?? 'meet')) : f.facing\n const surface = !f.toPlane && f.target && f.surface ? surfaceOf(asset, f.target) : undefined\n if (surface) {\n /* On the surface: where is the surface's own words, so no alignment goes with it (ADR-0140 3.9). */\n const action = {\n kind: 'attach',\n part,\n to: { part: f.target, surface: f.surface },\n facing: mode,\n ...fastenGapOf(f),\n replace: !!heldBy(asset, part)\n } as FigureAuthoringAction\n return { theirFaces, targetFace, mode, alignRows: [], action, surface: { ...surface, place: f.surface! } }\n }\n if (!aim) return { theirFaces, targetFace, mode, alignRows: [] }\n const align = alignOf(asset, aim, f.align, f.alignMm, f.alignInput)\n const view = faceViewOf(revision, part, aim, mode)\n const action = {\n kind: 'attach',\n part,\n to: f.toPlane ? { plane: 'mounting-plane' } : { part: f.target, face: targetFace },\n /* On the plane the kernel takes the placement's way when none is said: only another way is said. */\n ...(f.toPlane ? (mode === planeWay ? {} : { facing: mode }) : { facing: mode }),\n ...fastenGapOf(f),\n ...(align ? { align } : {}),\n replace: !!heldBy(asset, part)\n } as FigureAuthoringAction\n return {\n aim,\n theirFaces,\n targetFace,\n mode,\n view,\n alignRows: alignRowsOf(asset, aim, view, f.alignInput),\n alignAxes: alignAxesOf(asset, aim),\n action,\n ...(f.toPlane ? {} : { face: { part: f.target, face: targetFace } })\n }\n}\n\ntype Say = (key: string, params?: Record<string, string>) => string\n\n/** 「bin의 윗면에 올려놓기」: what the fastening is, in one line -- the part is the one the window is open on, not named again. */\nexport function fastenSentenceOf(\n form: FastenForm,\n plan: FastenPlan,\n planeWord: string,\n say: Say\n): string {\n const where = plan.surface ? say('figure.label.surface-' + plan.surface.form) : say('figure.label.face-' + plan.targetFace)\n const to = form.toPlane ? planeWord : `${form.target}의 ${where}`\n return say(`figure.text.fasten-sentence-${plan.mode}`, { to })\n}\n\n/**\n * What the folded details hold that is not their default, said while folded; nothing for the default. The gap's default\n * is the one it starts at -- the distance the part stands at, or 0 where the canvas was pressed -- so only a number typed\n * is said.\n */\nexport function fastenDetailWordsOf(form: FastenForm, say: Say): string[] {\n return [\n ...(form.gapKept !== undefined\n ? [say('figure.text.fasten-summary-gap-worked-out')]\n : form.gapFrom === 'typed' && form.gap\n ? [say('figure.text.fasten-summary-gap', { mm: String(form.gap) })]\n : [])\n ]\n}\n\n/**\n * A part pressed on the canvas while the window is open (ADR-0140 stage 4): aimed at, on the face or the point pressed,\n * its middle where it was pressed -- the kernel's reading of the press (`figureFastenTargetAt`), the point and normal brought into that part's own\n * frame by the pose its body is drawn at (`at.id`, a row's copy too). The part itself, one hanging from it, or one with\n * nothing to fasten to is not aimed at; a press on a curve there is no fastening to aims at the part alone. Each says\n * why, by its words' key.\n */\n/* Put where the canvas was pressed: on the face or the surface there, with no gap (ADR-0140 stage 4). */\nconst PRESSED = { gap: 0, gapKept: undefined, gapFrom: 'pressed' as const }\n\nexport function aimFastenAt(\n revision: FigureRevision,\n part: string,\n form: FastenForm,\n at: { id: string; point?: readonly number[]; normal?: readonly number[] }\n): { form?: FastenForm; note?: 'aim-self' | 'aim-cannot' | 'target-would-loop' | 'aim-not-here'; part: string } {\n const target = ownerOf(at.id)\n const offered = fastenTargetsOf(revision, part).find(t => t.part === target)\n if (target === part) return { note: 'aim-self', part: target }\n if (!offered) return { note: 'aim-cannot', part: target }\n if (offered.loops) return { note: 'target-would-loop', part: target }\n const aimed = aimFasten(revision, part, form, { part: target })\n const pose = (revision.geometry as Array<{ id: string; pose?: { r: number[][]; t: number[] } }>).find(g => g.id === at.id)?.pose\n if (!pose || !at.point || !at.normal) return { form: aimed, part: target }\n /* Into the part's own frame: rᵀ (p − t), and the normal by rᵀ. */\n const own = (v: readonly number[], from: readonly number[] = [0, 0, 0]) => [0, 1, 2].map(c => [0, 1, 2].reduce((sum, i) => sum + pose.r[i]![c]! * (v[i]! - from[i]!), 0))\n const lands = figureFastenTargetAt(revision.asset, target, own(at.point, pose.t), own(at.normal), { ...revision.inputs })\n if ('face' in lands) {\n /* On the face where it was pressed, as a point of a curved surface is: its middle there. */\n const onFace = aimFasten(revision, part, { ...aimed, surface: undefined }, { targetFace: lands.face as FigureFace })\n const at = lands.at as Partial<Record<Axis, number>>\n return { form: { ...onFace, ...PRESSED, align: Object.fromEntries(Object.keys(at).map(a => [a, 'mm'])) as FastenForm['align'], alignMm: { ...at }, alignInput: {} }, part: target }\n }\n if ('surface' in lands) return { form: { ...aimed, ...PRESSED, toPlane: false, target, surface: lands.surface, align: {}, alignMm: {}, alignInput: {} }, part: target }\n return { form: aimed, note: 'aim-not-here', part: target }\n}\n"]}
1
+ {"version":3,"file":"fasten-form.js","sourceRoot":"","sources":["../../client/modeller/fasten-form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AAGjJ,OAAO,EAAE,UAAU,EAAE,cAAc,EAAqD,MAAM,sBAAsB,CAAA;AAEpH,OAAO,EACL,OAAO,EACP,WAAW,EACX,WAAW,EACX,SAAS,EACT,OAAO,EACP,WAAW,EACX,WAAW,EACX,SAAS,EACT,MAAM,EACN,eAAe,EACf,WAAW,EACX,SAAS,EAGV,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAErC;;;;GAIG;AAEH,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAiB,CAAA;AA6BvD,MAAM,CAAC,MAAM,WAAW,GAAG,GAAe,EAAE,CAAC,CAAC;IAC5C,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,EAAE;IACV,UAAU,EAAE,KAAK;IACjB,MAAM,EAAE,MAAM;IACd,GAAG,EAAE,CAAC;IACN,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,MAAM;IACf,KAAK,EAAE,EAAE;IACT,OAAO,EAAE,EAAE;IACX,UAAU,EAAE,EAAE;CACf,CAAC,CAAA;AAEF,oHAAoH;AACpH,MAAM,UAAU,YAAY,CAAC,QAAwB,EAAE,IAAY;IACjE,MAAM,GAAG,GAAG,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IACvC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACzB,mHAAmH;QACnH,OAAO,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,WAAW,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;IACjG,CAAC;IACD;;;;MAIE;IACF,OAAO,EAAE,GAAG,WAAW,EAAE,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAgB,CAAA;AACrF,CAAC;AAED,oHAAoH;AACpH,SAAS,aAAa,CAAC,QAAwB,EAAE,IAAY;IAC3D,IAAI,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IAC7C,MAAM,IAAI,GAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAE,CAAoB,CAAC,EAAE,KAAK,IAAI,CAA2D,EAAE,IAAI,CAAA;IAC5I,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAA;AAC/B,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,QAAwB,EAAE,IAAY,EAAE,MAAc,EAAE,KAAiB;IAC3F,MAAM,EAAE,GAAG,CAAC,GAAY,EAAE,EAAE,CAAC,GAAuC,CAAA;IACpE,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAA;IAC1C,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAC9H,kHAAkH;IAClH,IAAI,MAA8B,CAAA;IAClC,KAAK,MAAM,CAAC,IAAI,MAAM;QACpB,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,IAAK,CAAyB,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YACrG,MAAM,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAkB,CAAC,CAAA;YACjD,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACnB,CAAC;IACH;;;;MAIE;IACF,IAAI,MAAM,IAAI,YAAY,CAAC,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAE,OAAO,QAAQ,CAAA;IAC/G,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACnC,yFAAyF;IACzF,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAC7C,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAA;IAC/D,OAAQ,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAA4B,IAAI,KAAK,CAAA;AAClF,CAAC;AAED,6GAA6G;AAC7G,SAAS,MAAM,CAAC,QAAwB,EAAE,IAAY;IACpD,MAAM,KAAK,GAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAE,CAAoB,CAAC,EAAE,KAAK,IAAI,CAAwE,EAAE,KAAK,EAAE,KAAK,CAAA;IAClK,MAAM,IAAI,GAAG,CAAC,EAAU,EAAE,EAAU,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAE,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,CAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IAC3H,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACvF,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAC3F,CAAC;AAED,MAAM,QAAQ,GAAmC,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AAE7I,6GAA6G;AAC7G,MAAM,UAAU,eAAe,CAAC,QAAwB,EAAE,IAAY;IACpE,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAA;IAC5B,OAAO,CACL,KAAK,CAAC,UAAU;SACb,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC;SACxB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,IAAI,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACrD,kGAAkG;SACjG,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CACnE,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAAwB,EAAE,IAAY,EAAE,MAAc;IAClF,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IACjD,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAA;IAC9B,OAAO,CACL,wBAAwB,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC9E,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAChG,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CACvB,QAAwB,EACxB,IAAY,EACZ,IAAgB,EAChB,EAAuF;IAEvF,IAAI,SAAS,IAAI,EAAE;QAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAA;IACpI,IAAI,MAAM,IAAI,EAAE,EAAE,CAAC;QACjB,sHAAsH;QACtH,MAAM,MAAM,GAAG,wBAAwB,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;QAC9F,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;QACtH,IAAI,IAAI;YAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAA;IACtH,CAAC;IACD,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;IACtC,qIAAqI;IACrI,MAAM,CAAC,GACL,OAAO,IAAI,EAAE;QACX,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE;QACnI,CAAC,CAAC,MAAM,IAAI,EAAE;YACZ,CAAC,CAAC;gBACE,GAAG,IAAI;gBACP,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,EAAE,CAAC,IAAI;gBACf,UAAU,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU;aAC9I;YACH,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,EAAE,CAAA;IAC9C,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IACvE,MAAM,UAAU,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAe,CAAA;IACnG,MAAM,GAAG,GAA6B,CAAC,CAAC,OAAO;QAC7C,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE;YACtC,CAAC,CAAC,SAAS,CAAA;IACf;;;;OAIG;IACH,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAA;IAC1D,IAAI,CAAC,CAAC,QAAQ;QAAE,OAAO,CAAC,CAAA;IACxB,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAA;AAC1G,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,aAAa,CAC3B,QAAwB,EACxB,IAAY,EACZ,IAAgB,EAChB,MAAoB;IAEpB,MAAM,CAAC,GAAG,IAAI,CAAA;IACd,IAAI,YAAY,IAAI,MAAM;QAAE,OAAO,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAwB,EAAE,CAAC,CAAC,CAAA;IAC3I,IAAI,WAAW,IAAI,MAAM;QAAE,OAAO,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAC5G,IAAI,SAAS,IAAI,MAAM;QAAE,OAAO,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;IAC5F,IAAI,MAAM,IAAI,MAAM;QAAE,OAAO,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;IACrF,IAAI,KAAK,IAAI,MAAM;QAAE,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;IAC3F,OAAO;QACL,GAAG,CAAC;QACJ,KAAK,EAAE,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,GAAG,EAAE;QAChD,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO;KAC3F,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,QAAwB,EAAE,IAAY,EAAE,IAAgB;IAChF,IAAI,IAAI,CAAC,OAAO,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAA;IAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IACpE,MAAM,UAAU,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAe,CAAA;IACzG,MAAM,QAAQ,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,IAAI,GAAe,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAA;IACjJ,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO;QACrB,CAAC,CAAE,OAAiB;QACpB,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,IAAI,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;YAC5D,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;YAC9C,CAAC,CAAC,IAAI,CAAC,MAAM;gBACX,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE;gBACzC,CAAC,CAAC,SAAS,CAAA;IACjB,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAA;IACpB,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,qBAAqB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;AACnH,CAAC;AA0BD,MAAM,UAAU,YAAY,CAAC,QAAwB,EAAE,IAAY,EAAE,CAAa;IAChF,MAAM,IAAI,GAAG,iBAAiB,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IACjD,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,cAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACpG,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAwB,EAAE,IAAY,EAAE,CAAa;IAC9E,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAA;IAC5B,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IAC9D,MAAM,UAAU,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAe,CAAA;IACnG,MAAM,GAAG,GAA6B,CAAC,CAAC,OAAO;QAC7C,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE;YACtC,CAAC,CAAC,SAAS,CAAA;IACf,oHAAoH;IACpH,MAAM,QAAQ,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAA;IAC3C,MAAM,IAAI,GAAe,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IACrI,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAC5F,IAAI,OAAO,EAAE,CAAC;QACZ,oGAAoG;QACpG,MAAM,MAAM,GAAG;YACb,IAAI,EAAE,QAAQ;YACd,IAAI;YACJ,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;YAC1C,MAAM,EAAE,IAAI;YACZ,GAAG,WAAW,CAAC,CAAC,CAAC;YACjB,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC;SACN,CAAA;QAC1B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,OAAQ,EAAE,EAAE,CAAA;IAC5G,CAAC;IACD,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAA;IAChE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,UAAU,CAAC,CAAA;IACnE,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAA;IAClD,MAAM,MAAM,GAAG;QACb,IAAI,EAAE,QAAQ;QACd,IAAI;QACJ,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE;QAClF,oGAAoG;QACpG,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QAC/E,GAAG,WAAW,CAAC,CAAC,CAAC;QACjB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC;KACN,CAAA;IAC1B,OAAO;QACL,GAAG;QACH,UAAU;QACV,UAAU;QACV,IAAI;QACJ,IAAI;QACJ,SAAS,EAAE,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,UAAU,CAAC;QACtD,SAAS,EAAE,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC;QAClC,MAAM;QACN,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC;KACrE,CAAA;AACH,CAAC;AAID,yHAAyH;AACzH,MAAM,UAAU,gBAAgB,CAC9B,IAAgB,EAChB,IAAgB,EAChB,SAAiB,EACjB,GAAQ;IAER,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,uBAAuB,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,oBAAoB,GAAG,IAAI,CAAC,UAAU,CAAC,CAAA;IAC3H,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,KAAK,KAAK,EAAE,CAAA;IAChE,OAAO,GAAG,CAAC,+BAA+B,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AAChE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAgB,EAAE,GAAQ;IAC5D,OAAO;QACL,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS;YAC5B,CAAC,CAAC,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;YACpD,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,OAAO,IAAI,IAAI,CAAC,GAAG;gBACpC,CAAC,CAAC,CAAC,GAAG,CAAC,gCAAgC,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACnE,CAAC,CAAC,EAAE,CAAC;KACV,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,yGAAyG;AACzG,MAAM,OAAO,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,SAAkB,EAAE,CAAA;AAE3E,MAAM,UAAU,WAAW,CACzB,QAAwB,EACxB,IAAY,EACZ,IAAgB,EAChB,EAAyE;IAEzE,MAAM,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAC7B,MAAM,OAAO,GAAG,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAA;IAC5E,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IAC9D,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACzD,IAAI,OAAO,CAAC,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACrE,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;IAC/D,MAAM,IAAI,GAAI,QAAQ,CAAC,QAAyE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAA;IAChI,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IAC1E,kEAAkE;IAClE,MAAM,GAAG,GAAG,CAAC,CAAoB,EAAE,OAA0B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAE,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACzK,MAAM,KAAK,GAAG,oBAAoB,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IACzH,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;QACpB,4FAA4F;QAC5F,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,IAAkB,EAAE,CAAC,CAAA;QACpH,MAAM,EAAE,GAAG,KAAK,CAAC,EAAmC,CAAA;QACpD,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAwB,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACrL,CAAC;IACD,IAAI,SAAS,IAAI,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACvK,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AAC5D,CAAC","sourcesContent":["import { FIGURE_FACES, figureFastenKeptGapOf, figureFastenTargetAt, figurePlaneFacingOf, figureSurfaceKeptPlaceOf } from '@hatiolab/figure-model'\nimport type { FigureAuthoringAction, FigureFace, FigureSurfacePlace } from '@hatiolab/figure-model'\n\nimport { faceViewOf, facesAsDrawnOf, type FaceView, type FastenMode, type FastenTarget } from './fasten-geometry.js'\nimport type { FastenChange } from './figure-fasten-drawing.js'\nimport {\n alignOf,\n alignAxesOf,\n alignRowsOf,\n canFasten,\n facesOf,\n fasteningOf,\n fastenGapOf,\n hangsFrom,\n heldBy,\n mountingPlaneOf,\n placeKeptOf,\n surfaceOf,\n type Alignment,\n type Axis\n} from './links.js'\nimport type { FigureRevision } from './revision.js'\nimport { standsWhereAdded } from './palette.js'\nimport { ownerOf } from './fields.js'\n\n/*\n * A fastening as it is chosen, before it is done: to what, which face, how, where across the face. The fastening window\n * (`figure-fasten-dialog.ts`) holds one and changes it as the author chooses; the attach command is worked out from it\n * only here, so the window, its preview and the panel's 「정렬 고치기」 send the same command for the same choice.\n */\n\nconst FACES = Object.keys(FIGURE_FACES) as FigureFace[]\n\nexport interface FastenForm {\n toPlane: boolean\n target: string\n targetFace: FigureFace\n /** How it meets the face: its near end on it, its far end on it (flush), or its middle (ADR-0140 3.11). */\n facing: FastenMode\n gap: number\n /** The record's gap when it is worked out (a length that follows something): kept until a number is typed. */\n gapKept: unknown\n /**\n * Where the gap came from (ADR-0140 stage 4, 「현재 상태로 붙이기」): `kept` -- the distance the part stands at, worked\n * out again whenever where or how it is fastened changes, so fastening does not move it; `typed` -- the author's\n * number, left as it is; `pressed` -- 0, the part put where the canvas was pressed.\n */\n gapFrom: 'kept' | 'typed' | 'pressed'\n align: Partial<Record<Axis, Alignment>>\n alignMm: Partial<Record<Axis, number>>\n alignInput: Partial<Record<Axis, string>>\n /** On a point of the target's curved surface instead of a face (ADR-0140 3.9): where, as the record says it. */\n surface?: FigureSurfacePlace\n /**\n * Opened on the part's own fastening: aimed again, it goes there with that fastening's gap and place across the face\n * (tutorial 2026-11-12). 「현재 상태로 붙이기」 -- where it stands kept -- is a loose part's.\n */\n fastened?: boolean\n}\n\nexport const freshFasten = (): FastenForm => ({\n toPlane: false,\n target: '',\n targetFace: 'top',\n facing: 'meet',\n gap: 0,\n gapKept: undefined,\n gapFrom: 'kept',\n align: {},\n alignMm: {},\n alignInput: {}\n})\n\n/** The part's own fastening as a form, or a fresh one: a window opens on what the part has (walk 1, 2026-09-26). */\nexport function fastenFormOf(revision: FigureRevision, part: string): FastenForm {\n const own = fasteningOf(revision, part)\n if (!own || own.atOrigin) {\n /* A part still where it was added was put nowhere by the author: fastened, it goes on the face, no gap to keep. */\n return addedUnplaced(revision, part) ? { ...freshFasten(), gapFrom: 'pressed' } : freshFasten()\n }\n /*\n Its fastening is the author's, gap and place: aimed at another face or part through 「바꾸기」 it goes there with them\n (tutorial 2026-11-12: a pillar copied from the front one and turned to the body's back face stayed in front, its gap\n worked out as the whole body's depth).\n */\n return { ...freshFasten(), ...own, gapFrom: 'typed', fastened: true } as FastenForm\n}\n\n/** A part fastened to nothing that still stands where the palette put it: the author has put it nowhere to keep. */\nfunction addedUnplaced(revision: FigureRevision, part: string): boolean {\n if (fasteningOf(revision, part)) return false\n const pose = (revision.geometry.find(g => (g as { id: string }).id === part) as { pose?: { r: number[][]; t: number[] } } | undefined)?.pose\n return standsWhereAdded(pose)\n}\n\n/**\n * The face a part just added starts on when aimed at `target`: the one asked, unless another part is fastened there\n * already -- then the first face of the target nothing is fastened to (review 2026-11-11: a thigh aimed at a pelvis\n * started on its top, where the body stands, and went inside the body until the bottom was chosen). A part the author\n * has placed keeps the face it stands by: a face holds many parts (rollers and a lid on one frame).\n */\nfunction freeFaceOf(revision: FigureRevision, part: string, target: string, asked: FigureFace): FigureFace {\n const on = (end: unknown) => end as { part?: string; face?: string }\n const joints = revision.asset.joints ?? []\n const taken = new Set(joints.filter(j => on(j.body0).part === target && on(j.body1).part !== part).map(j => on(j.body0).face))\n /* The face the target itself stands on its own parent by: a thigh hung under a pelvis touches it with its top. */\n let hungBy: FigureFace | undefined\n for (const j of joints)\n if (on(j.body1).part === target && on(j.body0).face && (j as { facing?: string }).facing !== 'flush') {\n hungBy = OPPOSITE[on(j.body0).face as FigureFace]\n taken.add(hungBy)\n }\n /*\n A tall part hung by its side -- an upper arm on a torso -- goes on downward: what is fastened to it starts under its\n bottom end, not on its top beside the shoulder (tutorial run 2026-11-12: a forearm started on the upper arm's top\n and its bottom had to be picked by hand).\n */\n if (hungBy && FIGURE_FACES[hungBy] !== 'y' && !taken.has('bottom') && tallOf(revision, target)) return 'bottom'\n if (!taken.has(asked)) return asked\n /* The face across from the one taken first: a body on the top, legs under the bottom. */\n const across = OPPOSITE[asked]\n const faces = facesOf(revision.asset, target)\n if (faces.includes(across) && !taken.has(across)) return across\n return (faces.find(face => !taken.has(face)) as FigureFace | undefined) ?? asked\n}\n\n/** Whether a part as drawn is at least half again as tall as it is wide and deep, read off its own faces. */\nfunction tallOf(revision: FigureRevision, part: string): boolean {\n const faces = (revision.geometry.find(g => (g as { id: string }).id === part) as { shape?: { faces?: Record<string, { at: number }> } } | undefined)?.shape?.faces\n const span = (lo: string, hi: string) => (faces?.[hi] && faces?.[lo] ? Math.abs(faces[hi]!.at - faces[lo]!.at) : undefined)\n const [x, y, z] = [span('left', 'right'), span('bottom', 'top'), span('back', 'front')]\n return x !== undefined && y !== undefined && z !== undefined && y >= 1.5 * Math.max(x, z)\n}\n\nconst OPPOSITE: Record<FigureFace, FigureFace> = { top: 'bottom', bottom: 'top', left: 'right', right: 'left', front: 'back', back: 'front' }\n\n/** What a part may be fastened to: every other part that can be, each said whether it would close a loop. */\nexport function fastenTargetsOf(revision: FigureRevision, part: string): Array<{ part: string; loops: boolean }> {\n const asset = revision.asset\n return (\n asset.appearance\n .map(look => look.target)\n .filter(one => one !== part && canFasten(asset, one))\n /* Not a part that hangs from this one: fastening a frame to its own roller would close a loop. */\n .map(one => ({ part: one, loops: hangsFrom(asset, one, part) }))\n )\n}\n\n/**\n * The point of a target's curved surface on the side the part stands on now (ADR-0140 3.9): the kernel's\n * (`figureSurfaceKeptPlaceOf`), or, the part standing on its axis, the surface's first point.\n */\nexport function surfaceKeptOf(revision: FigureRevision, part: string, target: string): FigureSurfacePlace | undefined {\n const surface = surfaceOf(revision.asset, target)\n if (!surface) return undefined\n return (\n figureSurfaceKeptPlaceOf(revision.asset, part, target, { ...revision.inputs }) ??\n (surface.form === 'around' ? { around: 0, along: 'centre' } : { toward: { x: 0, y: 1, z: 0 } })\n )\n}\n\n/**\n * A new aim: the plane, another part, another face of the same part, or a point of its curved surface. Where on the\n * face -- or the surface -- is filled in as where the part stands now, so fastening it keeps it there (`placeKeptOf`,\n * `surfaceKeptOf`); the author changes it from there. A part aimed at a sphere, or beside a cylinder's side within its\n * length, is aimed at the surface: it is fastened as it stands (ADR-0140 3.6).\n */\nexport function aimFasten(\n revision: FigureRevision,\n part: string,\n form: FastenForm,\n to: { plane: true } | { part: string } | { targetFace: FigureFace } | { surface: true }\n): FastenForm {\n if ('surface' in to) return { ...form, surface: surfaceKeptOf(revision, part, form.target), align: {}, alignMm: {}, alignInput: {} }\n if ('part' in to) {\n /* Beside its curved surface -- a sphere's anywhere, a cylinder's side within its length -- or with no face at all. */\n const beside = figureSurfaceKeptPlaceOf(revision.asset, part, to.part, { ...revision.inputs })\n const kept = beside ?? (!facesOf(revision.asset, to.part).length ? surfaceKeptOf(revision, part, to.part) : undefined)\n if (kept) return { ...form, toPlane: false, target: to.part, surface: kept, align: {}, alignMm: {}, alignInput: {} }\n }\n form = { ...form, surface: undefined }\n /* Aimed at the plane, it meets it toward the side the placement uses -- below a ceiling -- until the author says (ADR-0140 3.11). */\n const f: FastenForm =\n 'plane' in to\n ? { ...form, toPlane: true, target: '', facing: form.toPlane ? form.facing : (figurePlaneFacingOf(revision.asset) ?? form.facing) }\n : 'part' in to\n ? {\n ...form,\n toPlane: false,\n target: to.part,\n targetFace: to.part !== form.target && addedUnplaced(revision, part) ? freeFaceOf(revision, part, to.part, form.targetFace) : form.targetFace\n }\n : { ...form, targetFace: to.targetFace }\n const theirFaces = f.target ? facesOf(revision.asset, f.target) : FACES\n const targetFace = (theirFaces.includes(f.targetFace) ? f.targetFace : theirFaces[0]) as FigureFace\n const aim: FastenTarget | undefined = f.toPlane\n ? 'plane'\n : f.target\n ? { part: f.target, face: targetFace }\n : undefined\n /*\n * A part still where the palette put it has no place of the author's to keep: it goes to the face's centre (review\n * 2026-11-11: a shin added at the origin and aimed at a thigh 85 mm off it was put 85 mm off the thigh's centre; an\n * arm aimed at the body's side stood off the face, out of reach of the drawing).\n */\n const centred = { align: {}, alignMm: {}, alignInput: {} }\n if (f.fastened) return f\n return { ...f, ...(aim && !addedUnplaced(revision, part) ? placeKeptOf(revision, part, aim) : centred) }\n}\n\n/** A change the drawing asks: a face, a way, a gap or a place across the face. */\nexport function fastenChanged(\n revision: FigureRevision,\n part: string,\n form: FastenForm,\n change: FastenChange\n): FastenForm {\n const f = form\n if ('targetFace' in change) return keptGapOf(revision, part, aimFasten(revision, part, f, { targetFace: change.targetFace as FigureFace }))\n if ('onSurface' in change) return keptGapOf(revision, part, aimFasten(revision, part, f, { surface: true }))\n if ('surface' in change) return keptGapOf(revision, part, { ...f, surface: change.surface })\n if ('mode' in change) return keptGapOf(revision, part, { ...f, facing: change.mode })\n if ('gap' in change) return { ...f, gap: change.gap, gapKept: undefined, gapFrom: 'typed' }\n return {\n ...f,\n align: { ...f.align, [change.axis]: change.how },\n alignMm: change.how === 'mm' ? { ...f.alignMm, [change.axis]: change.mm ?? 0 } : f.alignMm\n }\n}\n\n/**\n * The form with the gap that keeps the part where it stands (ADR-0140 stage 4, user 2026-10-20 「현재 상태로 붙이기」):\n * figure-model's `figureFastenKeptGapOf` for what it is aimed at and how it meets it. A gap the author typed, or the 0 a\n * canvas press puts, is left as it is. Nothing aimed at yet, the form is as it was.\n */\nexport function keptGapOf(revision: FigureRevision, part: string, form: FastenForm): FastenForm {\n if (form.gapFrom !== 'kept') return form\n const asset = revision.asset\n const theirFaces = form.target ? facesOf(asset, form.target) : FACES\n const targetFace = (theirFaces.includes(form.targetFace) ? form.targetFace : theirFaces[0]) as FigureFace\n const planeWay = figurePlaneFacingOf(asset)\n const mode: FastenMode = form.toPlane ? (mountingPlaneOf(asset).facings.includes(form.facing) ? form.facing : (planeWay ?? 'meet')) : form.facing\n const to = form.toPlane\n ? ('plane' as const)\n : form.target && form.surface && surfaceOf(asset, form.target)\n ? { part: form.target, surface: form.surface }\n : form.target\n ? { part: form.target, face: targetFace }\n : undefined\n if (!to) return form\n return { ...form, gap: figureFastenKeptGapOf(asset, part, to, mode, { ...revision.inputs }), gapKept: undefined }\n}\n\n/** What a form comes to: the face aimed at, the way, the drawing of the face and the command. */\nexport interface FastenPlan {\n /** Nothing is aimed at until a part or the plane is chosen. */\n aim?: FastenTarget\n theirFaces: string[]\n targetFace: FigureFace\n mode: FastenMode\n view?: FaceView\n /** The axes across the face asked in rows: where the drawing cannot set the place. */\n alignRows: { axis: Axis; edges: boolean }[]\n /**\n * Every axis across the face, the drawing's too: the details ask each by a number as well (review 2026-11-11: a thigh\n * dragged to a pelvis's edge overshot by 47 mm, and dragged back by eye).\n */\n alignAxes?: { axis: Axis; edges: boolean }[]\n action?: FigureAuthoringAction\n /** The face the command would meet, lit on the canvas while it is tried. */\n face?: { part: string; face: string }\n /** On a point of the target's curved surface: which surface, about which axis, and where (ADR-0140 3.9). */\n surface?: { form: 'around' | 'toward'; axis: Axis; place: FigureSurfacePlace }\n /** The target's own face at each of the drawing's places, as the target is drawn (\\`facesAsDrawnOf\\`). */\n drawnFaces?: Record<FigureFace, FigureFace>\n}\n\nexport function fastenPlanOf(revision: FigureRevision, part: string, f: FastenForm): FastenPlan {\n const plan = fastenPlanWithout(revision, part, f)\n return f.target && !f.toPlane ? { ...plan, drawnFaces: facesAsDrawnOf(revision, f.target) } : plan\n}\n\nfunction fastenPlanWithout(revision: FigureRevision, part: string, f: FastenForm): FastenPlan {\n const asset = revision.asset\n const theirFaces = f.target ? facesOf(asset, f.target) : FACES\n const targetFace = (theirFaces.includes(f.targetFace) ? f.targetFace : theirFaces[0]) as FigureFace\n const aim: FastenTarget | undefined = f.toPlane\n ? 'plane'\n : f.target\n ? { part: f.target, face: targetFace }\n : undefined\n /* The plane takes a part standing on it or hanging below it, a wall only one standing out of it (ADR-0140 3.11). */\n const planeWay = figurePlaneFacingOf(asset)\n const mode: FastenMode = f.toPlane ? (mountingPlaneOf(asset).facings.includes(f.facing) ? f.facing : (planeWay ?? 'meet')) : f.facing\n const surface = !f.toPlane && f.target && f.surface ? surfaceOf(asset, f.target) : undefined\n if (surface) {\n /* On the surface: where is the surface's own words, so no alignment goes with it (ADR-0140 3.9). */\n const action = {\n kind: 'attach',\n part,\n to: { part: f.target, surface: f.surface },\n facing: mode,\n ...fastenGapOf(f),\n replace: !!heldBy(asset, part)\n } as FigureAuthoringAction\n return { theirFaces, targetFace, mode, alignRows: [], action, surface: { ...surface, place: f.surface! } }\n }\n if (!aim) return { theirFaces, targetFace, mode, alignRows: [] }\n const align = alignOf(asset, aim, f.align, f.alignMm, f.alignInput)\n const view = faceViewOf(revision, part, aim, mode)\n const action = {\n kind: 'attach',\n part,\n to: f.toPlane ? { plane: 'mounting-plane' } : { part: f.target, face: targetFace },\n /* On the plane the kernel takes the placement's way when none is said: only another way is said. */\n ...(f.toPlane ? (mode === planeWay ? {} : { facing: mode }) : { facing: mode }),\n ...fastenGapOf(f),\n ...(align ? { align } : {}),\n replace: !!heldBy(asset, part)\n } as FigureAuthoringAction\n return {\n aim,\n theirFaces,\n targetFace,\n mode,\n view,\n alignRows: alignRowsOf(asset, aim, view, f.alignInput),\n alignAxes: alignAxesOf(asset, aim),\n action,\n ...(f.toPlane ? {} : { face: { part: f.target, face: targetFace } })\n }\n}\n\ntype Say = (key: string, params?: Record<string, string>) => string\n\n/** 「bin의 윗면에 올려놓기」: what the fastening is, in one line -- the part is the one the window is open on, not named again. */\nexport function fastenSentenceOf(\n form: FastenForm,\n plan: FastenPlan,\n planeWord: string,\n say: Say\n): string {\n const where = plan.surface ? say('figure.label.surface-' + plan.surface.form) : say('figure.label.face-' + plan.targetFace)\n const to = form.toPlane ? planeWord : `${form.target}의 ${where}`\n return say(`figure.text.fasten-sentence-${plan.mode}`, { to })\n}\n\n/**\n * What the folded details hold that is not their default, said while folded; nothing for the default. The gap's default\n * is the one it starts at -- the distance the part stands at, or 0 where the canvas was pressed -- so only a number typed\n * is said.\n */\nexport function fastenDetailWordsOf(form: FastenForm, say: Say): string[] {\n return [\n ...(form.gapKept !== undefined\n ? [say('figure.text.fasten-summary-gap-worked-out')]\n : form.gapFrom === 'typed' && form.gap\n ? [say('figure.text.fasten-summary-gap', { mm: String(form.gap) })]\n : [])\n ]\n}\n\n/**\n * A part pressed on the canvas while the window is open (ADR-0140 stage 4): aimed at, on the face or the point pressed,\n * its middle where it was pressed -- the kernel's reading of the press (`figureFastenTargetAt`), the point and normal brought into that part's own\n * frame by the pose its body is drawn at (`at.id`, a row's copy too). The part itself, one hanging from it, or one with\n * nothing to fasten to is not aimed at; a press on a curve there is no fastening to aims at the part alone. Each says\n * why, by its words' key.\n */\n/* Put where the canvas was pressed: on the face or the surface there, with no gap (ADR-0140 stage 4). */\nconst PRESSED = { gap: 0, gapKept: undefined, gapFrom: 'pressed' as const }\n\nexport function aimFastenAt(\n revision: FigureRevision,\n part: string,\n form: FastenForm,\n at: { id: string; point?: readonly number[]; normal?: readonly number[] }\n): { form?: FastenForm; note?: 'aim-self' | 'aim-cannot' | 'target-would-loop' | 'aim-not-here'; part: string } {\n const target = ownerOf(at.id)\n const offered = fastenTargetsOf(revision, part).find(t => t.part === target)\n if (target === part) return { note: 'aim-self', part: target }\n if (!offered) return { note: 'aim-cannot', part: target }\n if (offered.loops) return { note: 'target-would-loop', part: target }\n const aimed = aimFasten(revision, part, form, { part: target })\n const pose = (revision.geometry as Array<{ id: string; pose?: { r: number[][]; t: number[] } }>).find(g => g.id === at.id)?.pose\n if (!pose || !at.point || !at.normal) return { form: aimed, part: target }\n /* Into the part's own frame: rᵀ (p − t), and the normal by rᵀ. */\n const own = (v: readonly number[], from: readonly number[] = [0, 0, 0]) => [0, 1, 2].map(c => [0, 1, 2].reduce((sum, i) => sum + pose.r[i]![c]! * (v[i]! - from[i]!), 0))\n const lands = figureFastenTargetAt(revision.asset, target, own(at.point, pose.t), own(at.normal), { ...revision.inputs })\n if ('face' in lands) {\n /* On the face where it was pressed, as a point of a curved surface is: its middle there. */\n const onFace = aimFasten(revision, part, { ...aimed, surface: undefined }, { targetFace: lands.face as FigureFace })\n const at = lands.at as Partial<Record<Axis, number>>\n return { form: { ...onFace, ...PRESSED, align: Object.fromEntries(Object.keys(at).map(a => [a, 'mm'])) as FastenForm['align'], alignMm: { ...at }, alignInput: {} }, part: target }\n }\n if ('surface' in lands) return { form: { ...aimed, ...PRESSED, toPlane: false, target, surface: lands.surface, align: {}, alignMm: {}, alignInput: {} }, part: target }\n return { form: aimed, note: 'aim-not-here', part: target }\n}\n"]}
@@ -111,7 +111,8 @@ export declare function fastenLabelsOf(face: {
111
111
  /**
112
112
  * How much of an axis the face drawing shows, mm: room around the face for the part to go past its edges by about half
113
113
  * itself, and for a part bigger than the face lined up with one of its edges (a mast flush with a low body's bottom) to
114
- * show whole; a plane with no size, three parts wide.
114
+ * show whole; a plane with no size, three parts wide. A part standing `at` mm off the centre is reached too, its far
115
+ * edge inside (roll cradle tutorial 2026-11-12: a box left far up had its size label cut off the drawing).
115
116
  */
116
- export declare function drawingSpanOf(face: number | undefined, part: number): number;
117
+ export declare function drawingSpanOf(face: number | undefined, part: number, at?: number): number;
117
118
  export {};
@@ -151,9 +151,11 @@ export function fastenLabelsOf(face, part, partName, height) {
151
151
  /**
152
152
  * How much of an axis the face drawing shows, mm: room around the face for the part to go past its edges by about half
153
153
  * itself, and for a part bigger than the face lined up with one of its edges (a mast flush with a low body's bottom) to
154
- * show whole; a plane with no size, three parts wide.
154
+ * show whole; a plane with no size, three parts wide. A part standing `at` mm off the centre is reached too, its far
155
+ * edge inside (roll cradle tutorial 2026-11-12: a box left far up had its size label cut off the drawing).
155
156
  */
156
- export function drawingSpanOf(face, part) {
157
- return face !== undefined ? Math.max(face + part, 2 * part - face) : part * 3;
157
+ export function drawingSpanOf(face, part, at = 0) {
158
+ const room = face !== undefined ? Math.max(face + part, 2 * part - face) : part * 3;
159
+ return Math.max(room, 2 * Math.abs(at) + part);
158
160
  }
159
161
  //# sourceMappingURL=fasten-geometry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fasten-geometry.js","sourceRoot":"","sources":["../../client/modeller/fasten-geometry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAG9I,OAAO,EAAE,WAAW,EAAkB,MAAM,YAAY,CAAA;AAexD,mFAAmF;AACnF,MAAM,OAAO,GAA+B,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAA;AAC1G,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAiB,CAAA;AAEhD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,QAAwB,EAAE,MAAc;IACrE,MAAM,KAAK,GAAI,QAAQ,CAAC,QAA4D,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;IACxH,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAmC,CAAA;IACvF,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IACvB,MAAM,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAW,CAAA;IAC3C,sEAAsE;IACtE,MAAM,KAAK,GAAG,CAAC,CAAa,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAS,CAAC,CAAE,CAAC,CAAA;IAC5G,MAAM,KAAK,GAAG,CAAC,KAAiB,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,YAAY,CAAC,KAAK,CAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACxH,MAAM,GAAG,GAAG,CAAC,CAAW,EAAE,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC,CAAA;IACrF,OAAO,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAmC,CAAA;AAC5L,CAAC;AAeD,MAAM,UAAU,UAAU,CAAC,QAAwB,EAAE,IAAY,EAAE,MAAoB,EAAE,MAAkB;IACzG,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE;QACpB,IAAI,CAAC;YACH,OAAO,mBAAmB,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;QAC3F,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAA;QAClB,CAAC;IACH,CAAC,CAAC,EAAE,CAAA;IACJ,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAA;IAC9B,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAClD,MAAM,IAAI,GAAkC,EAAE,CAAA;IAC9C,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAC1B,IAAI,CAAC;YAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAA;IAC9B,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,CAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACvE,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI;QACJ,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE;QACxD,KAAK,EAAE,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAA0B;QACvH,OAAO;KACR,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,GAA0B,EAAE,EAAsB,EAAE,IAAwB,EAAE,IAAY;IACjH,MAAM,KAAK,GAAG,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;IACtI,OAAO,mBAAmB,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,CAAE,CAAA;AAC9F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,CAAS,EAAE,IAAwB,EAAE,IAAY,EAAE,KAAc,EAAE,OAAe;IAC5G,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,OAAO;QAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAA;IACtE,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAC9B,OAAO,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAA;QAC7B,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI;YACtB,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC;YAChB,CAAC,MAAM,EAAE,KAAK,CAAC;SACP;YACR,qHAAqH;YACrH,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,OAAO,IAAI,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAA;QACjJ,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC;YAAE,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,OAAO;gBAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;IACpI,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;AACzC,CAAC;AAED,2HAA2H;AAC3H,SAAS,aAAa,CAAC,IAAc,EAAE,KAAuC,EAAE,OAAsC;IACpH,MAAM,GAAG,GAAiD,EAAE,CAAA;IAC5D,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAA;QAChC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAA;IACtH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAA0B,EAC1B,KAAuC,EACvC,OAAsC;IAEtC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAA;IAC1C,MAAM,GAAG,GAAG,uBAAuB,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;IAClG,IAAI,QAAQ,IAAI,GAAG;QAAE,OAAO,GAAG,CAAA;IAC/B,MAAM,IAAI,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAe,EAAE,CAAA;IACzF,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,KAAe,EAAE,CAAC;QACpC,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACxB,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;aACnF,IAAI,GAAG;YAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAA;IACnC,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAc,EAAE,EAAwB;IACjE,MAAM,CAAC,GAAG,oBAAoB,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IACxG,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAgE,EAAE,CAAA;AAC5H,CAAC;AAED,mGAAmG;AACnG,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAkB,EAAE,IAAY,EAAE,EAAE,CAC/D,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAA;AAE/I;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAA2B;IAClD,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAA;IACxC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAE,CAAA;IAClC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAA0B,EAAE,CAAA;AAC9F,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAoD,EACpD,IAAoD,EACpD,QAAgB,EAChB,MAAc;IAEd,MAAM,SAAS,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAA;IACjG,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;IAC9D,IAAI,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAA;IAChH,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,EAAE,CAAA;IAClC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAA;IAC5B,IAAI,KAAK,GAAG,EAAE,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,IAAI,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,IAAI,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAA;IACvK,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK,IAAI,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAA;AAC3H,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,IAAwB,EAAE,IAAY;IAClE,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAA;AAC/E,CAAC","sourcesContent":["import { FIGURE_FACES, figureFaceInsideAlignOf, figureFaceMeasureOf, figureFaceOverhangAt, figureFastenPlaceOf } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureFace, FigureFaceMeasure, FigureFacing, FigureFastenAlignment } from '@hatiolab/figure-model'\n\nimport { alignAxesOf, type Alignment } from './links.js'\nimport type { FigureRevision } from './revision.js'\n\n/*\n * What the 붙이기 drawing reads (user 2026-10-01): the face a part is fastened to, seen from above, and where the part\n * lies on it. A fastening is chosen by the target's face and how (올려놓기 · 같은 면에 맞추기 · 가운데를 면에); the part\n * meets it by its real shape in the pose it has, by no face of its own (ADR-0140 3.2). What is measured -- the face,\n * the part's extent across it, how far it comes past and whether what touches is on it -- is the kernel's\n * (`figureFaceMeasureOf`, 3.8); this only draws it.\n */\n\ntype Axis = 'x' | 'y' | 'z'\nexport type FastenMode = FigureFacing\nexport type FastenTarget = { part: string; face: string } | 'plane'\n\n/* Which way each of the six faces looks, along its axis, in a part's own frame. */\nconst OUTWARD: Record<FigureFace, number> = { left: -1, right: 1, bottom: -1, top: 1, back: -1, front: 1 }\nconst SIX = Object.keys(OUTWARD) as FigureFace[]\n\n/**\n * The target's own face that stands at each of the drawing's six places as the part is drawn (robot dog, 2026-11-03):\n * a face is named in its part's own frame, so a cylinder laid on its side has its 「아래」 on the side, and the box drawn\n * upright showed the name where the face is not. Each place -- up, down, right, left, toward the eye, away -- shows the\n * own face whose outward normal, turned by the part's pose, points most that way. Unturned, each is itself.\n */\nexport function facesAsDrawnOf(revision: FigureRevision, target: string): Record<FigureFace, FigureFace> {\n const drawn = (revision.geometry as Array<{ id: string; pose?: { r: number[][] } }>).find(g => g.id === target)?.pose?.r\n const same = Object.fromEntries(SIX.map(f => [f, f])) as Record<FigureFace, FigureFace>\n if (!drawn) return same\n const index = { x: 0, y: 1, z: 2 } as const\n /* World direction of an own face: its column of the pose, outward. */\n const world = (f: FigureFace) => [0, 1, 2].map(i => OUTWARD[f] * drawn[i]![index[FIGURE_FACES[f] as Axis]]!)\n const along = (place: FigureFace) => [0, 1, 2].map(i => (i === index[FIGURE_FACES[place] as Axis] ? OUTWARD[place] : 0))\n const dot = (a: number[], b: number[]) => a.reduce((sum, v, i) => sum + v * b[i]!, 0)\n return Object.fromEntries(SIX.map(place => [place, SIX.reduce((best, f) => (dot(world(f), along(place)) > dot(world(best), along(place)) ? f : best))])) as Record<FigureFace, FigureFace>\n}\n\n/**\n * The face seen from above: its two axes across (as the form's alignment names them), the face's size on each as a\n * fastening lines up to it -- none for a plane with no declared size -- the part's real extent on each, whether its\n * edges can be lined up with, and the kernel's measure the drawing asks again as the part is moved.\n */\nexport interface FaceView {\n axes: [Axis, Axis]\n face: Partial<Record<Axis, number>>\n part: Record<Axis, number>\n edges: Record<Axis, boolean>\n measure: FigureFaceMeasure\n}\n\nexport function faceViewOf(revision: FigureRevision, part: string, target: FastenTarget, facing: FastenMode): FaceView | undefined {\n const measure = (() => {\n try {\n return figureFaceMeasureOf(revision.asset, part, revision.inputs, { on: target, facing })\n } catch {\n return undefined\n }\n })()\n if (!measure) return undefined\n const across = alignAxesOf(revision.asset, target)\n const face: Partial<Record<Axis, number>> = {}\n for (const a of measure.axes) {\n const e = measure.edges[a]\n if (e) face[a] = e.hi - e.lo\n }\n const extent = (a: Axis) => measure.extent[a].hi - measure.extent[a].lo\n return {\n axes: measure.axes,\n face,\n part: { x: extent('x'), y: extent('y'), z: extent('z') },\n edges: Object.fromEntries(measure.axes.map(a => [a, !!across.find(o => o.axis === a)?.edges])) as Record<Axis, boolean>,\n measure\n }\n}\n\n/**\n * Where the part's centre is from the face's centre along one axis, for the way it is aligned there: the kernel's\n * `figureFastenPlaceOf` (ADR-0140 D2), the form's distances -- typed, or a share of the size read as millimetres -- as\n * a distance, a position field's as the centre.\n */\nexport function offsetOf(how: Alignment | undefined, mm: number | undefined, face: number | undefined, part: number): number {\n const align = how === 'mm' || how === 'share' ? { mm: mm ?? 0 } : how === 'min' || how === 'max' || how === 'centre' ? how : undefined\n return figureFastenPlaceOf({ align: { x: align }, face: { x: face }, part: { x: part } }).x!\n}\n\n/**\n * A place dropped on the face, as the form's alignment: caught at the centre, at an edge from inside (level with it:\n * `min` / `max`, where the edges can be lined up with), or at an edge from outside (touching it, as a distance), when\n * within `catchMm`; anywhere else a distance off centre, whole millimetres.\n */\nexport function alignmentAt(d: number, face: number | undefined, part: number, edges: boolean, catchMm: number): { how: Alignment; mm?: number; caught?: 'centre' | 'inside' | 'outside' } {\n if (Math.abs(d) <= catchMm) return { how: 'centre', caught: 'centre' }\n if (face !== undefined) {\n const inside = (face - part) / 2,\n outside = (face + part) / 2\n for (const [at, how] of [\n [-inside, 'min'],\n [inside, 'max']\n ] as const)\n /* Level with an edge whatever the sizes: a part bigger than the face (a mast on a low body) lines up with it too. */\n if (Math.abs(d - at) <= catchMm && inside !== 0) return edges ? { how, caught: 'inside' } : { how: 'mm', mm: Math.round(at), caught: 'inside' }\n for (const at of [-outside, outside]) if (Math.abs(d - at) <= catchMm) return { how: 'mm', mm: Math.round(at), caught: 'outside' }\n }\n return { how: 'mm', mm: Math.round(d) }\n}\n\n/** The form's alignment on each axis across the face, in the kernel's words: a typed or shared distance as millimetres. */\nfunction kernelAlignOf(view: FaceView, align: Partial<Record<Axis, Alignment>>, alignMm: Partial<Record<Axis, number>>): Partial<Record<Axis, FigureFastenAlignment>> {\n const out: Partial<Record<Axis, FigureFastenAlignment>> = {}\n for (const a of view.axes) {\n const how = align[a] ?? 'centre'\n out[a] = how === 'mm' || how === 'share' ? { mm: alignMm[a] ?? 0 } : how === 'min' || how === 'max' ? how : 'centre'\n }\n return out\n}\n\n/**\n * The alignment that brings the part inside the face, for 정렬·간격 고치기: the kernel's least move\n * (`figureFaceInsideAlignOf`), in the form's words. `inside` when nothing of it is past an edge: what is out is something\n * fastened on it. `unmeasured` when the face has no size to measure against.\n */\nexport function insideAlignmentOf(\n view: FaceView | undefined,\n align: Partial<Record<Axis, Alignment>>,\n alignMm: Partial<Record<Axis, number>>\n): { align: Partial<Record<Axis, Alignment>>; alignMm: Partial<Record<Axis, number>>; moved: Axis[] } | { cannot: 'inside' | 'unmeasured' } {\n if (!view) return { cannot: 'unmeasured' }\n const fix = figureFaceInsideAlignOf(view.measure, kernelAlignOf(view, align, alignMm), view.edges)\n if ('cannot' in fix) return fix\n const next = { align: { ...align }, alignMm: { ...alignMm }, moved: fix.moved as Axis[] }\n for (const a of fix.moved as Axis[]) {\n const how = fix.align[a]\n if (how && typeof how === 'object') ((next.align[a] = 'mm'), (next.alignMm[a] = how.mm))\n else if (how) next.align[a] = how\n }\n return next\n}\n\n/**\n * How far the part comes past the face's edges, per side, whether any of it lies over the face, and whether what\n * touches the face is on it -- with its middle `at` from the face's middle: the kernel's (`figureFaceOverhangAt`).\n */\nexport function overhangOf(view: FaceView, at: Record<Axis, number>): { touching: boolean; contact: 'on' | 'off' | 'unknown'; sides: Array<{ axis: Axis; side: 'low' | 'high'; mm: number }> } {\n const o = figureFaceOverhangAt(view.measure, Object.fromEntries(view.axes.map(a => [a, { mm: at[a] }])))\n return { touching: o.over, contact: o.contact, sides: o.sides as Array<{ axis: Axis; side: 'low' | 'high'; mm: number }> }\n}\n\n/** Whether the part turns about a joint: then 가운데를 면에 is offered -- a roller spinning in place. */\nexport const turnsInPlace = (asset: FigureAsset, part: string) =>\n (asset.joints ?? []).some(j => j.body1.part === part && ((j.motion ?? []).some(a => a.type === 'revolute') || j.origin?.facing === 'centre'))\n\n/**\n * How the face lies on the screen: X across when it is one of the face's axes, else Z; the other down the screen -- up\n * for Y, which rises, so a front face is seen as it stands and not upside down.\n */\nexport function screenOf(axes: readonly [Axis, Axis]): { h: Axis; v: Axis; sign: Record<Axis, 1 | -1> } {\n const h = axes.includes('x') ? 'x' : 'z'\n const v = axes.find(a => a !== h)!\n return { h, v, sign: { x: 1, y: 1, z: 1, [v]: v === 'y' ? -1 : 1 } as Record<Axis, 1 | -1> }\n}\n\n/**\n * Where the fastening drawing writes its two names so neither lies over the other (user 2026-10-16: 「roller」 over the\n * roller's thin bar, and 「frame 윗면 2000 × 600」 cut by it). The face's name stands above the face, inside its top edge\n * only when the face fills the drawing. The part's name stands in the part when the part is wide and tall enough for it,\n * else just under it -- above it when the drawing ends first. Under a part as tall as the face, just under it is the\n * face's own edge, and the name lay on that line (2026-10-18, the roller in the fastening window): it goes under the edge.\n * Widths are estimated: 7 px a letter at 11-12 px; a line of it stands 11 px over its baseline and 3 under.\n */\nexport function fastenLabelsOf(\n face: { x: number; y: number; w: number; h: number },\n part: { x: number; y: number; w: number; h: number },\n partName: string,\n height: number\n): { face: { x: number; y: number }; part: { x: number; y: number; inside: boolean } } {\n const facePlace = face.y >= 16 ? { x: face.x, y: face.y - 5 } : { x: face.x + 4, y: face.y + 14 }\n const fits = part.w >= partName.length * 7 + 8 && part.h >= 16\n if (fits) return { face: facePlace, part: { x: part.x + part.w / 2, y: part.y + part.h / 2 + 4, inside: true } }\n const below = part.y + part.h + 14\n const edge = face.y + face.h\n if (below - 11 < edge + 2 && below + 3 > edge - 2 && edge + 14 <= height - 4) return { face: facePlace, part: { x: part.x + part.w / 2, y: edge + 14, inside: false } }\n return { face: facePlace, part: { x: part.x + part.w / 2, y: below <= height - 20 ? below : part.y - 5, inside: false } }\n}\n\n/**\n * How much of an axis the face drawing shows, mm: room around the face for the part to go past its edges by about half\n * itself, and for a part bigger than the face lined up with one of its edges (a mast flush with a low body's bottom) to\n * show whole; a plane with no size, three parts wide.\n */\nexport function drawingSpanOf(face: number | undefined, part: number): number {\n return face !== undefined ? Math.max(face + part, 2 * part - face) : part * 3\n}\n"]}
1
+ {"version":3,"file":"fasten-geometry.js","sourceRoot":"","sources":["../../client/modeller/fasten-geometry.ts"],"names":[],"mappings":"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","sourcesContent":["import { FIGURE_FACES, figureFaceInsideAlignOf, figureFaceMeasureOf, figureFaceOverhangAt, figureFastenPlaceOf } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureFace, FigureFaceMeasure, FigureFacing, FigureFastenAlignment } from '@hatiolab/figure-model'\n\nimport { alignAxesOf, type Alignment } from './links.js'\nimport type { FigureRevision } from './revision.js'\n\n/*\n * What the 붙이기 drawing reads (user 2026-10-01): the face a part is fastened to, seen from above, and where the part\n * lies on it. A fastening is chosen by the target's face and how (올려놓기 · 같은 면에 맞추기 · 가운데를 면에); the part\n * meets it by its real shape in the pose it has, by no face of its own (ADR-0140 3.2). What is measured -- the face,\n * the part's extent across it, how far it comes past and whether what touches is on it -- is the kernel's\n * (`figureFaceMeasureOf`, 3.8); this only draws it.\n */\n\ntype Axis = 'x' | 'y' | 'z'\nexport type FastenMode = FigureFacing\nexport type FastenTarget = { part: string; face: string } | 'plane'\n\n/* Which way each of the six faces looks, along its axis, in a part's own frame. */\nconst OUTWARD: Record<FigureFace, number> = { left: -1, right: 1, bottom: -1, top: 1, back: -1, front: 1 }\nconst SIX = Object.keys(OUTWARD) as FigureFace[]\n\n/**\n * The target's own face that stands at each of the drawing's six places as the part is drawn (robot dog, 2026-11-03):\n * a face is named in its part's own frame, so a cylinder laid on its side has its 「아래」 on the side, and the box drawn\n * upright showed the name where the face is not. Each place -- up, down, right, left, toward the eye, away -- shows the\n * own face whose outward normal, turned by the part's pose, points most that way. Unturned, each is itself.\n */\nexport function facesAsDrawnOf(revision: FigureRevision, target: string): Record<FigureFace, FigureFace> {\n const drawn = (revision.geometry as Array<{ id: string; pose?: { r: number[][] } }>).find(g => g.id === target)?.pose?.r\n const same = Object.fromEntries(SIX.map(f => [f, f])) as Record<FigureFace, FigureFace>\n if (!drawn) return same\n const index = { x: 0, y: 1, z: 2 } as const\n /* World direction of an own face: its column of the pose, outward. */\n const world = (f: FigureFace) => [0, 1, 2].map(i => OUTWARD[f] * drawn[i]![index[FIGURE_FACES[f] as Axis]]!)\n const along = (place: FigureFace) => [0, 1, 2].map(i => (i === index[FIGURE_FACES[place] as Axis] ? OUTWARD[place] : 0))\n const dot = (a: number[], b: number[]) => a.reduce((sum, v, i) => sum + v * b[i]!, 0)\n return Object.fromEntries(SIX.map(place => [place, SIX.reduce((best, f) => (dot(world(f), along(place)) > dot(world(best), along(place)) ? f : best))])) as Record<FigureFace, FigureFace>\n}\n\n/**\n * The face seen from above: its two axes across (as the form's alignment names them), the face's size on each as a\n * fastening lines up to it -- none for a plane with no declared size -- the part's real extent on each, whether its\n * edges can be lined up with, and the kernel's measure the drawing asks again as the part is moved.\n */\nexport interface FaceView {\n axes: [Axis, Axis]\n face: Partial<Record<Axis, number>>\n part: Record<Axis, number>\n edges: Record<Axis, boolean>\n measure: FigureFaceMeasure\n}\n\nexport function faceViewOf(revision: FigureRevision, part: string, target: FastenTarget, facing: FastenMode): FaceView | undefined {\n const measure = (() => {\n try {\n return figureFaceMeasureOf(revision.asset, part, revision.inputs, { on: target, facing })\n } catch {\n return undefined\n }\n })()\n if (!measure) return undefined\n const across = alignAxesOf(revision.asset, target)\n const face: Partial<Record<Axis, number>> = {}\n for (const a of measure.axes) {\n const e = measure.edges[a]\n if (e) face[a] = e.hi - e.lo\n }\n const extent = (a: Axis) => measure.extent[a].hi - measure.extent[a].lo\n return {\n axes: measure.axes,\n face,\n part: { x: extent('x'), y: extent('y'), z: extent('z') },\n edges: Object.fromEntries(measure.axes.map(a => [a, !!across.find(o => o.axis === a)?.edges])) as Record<Axis, boolean>,\n measure\n }\n}\n\n/**\n * Where the part's centre is from the face's centre along one axis, for the way it is aligned there: the kernel's\n * `figureFastenPlaceOf` (ADR-0140 D2), the form's distances -- typed, or a share of the size read as millimetres -- as\n * a distance, a position field's as the centre.\n */\nexport function offsetOf(how: Alignment | undefined, mm: number | undefined, face: number | undefined, part: number): number {\n const align = how === 'mm' || how === 'share' ? { mm: mm ?? 0 } : how === 'min' || how === 'max' || how === 'centre' ? how : undefined\n return figureFastenPlaceOf({ align: { x: align }, face: { x: face }, part: { x: part } }).x!\n}\n\n/**\n * A place dropped on the face, as the form's alignment: caught at the centre, at an edge from inside (level with it:\n * `min` / `max`, where the edges can be lined up with), or at an edge from outside (touching it, as a distance), when\n * within `catchMm`; anywhere else a distance off centre, whole millimetres.\n */\nexport function alignmentAt(d: number, face: number | undefined, part: number, edges: boolean, catchMm: number): { how: Alignment; mm?: number; caught?: 'centre' | 'inside' | 'outside' } {\n if (Math.abs(d) <= catchMm) return { how: 'centre', caught: 'centre' }\n if (face !== undefined) {\n const inside = (face - part) / 2,\n outside = (face + part) / 2\n for (const [at, how] of [\n [-inside, 'min'],\n [inside, 'max']\n ] as const)\n /* Level with an edge whatever the sizes: a part bigger than the face (a mast on a low body) lines up with it too. */\n if (Math.abs(d - at) <= catchMm && inside !== 0) return edges ? { how, caught: 'inside' } : { how: 'mm', mm: Math.round(at), caught: 'inside' }\n for (const at of [-outside, outside]) if (Math.abs(d - at) <= catchMm) return { how: 'mm', mm: Math.round(at), caught: 'outside' }\n }\n return { how: 'mm', mm: Math.round(d) }\n}\n\n/** The form's alignment on each axis across the face, in the kernel's words: a typed or shared distance as millimetres. */\nfunction kernelAlignOf(view: FaceView, align: Partial<Record<Axis, Alignment>>, alignMm: Partial<Record<Axis, number>>): Partial<Record<Axis, FigureFastenAlignment>> {\n const out: Partial<Record<Axis, FigureFastenAlignment>> = {}\n for (const a of view.axes) {\n const how = align[a] ?? 'centre'\n out[a] = how === 'mm' || how === 'share' ? { mm: alignMm[a] ?? 0 } : how === 'min' || how === 'max' ? how : 'centre'\n }\n return out\n}\n\n/**\n * The alignment that brings the part inside the face, for 정렬·간격 고치기: the kernel's least move\n * (`figureFaceInsideAlignOf`), in the form's words. `inside` when nothing of it is past an edge: what is out is something\n * fastened on it. `unmeasured` when the face has no size to measure against.\n */\nexport function insideAlignmentOf(\n view: FaceView | undefined,\n align: Partial<Record<Axis, Alignment>>,\n alignMm: Partial<Record<Axis, number>>\n): { align: Partial<Record<Axis, Alignment>>; alignMm: Partial<Record<Axis, number>>; moved: Axis[] } | { cannot: 'inside' | 'unmeasured' } {\n if (!view) return { cannot: 'unmeasured' }\n const fix = figureFaceInsideAlignOf(view.measure, kernelAlignOf(view, align, alignMm), view.edges)\n if ('cannot' in fix) return fix\n const next = { align: { ...align }, alignMm: { ...alignMm }, moved: fix.moved as Axis[] }\n for (const a of fix.moved as Axis[]) {\n const how = fix.align[a]\n if (how && typeof how === 'object') ((next.align[a] = 'mm'), (next.alignMm[a] = how.mm))\n else if (how) next.align[a] = how\n }\n return next\n}\n\n/**\n * How far the part comes past the face's edges, per side, whether any of it lies over the face, and whether what\n * touches the face is on it -- with its middle `at` from the face's middle: the kernel's (`figureFaceOverhangAt`).\n */\nexport function overhangOf(view: FaceView, at: Record<Axis, number>): { touching: boolean; contact: 'on' | 'off' | 'unknown'; sides: Array<{ axis: Axis; side: 'low' | 'high'; mm: number }> } {\n const o = figureFaceOverhangAt(view.measure, Object.fromEntries(view.axes.map(a => [a, { mm: at[a] }])))\n return { touching: o.over, contact: o.contact, sides: o.sides as Array<{ axis: Axis; side: 'low' | 'high'; mm: number }> }\n}\n\n/** Whether the part turns about a joint: then 가운데를 면에 is offered -- a roller spinning in place. */\nexport const turnsInPlace = (asset: FigureAsset, part: string) =>\n (asset.joints ?? []).some(j => j.body1.part === part && ((j.motion ?? []).some(a => a.type === 'revolute') || j.origin?.facing === 'centre'))\n\n/**\n * How the face lies on the screen: X across when it is one of the face's axes, else Z; the other down the screen -- up\n * for Y, which rises, so a front face is seen as it stands and not upside down.\n */\nexport function screenOf(axes: readonly [Axis, Axis]): { h: Axis; v: Axis; sign: Record<Axis, 1 | -1> } {\n const h = axes.includes('x') ? 'x' : 'z'\n const v = axes.find(a => a !== h)!\n return { h, v, sign: { x: 1, y: 1, z: 1, [v]: v === 'y' ? -1 : 1 } as Record<Axis, 1 | -1> }\n}\n\n/**\n * Where the fastening drawing writes its two names so neither lies over the other (user 2026-10-16: 「roller」 over the\n * roller's thin bar, and 「frame 윗면 2000 × 600」 cut by it). The face's name stands above the face, inside its top edge\n * only when the face fills the drawing. The part's name stands in the part when the part is wide and tall enough for it,\n * else just under it -- above it when the drawing ends first. Under a part as tall as the face, just under it is the\n * face's own edge, and the name lay on that line (2026-10-18, the roller in the fastening window): it goes under the edge.\n * Widths are estimated: 7 px a letter at 11-12 px; a line of it stands 11 px over its baseline and 3 under.\n */\nexport function fastenLabelsOf(\n face: { x: number; y: number; w: number; h: number },\n part: { x: number; y: number; w: number; h: number },\n partName: string,\n height: number\n): { face: { x: number; y: number }; part: { x: number; y: number; inside: boolean } } {\n const facePlace = face.y >= 16 ? { x: face.x, y: face.y - 5 } : { x: face.x + 4, y: face.y + 14 }\n const fits = part.w >= partName.length * 7 + 8 && part.h >= 16\n if (fits) return { face: facePlace, part: { x: part.x + part.w / 2, y: part.y + part.h / 2 + 4, inside: true } }\n const below = part.y + part.h + 14\n const edge = face.y + face.h\n if (below - 11 < edge + 2 && below + 3 > edge - 2 && edge + 14 <= height - 4) return { face: facePlace, part: { x: part.x + part.w / 2, y: edge + 14, inside: false } }\n return { face: facePlace, part: { x: part.x + part.w / 2, y: below <= height - 20 ? below : part.y - 5, inside: false } }\n}\n\n/**\n * How much of an axis the face drawing shows, mm: room around the face for the part to go past its edges by about half\n * itself, and for a part bigger than the face lined up with one of its edges (a mast flush with a low body's bottom) to\n * show whole; a plane with no size, three parts wide. A part standing `at` mm off the centre is reached too, its far\n * edge inside (roll cradle tutorial 2026-11-12: a box left far up had its size label cut off the drawing).\n */\nexport function drawingSpanOf(face: number | undefined, part: number, at = 0): number {\n const room = face !== undefined ? Math.max(face + part, 2 * part - face) : part * 3\n return Math.max(room, 2 * Math.abs(at) + part)\n}\n"]}
@@ -2,18 +2,6 @@ import '@material/web/icon/icon.js';
2
2
  import { LitElement, nothing } from 'lit';
3
3
  import type { FigureAsset } from '@hatiolab/figure-model';
4
4
  declare const FigureDataPanel_base: (new (...args: any[]) => LitElement) & typeof LitElement;
5
- /**
6
- * 데이터 -- the data a site sends this figure, in its own names, and what each value moves (ADR-0051 §8, data binding
7
- * design decision 4). The schema is free: fields the author names, a tree of objects and lists. A binding reads one place
8
- * of it and shows it in the figure (ADR-0166): as it is or scaled, it moves one thing -- a control, a colour, a motion
9
- * group's clip, speed or pause, its position, the items in its places; by a code table or a range table, each value's
10
- * look goes to several targets at once -- parts' colour and effect, controls' values, a motion group. The field's kind
11
- * says which methods there are. Every change is one part edit, and whether it holds is the kernel's check.
12
- *
13
- * The sample is not saved: data tried here, read the one way a placed figure reads it, and -- in the preview -- the figure
14
- * running on it (`data-sample`); a sample legend pasted for a table's role shows it as a board's legend would
15
- * (`legend-sample`).
16
- */
17
5
  export declare class FigureDataPanel extends FigureDataPanel_base {
18
6
  private readonly help;
19
7
  static styles: import("lit").CSSResult[];
@@ -21,6 +9,8 @@ export declare class FigureDataPanel extends FigureDataPanel_base {
21
9
  locked: boolean;
22
10
  /** The part chosen in the modeller, put among a table's parts by one button. */
23
11
  selected: string;
12
+ /** Whether the editor shows the preview: the sample shows there only, the edit canvas keeping the figure as made. */
13
+ previewing: boolean;
24
14
  /** The schema row chosen, by its path; its field is edited under it. */
25
15
  private chosen;
26
16
  private adding;
@@ -37,20 +27,22 @@ export declare class FigureDataPanel extends FigureDataPanel_base {
37
27
  /** The sample as typed, JSON; empty until edited, the schema's example shown until then. */
38
28
  private sample;
39
29
  private sampling;
40
- render(): import("lit-html").TemplateResult<1> | typeof nothing;
30
+ render(): typeof nothing | import("lit-html").TemplateResult<1>;
41
31
  /** The data a product's capability the figure wears reads itself (ADR-0167 14): said, read only. */
42
32
  private renderCapabilityData;
43
33
  private renderSchema;
44
- /** A field's kind in the words every screen says it in (figure-viewer kindWords). */
45
- private kindWord;
46
34
  /** The chosen field's own settings, and a field added under it when it holds fields. */
47
35
  private renderField;
48
36
  private renderAddField;
49
37
  /** A site's data pasted: the schema guessed from it, kept only when the author says so -- units, ranges and closed values are theirs to set. */
50
38
  private renderFromSample;
39
+ /** What a binding moves: a table's parts, a value's behavior -- one wording for the binding's line and the field's row. */
40
+ private bindingWhat;
51
41
  /** What a behavior moves, in the words every screen says it in (figure-viewer dataMoveOf · moveWords). */
52
42
  private behaviorName;
53
- private renderBindings;
43
+ private renderFieldBindings;
44
+ /** A binding no field reads -- its field renamed or taken out -- kept in sight below the fields, to delete or fix. */
45
+ private renderStrayBindings;
54
46
  private renderBinding;
55
47
  /**
56
48
  * A binding a product's capability takes over where the product registers it (ADR-0167 17): there it rests, whole or for
@@ -63,12 +55,12 @@ export declare class FigureDataPanel extends FigureDataPanel_base {
63
55
  * with a cell per column, the look of every other value, and whether a board may swap it for a legend.
64
56
  */
65
57
  private renderTable;
66
- /** Which parts, controls and motion group a table shows on: chips to tick, and the part chosen in the modeller by one button. */
67
58
  private renderTargets;
68
59
  /** The column heads of a table's rows, after the key's own one or two. */
69
60
  private columnHeads;
70
61
  /** One look's cells, a column each: a colour, an effect, a control's value, a clip, a speed, a pause -- empty leaves the target as it is. */
71
62
  private lookCells;
63
+ private renderLook;
72
64
  /** The look of every other value: a row like the others, added or taken away; kept when a closed field's values are not all rows. */
73
65
  private renderOtherwise;
74
66
  /**
@@ -87,6 +79,7 @@ export declare class FigureDataPanel extends FigureDataPanel_base {
87
79
  * 「연결 추가」 (ADR-0166 10): a field there or a new name, then a method the field's kind allows. As it is or scaled,
88
80
  * the one thing it moves; a table, the parts, controls and motion group it shows on -- a new name makes the field.
89
81
  */
82
+ /** A binding added to the field whose row is open: how it is read, and what it moves. */
90
83
  private renderAddBinding;
91
84
  /**
92
85
  * 「+ 회전」: a joint that turns without end, turned by the data -- on, speed in rpm, direction -- in one step. The
@@ -95,8 +88,6 @@ export declare class FigureDataPanel extends FigureDataPanel_base {
95
88
  private renderRotation;
96
89
  /** Data to try, read the way a placed figure reads it: what it moves, or why it is refused; in the preview, the figure running on it. */
97
90
  private renderSample;
98
- /** Which row of a table the sample's value matched, and from which table: the figure's own or the sample legend of its role. */
99
- private renderTableUse;
100
91
  /** The sample legends as read: by role, those that are JSON. */
101
92
  private sampleLegends;
102
93
  /**