@things-factory/figure-ui 10.1.203 → 10.1.205
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/modeller/canvas.ts +91 -9
- package/client/modeller/editor-styles.ts +7 -22
- package/client/modeller/editor.ts +176 -145
- package/client/modeller/fasten-form.ts +91 -9
- package/client/modeller/figure-fasten-dialog.ts +42 -9
- package/client/modeller/figure-fasten-drawing.ts +233 -12
- package/client/modeller/figure-section-editor.ts +6 -6
- package/client/modeller/figure-slot-layout-panel.ts +1 -1
- package/client/modeller/gizmo.ts +12 -1
- package/client/modeller/links.ts +48 -6
- package/client/modeller/section-drawing.ts +7 -7
- package/client/modeller/size-drag.ts +112 -0
- package/client/modeller/slot-layout-host.ts +3 -3
- package/client/modeller/stage-model.ts +6 -9
- package/client/pages/figure-modeller-page.ts +1 -4
- package/dist-client/modeller/canvas.d.ts +24 -3
- package/dist-client/modeller/canvas.js +96 -11
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +7 -22
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +28 -32
- package/dist-client/modeller/editor.js +176 -153
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +36 -3
- package/dist-client/modeller/fasten-form.js +86 -8
- package/dist-client/modeller/fasten-form.js.map +1 -1
- package/dist-client/modeller/figure-fasten-dialog.d.ts +17 -3
- package/dist-client/modeller/figure-fasten-dialog.js +47 -10
- package/dist-client/modeller/figure-fasten-dialog.js.map +1 -1
- package/dist-client/modeller/figure-fasten-drawing.d.ts +33 -1
- package/dist-client/modeller/figure-fasten-drawing.js +235 -11
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
- package/dist-client/modeller/figure-section-editor.d.ts +3 -3
- package/dist-client/modeller/figure-section-editor.js +4 -4
- package/dist-client/modeller/figure-section-editor.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
- package/dist-client/modeller/gizmo.d.ts +12 -1
- package/dist-client/modeller/gizmo.js +8 -0
- package/dist-client/modeller/gizmo.js.map +1 -1
- package/dist-client/modeller/links.d.ts +14 -2
- package/dist-client/modeller/links.js +42 -4
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/section-drawing.d.ts +3 -4
- package/dist-client/modeller/section-drawing.js +10 -9
- package/dist-client/modeller/section-drawing.js.map +1 -1
- package/dist-client/modeller/size-drag.d.ts +38 -0
- package/dist-client/modeller/size-drag.js +85 -0
- package/dist-client/modeller/size-drag.js.map +1 -0
- package/dist-client/modeller/slot-layout-host.d.ts +1 -3
- package/dist-client/modeller/slot-layout-host.js +3 -3
- package/dist-client/modeller/slot-layout-host.js.map +1 -1
- package/dist-client/modeller/stage-model.d.ts +5 -7
- package/dist-client/modeller/stage-model.js +6 -6
- package/dist-client/modeller/stage-model.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +0 -2
- package/dist-client/pages/figure-modeller-page.js +1 -6
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/test/align.test.ts +7 -11
- package/test/browser/acceptance-page.js +10 -0
- package/test/browser/editing.check.mjs +17 -13
- package/test/browser/links-page.js +2 -2
- package/test/browser/links.check.mjs +94 -56
- package/test/browser/modeller-page.js +30 -18
- package/test/browser/modeller-revision.check.mjs +39 -0
- package/test/browser/modeller.check.mjs +32 -10
- package/test/browser/screen-page.js +0 -2
- package/test/browser/screen.check.mjs +11 -13
- package/test/browser/section.check.mjs +2 -2
- package/test/fasten-form.test.ts +82 -2
- package/test/gizmo.test.ts +13 -15
- package/test/links.test.ts +8 -3
- package/test/resize-with-placement.test.ts +14 -13
- package/test/revision.test.ts +3 -2
- package/test/section-editor.test.ts +9 -10
- package/test/size-drag.test.ts +114 -0
- package/test/slot-layout-host.test.ts +13 -8
- package/translations/en.json +25 -22
- package/translations/es.json +25 -22
- package/translations/ja.json +25 -22
- package/translations/ko.json +25 -22
- package/translations/ms.json +25 -22
- package/translations/zh.json +25 -22
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FigureAuthoringAction, FigureFace } from '@hatiolab/figure-model';
|
|
1
|
+
import type { FigureAuthoringAction, FigureFace, FigureSurfacePlace } from '@hatiolab/figure-model';
|
|
2
2
|
import { type FaceView, type FastenMode, type FastenTarget } from './fasten-geometry.js';
|
|
3
3
|
import type { FastenChange } from './figure-fasten-drawing.js';
|
|
4
4
|
import { type Alignment, type Axis } from './links.js';
|
|
@@ -15,6 +15,8 @@ export interface FastenForm {
|
|
|
15
15
|
align: Partial<Record<Axis, Alignment>>;
|
|
16
16
|
alignMm: Partial<Record<Axis, number>>;
|
|
17
17
|
alignInput: Partial<Record<Axis, string>>;
|
|
18
|
+
/** On a point of the target's curved surface instead of a face (ADR-0140 3.9): where, as the record says it. */
|
|
19
|
+
surface?: FigureSurfacePlace;
|
|
18
20
|
}
|
|
19
21
|
export declare const freshFasten: () => FastenForm;
|
|
20
22
|
/** 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). */
|
|
@@ -25,8 +27,15 @@ export declare function fastenTargetsOf(revision: FigureRevision, part: string):
|
|
|
25
27
|
loops: boolean;
|
|
26
28
|
}>;
|
|
27
29
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
+
* The point of a target's curved surface on the side the part stands on now (ADR-0140 3.9): the kernel's
|
|
31
|
+
* (`figureSurfaceKeptPlaceOf`), or, the part standing on its axis, the surface's first point.
|
|
32
|
+
*/
|
|
33
|
+
export declare function surfaceKeptOf(revision: FigureRevision, part: string, target: string): FigureSurfacePlace | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* A new aim: the plane, another part, another face of the same part, or a point of its curved surface. Where on the
|
|
36
|
+
* face -- or the surface -- is filled in as where the part stands now, so fastening it keeps it there (`placeKeptOf`,
|
|
37
|
+
* `surfaceKeptOf`); the author changes it from there. A part aimed at a sphere, or beside a cylinder's side within its
|
|
38
|
+
* length, is aimed at the surface: it is fastened as it stands (ADR-0140 3.6).
|
|
30
39
|
*/
|
|
31
40
|
export declare function aimFasten(revision: FigureRevision, part: string, form: FastenForm, to: {
|
|
32
41
|
plane: true;
|
|
@@ -34,6 +43,8 @@ export declare function aimFasten(revision: FigureRevision, part: string, form:
|
|
|
34
43
|
part: string;
|
|
35
44
|
} | {
|
|
36
45
|
targetFace: FigureFace;
|
|
46
|
+
} | {
|
|
47
|
+
surface: true;
|
|
37
48
|
}): FastenForm;
|
|
38
49
|
/** A change the drawing asks: a face, a way, a gap or a place across the face. */
|
|
39
50
|
export declare function fastenChanged(revision: FigureRevision, part: string, form: FastenForm, change: FastenChange): FastenForm;
|
|
@@ -56,6 +67,12 @@ export interface FastenPlan {
|
|
|
56
67
|
part: string;
|
|
57
68
|
face: string;
|
|
58
69
|
};
|
|
70
|
+
/** On a point of the target's curved surface: which surface, about which axis, and where (ADR-0140 3.9). */
|
|
71
|
+
surface?: {
|
|
72
|
+
form: 'around' | 'toward';
|
|
73
|
+
axis: Axis;
|
|
74
|
+
place: FigureSurfacePlace;
|
|
75
|
+
};
|
|
59
76
|
}
|
|
60
77
|
export declare function fastenPlanOf(revision: FigureRevision, part: string, f: FastenForm): FastenPlan;
|
|
61
78
|
type Say = (key: string, params?: Record<string, string>) => string;
|
|
@@ -63,4 +80,20 @@ type Say = (key: string, params?: Record<string, string>) => string;
|
|
|
63
80
|
export declare function fastenSentenceOf(form: FastenForm, plan: FastenPlan, planeWord: string, say: Say): string;
|
|
64
81
|
/** What the folded details hold that is not their default, said while folded; nothing for the default. */
|
|
65
82
|
export declare function fastenDetailWordsOf(form: FastenForm, say: Say): string[];
|
|
83
|
+
/**
|
|
84
|
+
* A part pressed on the canvas while the window is open (ADR-0140 stage 4): aimed at, on the face or the point pressed,
|
|
85
|
+
* its middle where it was pressed -- the kernel's reading of the press (`figureFastenTargetAt`), the point and normal brought into that part's own
|
|
86
|
+
* 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
|
|
87
|
+
* 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
|
|
88
|
+
* why, by its words' key.
|
|
89
|
+
*/
|
|
90
|
+
export declare function aimFastenAt(revision: FigureRevision, part: string, form: FastenForm, at: {
|
|
91
|
+
id: string;
|
|
92
|
+
point?: readonly number[];
|
|
93
|
+
normal?: readonly number[];
|
|
94
|
+
}): {
|
|
95
|
+
form?: FastenForm;
|
|
96
|
+
note?: 'aim-self' | 'aim-cannot' | 'target-would-loop' | 'aim-not-here';
|
|
97
|
+
part: string;
|
|
98
|
+
};
|
|
66
99
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { FIGURE_FACES } from '@hatiolab/figure-model';
|
|
1
|
+
import { FIGURE_FACES, figureFastenTargetAt, figurePlaneFacingOf, figureSurfaceKeptPlaceOf } from '@hatiolab/figure-model';
|
|
2
2
|
import { faceViewOf } from './fasten-geometry.js';
|
|
3
|
-
import { alignOf, alignRowsOf, canFasten, facesOf, fasteningOf, fastenGapOf, hangsFrom, heldBy, mountingPlaneOf, placeKeptOf } from './links.js';
|
|
3
|
+
import { alignOf, alignRowsOf, canFasten, facesOf, fasteningOf, fastenGapOf, hangsFrom, heldBy, mountingPlaneOf, placeKeptOf, surfaceOf } from './links.js';
|
|
4
|
+
import { ownerOf } from './fields.js';
|
|
4
5
|
/*
|
|
5
6
|
* A fastening as it is chosen, before it is done: to what, which face, how, where across the face. The fastening window
|
|
6
7
|
* (`figure-fasten-dialog.ts`) holds one and changes it as the author chooses; the attach command is worked out from it
|
|
@@ -33,12 +34,36 @@ export function fastenTargetsOf(revision, part) {
|
|
|
33
34
|
.map(one => ({ part: one, loops: hangsFrom(asset, one, part) })));
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
37
|
+
* The point of a target's curved surface on the side the part stands on now (ADR-0140 3.9): the kernel's
|
|
38
|
+
* (`figureSurfaceKeptPlaceOf`), or, the part standing on its axis, the surface's first point.
|
|
39
|
+
*/
|
|
40
|
+
export function surfaceKeptOf(revision, part, target) {
|
|
41
|
+
const surface = surfaceOf(revision.asset, target);
|
|
42
|
+
if (!surface)
|
|
43
|
+
return undefined;
|
|
44
|
+
return (figureSurfaceKeptPlaceOf(revision.asset, part, target, { ...revision.inputs }) ??
|
|
45
|
+
(surface.form === 'around' ? { around: 0, along: 'centre' } : { toward: { x: 0, y: 1, z: 0 } }));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A new aim: the plane, another part, another face of the same part, or a point of its curved surface. Where on the
|
|
49
|
+
* face -- or the surface -- is filled in as where the part stands now, so fastening it keeps it there (`placeKeptOf`,
|
|
50
|
+
* `surfaceKeptOf`); the author changes it from there. A part aimed at a sphere, or beside a cylinder's side within its
|
|
51
|
+
* length, is aimed at the surface: it is fastened as it stands (ADR-0140 3.6).
|
|
38
52
|
*/
|
|
39
53
|
export function aimFasten(revision, part, form, to) {
|
|
54
|
+
if ('surface' in to)
|
|
55
|
+
return { ...form, surface: surfaceKeptOf(revision, part, form.target), align: {}, alignMm: {}, alignInput: {} };
|
|
56
|
+
if ('part' in to) {
|
|
57
|
+
/* Beside its curved surface -- a sphere's anywhere, a cylinder's side within its length -- or with no face at all. */
|
|
58
|
+
const beside = figureSurfaceKeptPlaceOf(revision.asset, part, to.part, { ...revision.inputs });
|
|
59
|
+
const kept = beside ?? (!facesOf(revision.asset, to.part).length ? surfaceKeptOf(revision, part, to.part) : undefined);
|
|
60
|
+
if (kept)
|
|
61
|
+
return { ...form, toPlane: false, target: to.part, surface: kept, align: {}, alignMm: {}, alignInput: {} };
|
|
62
|
+
}
|
|
63
|
+
form = { ...form, surface: undefined };
|
|
64
|
+
/* Aimed at the plane, it meets it toward the side the placement uses -- below a ceiling -- until the author says (ADR-0140 3.11). */
|
|
40
65
|
const f = 'plane' in to
|
|
41
|
-
? { ...form, toPlane: true, target: '' }
|
|
66
|
+
? { ...form, toPlane: true, target: '', facing: form.toPlane ? form.facing : (figurePlaneFacingOf(revision.asset) ?? form.facing) }
|
|
42
67
|
: 'part' in to
|
|
43
68
|
? { ...form, toPlane: false, target: to.part }
|
|
44
69
|
: { ...form, targetFace: to.targetFace };
|
|
@@ -56,6 +81,10 @@ export function fastenChanged(revision, part, form, change) {
|
|
|
56
81
|
const f = form;
|
|
57
82
|
if ('targetFace' in change)
|
|
58
83
|
return aimFasten(revision, part, f, { targetFace: change.targetFace });
|
|
84
|
+
if ('onSurface' in change)
|
|
85
|
+
return aimFasten(revision, part, f, { surface: true });
|
|
86
|
+
if ('surface' in change)
|
|
87
|
+
return { ...f, surface: change.surface };
|
|
59
88
|
if ('mode' in change)
|
|
60
89
|
return { ...f, facing: change.mode };
|
|
61
90
|
if ('gap' in change)
|
|
@@ -76,7 +105,21 @@ export function fastenPlanOf(revision, part, f) {
|
|
|
76
105
|
? { part: f.target, face: targetFace }
|
|
77
106
|
: undefined;
|
|
78
107
|
/* The plane takes a part standing on it or hanging below it, a wall only one standing out of it (ADR-0140 3.11). */
|
|
79
|
-
const
|
|
108
|
+
const planeWay = figurePlaneFacingOf(asset);
|
|
109
|
+
const mode = f.toPlane ? (mountingPlaneOf(asset).facings.includes(f.facing) ? f.facing : (planeWay ?? 'meet')) : f.facing;
|
|
110
|
+
const surface = !f.toPlane && f.target && f.surface ? surfaceOf(asset, f.target) : undefined;
|
|
111
|
+
if (surface) {
|
|
112
|
+
/* On the surface: where is the surface's own words, so no alignment goes with it (ADR-0140 3.9). */
|
|
113
|
+
const action = {
|
|
114
|
+
kind: 'attach',
|
|
115
|
+
part,
|
|
116
|
+
to: { part: f.target, surface: f.surface },
|
|
117
|
+
facing: mode,
|
|
118
|
+
...fastenGapOf(f),
|
|
119
|
+
replace: !!heldBy(asset, part)
|
|
120
|
+
};
|
|
121
|
+
return { theirFaces, targetFace, mode, alignRows: [], action, surface: { ...surface, place: f.surface } };
|
|
122
|
+
}
|
|
80
123
|
if (!aim)
|
|
81
124
|
return { theirFaces, targetFace, mode, alignRows: [] };
|
|
82
125
|
const align = alignOf(asset, aim, f.align, f.alignMm, f.alignInput);
|
|
@@ -85,7 +128,8 @@ export function fastenPlanOf(revision, part, f) {
|
|
|
85
128
|
kind: 'attach',
|
|
86
129
|
part,
|
|
87
130
|
to: f.toPlane ? { plane: 'mounting-plane' } : { part: f.target, face: targetFace },
|
|
88
|
-
|
|
131
|
+
/* On the plane the kernel takes the placement's way when none is said: only another way is said. */
|
|
132
|
+
...(f.toPlane ? (mode === planeWay ? {} : { facing: mode }) : { facing: mode }),
|
|
89
133
|
...fastenGapOf(f),
|
|
90
134
|
...(align ? { align } : {}),
|
|
91
135
|
replace: !!heldBy(asset, part)
|
|
@@ -103,7 +147,8 @@ export function fastenPlanOf(revision, part, f) {
|
|
|
103
147
|
}
|
|
104
148
|
/** 「bin의 윗면에 올려놓기」: what the fastening is, in one line -- the part is the one the window is open on, not named again. */
|
|
105
149
|
export function fastenSentenceOf(form, plan, planeWord, say) {
|
|
106
|
-
const
|
|
150
|
+
const where = plan.surface ? say('figure.label.surface-' + plan.surface.form) : say('figure.label.face-' + plan.targetFace);
|
|
151
|
+
const to = form.toPlane ? planeWord : `${form.target}의 ${where}`;
|
|
107
152
|
return say(`figure.text.fasten-sentence-${plan.mode}`, { to });
|
|
108
153
|
}
|
|
109
154
|
/** What the folded details hold that is not their default, said while folded; nothing for the default. */
|
|
@@ -116,4 +161,37 @@ export function fastenDetailWordsOf(form, say) {
|
|
|
116
161
|
: [])
|
|
117
162
|
];
|
|
118
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* A part pressed on the canvas while the window is open (ADR-0140 stage 4): aimed at, on the face or the point pressed,
|
|
166
|
+
* its middle where it was pressed -- the kernel's reading of the press (`figureFastenTargetAt`), the point and normal brought into that part's own
|
|
167
|
+
* 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
|
|
168
|
+
* 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
|
|
169
|
+
* why, by its words' key.
|
|
170
|
+
*/
|
|
171
|
+
export function aimFastenAt(revision, part, form, at) {
|
|
172
|
+
const target = ownerOf(at.id);
|
|
173
|
+
const offered = fastenTargetsOf(revision, part).find(t => t.part === target);
|
|
174
|
+
if (target === part)
|
|
175
|
+
return { note: 'aim-self', part: target };
|
|
176
|
+
if (!offered)
|
|
177
|
+
return { note: 'aim-cannot', part: target };
|
|
178
|
+
if (offered.loops)
|
|
179
|
+
return { note: 'target-would-loop', part: target };
|
|
180
|
+
const aimed = aimFasten(revision, part, form, { part: target });
|
|
181
|
+
const pose = revision.geometry.find(g => g.id === at.id)?.pose;
|
|
182
|
+
if (!pose || !at.point || !at.normal)
|
|
183
|
+
return { form: aimed, part: target };
|
|
184
|
+
/* Into the part's own frame: rᵀ (p − t), and the normal by rᵀ. */
|
|
185
|
+
const own = (v, from = [0, 0, 0]) => [0, 1, 2].map(c => [0, 1, 2].reduce((sum, i) => sum + pose.r[i][c] * (v[i] - from[i]), 0));
|
|
186
|
+
const lands = figureFastenTargetAt(revision.asset, target, own(at.point, pose.t), own(at.normal), { ...revision.inputs });
|
|
187
|
+
if ('face' in lands) {
|
|
188
|
+
/* On the face where it was pressed, as a point of a curved surface is: its middle there. */
|
|
189
|
+
const onFace = aimFasten(revision, part, { ...aimed, surface: undefined }, { targetFace: lands.face });
|
|
190
|
+
const at = lands.at;
|
|
191
|
+
return { form: { ...onFace, align: Object.fromEntries(Object.keys(at).map(a => [a, 'mm'])), alignMm: { ...at }, alignInput: {} }, part: target };
|
|
192
|
+
}
|
|
193
|
+
if ('surface' in lands)
|
|
194
|
+
return { form: { ...aimed, toPlane: false, target, surface: lands.surface, align: {}, alignMm: {}, alignInput: {} }, part: target };
|
|
195
|
+
return { form: aimed, note: 'aim-not-here', part: target };
|
|
196
|
+
}
|
|
119
197
|
//# sourceMappingURL=fasten-form.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fasten-form.js","sourceRoot":"","sources":["../../client/modeller/fasten-form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAGrD,OAAO,EAAE,UAAU,EAAqD,MAAM,sBAAsB,CAAA;AAEpG,OAAO,EACL,OAAO,EACP,WAAW,EACX,SAAS,EACT,OAAO,EACP,WAAW,EACX,WAAW,EACX,SAAS,EACT,MAAM,EACN,eAAe,EACf,WAAW,EAGZ,MAAM,YAAY,CAAA;AAGnB;;;;GAIG;AAEH,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAiB,CAAA;AAgBvD,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,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,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAE,EAAE,GAAG,WAAW,EAAE,EAAE,GAAG,GAAG,EAAiB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;AAC5F,CAAC;AAED,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,SAAS,CACvB,QAAwB,EACxB,IAAY,EACZ,IAAgB,EAChB,EAAmE;IAEnE,MAAM,CAAC,GACL,OAAO,IAAI,EAAE;QACX,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;QACxC,CAAC,CAAC,MAAM,IAAI,EAAE;YACZ,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE;YAC9C,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,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,EAAE,CAAA;AAC3G,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,CAAC,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,UAAwB,EAAE,CAAC,CAAA;IAChH,IAAI,MAAM,IAAI,MAAM;QAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,CAAA;IAC1D,IAAI,KAAK,IAAI,MAAM;QAAE,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;IACzE,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;AAiBD,MAAM,UAAU,YAAY,CAAC,QAAwB,EAAE,IAAY,EAAE,CAAa;IAChF,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,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,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IACvH,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,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QAC9E,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,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,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC,oBAAoB,GAAG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAA;IACtG,OAAO,GAAG,CAAC,+BAA+B,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AAChE,CAAC;AAED,0GAA0G;AAC1G,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,GAAG;gBACR,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","sourcesContent":["import { FIGURE_FACES } from '@hatiolab/figure-model'\nimport type { FigureAuthoringAction, FigureFace } from '@hatiolab/figure-model'\n\nimport { faceViewOf, type FaceView, type FastenMode, type FastenTarget } from './fasten-geometry.js'\nimport type { FastenChange } from './figure-fasten-drawing.js'\nimport {\n alignOf,\n alignRowsOf,\n canFasten,\n facesOf,\n fasteningOf,\n fastenGapOf,\n hangsFrom,\n heldBy,\n mountingPlaneOf,\n placeKeptOf,\n type Alignment,\n type Axis\n} from './links.js'\nimport type { FigureRevision } from './revision.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 align: Partial<Record<Axis, Alignment>>\n alignMm: Partial<Record<Axis, number>>\n alignInput: Partial<Record<Axis, string>>\n}\n\nexport const freshFasten = (): FastenForm => ({\n toPlane: false,\n target: '',\n targetFace: 'top',\n facing: 'meet',\n gap: 0,\n gapKept: undefined,\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 return own && !own.atOrigin ? ({ ...freshFasten(), ...own } as FastenForm) : freshFasten()\n}\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 * A new aim: the plane, another part, or another face of the same part. Where on the face is filled in as where the\n * part stands now, so fastening it keeps it there (`placeKeptOf`); the author changes it from there.\n */\nexport function aimFasten(\n revision: FigureRevision,\n part: string,\n form: FastenForm,\n to: { plane: true } | { part: string } | { targetFace: FigureFace }\n): FastenForm {\n const f: FastenForm =\n 'plane' in to\n ? { ...form, toPlane: true, target: '' }\n : 'part' in to\n ? { ...form, toPlane: false, target: to.part }\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 return { ...f, ...(aim ? placeKeptOf(revision, part, aim) : { align: {}, alignMm: {}, alignInput: {} }) }\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 aimFasten(revision, part, f, { targetFace: change.targetFace as FigureFace })\n if ('mode' in change) return { ...f, facing: change.mode }\n if ('gap' in change) return { ...f, gap: change.gap, gapKept: undefined }\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/** 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 action?: FigureAuthoringAction\n /** The face the command would meet, lit on the canvas while it is tried. */\n face?: { part: string; face: string }\n}\n\nexport function fastenPlanOf(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 mode: FastenMode = f.toPlane ? (mountingPlaneOf(asset).facings.includes(f.facing) ? f.facing : 'meet') : f.facing\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 ...(f.toPlane ? (mode === 'flush' ? { 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 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 to = form.toPlane ? planeWord : `${form.target}의 ${say('figure.label.face-' + plan.targetFace)}`\n return say(`figure.text.fasten-sentence-${plan.mode}`, { to })\n}\n\n/** What the folded details hold that is not their default, said while folded; nothing for the default. */\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.gap\n ? [say('figure.text.fasten-summary-gap', { mm: String(form.gap) })]\n : [])\n ]\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fasten-form.js","sourceRoot":"","sources":["../../client/modeller/fasten-form.ts"],"names":[],"mappings":"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","sourcesContent":["import { FIGURE_FACES, figureFastenTargetAt, figurePlaneFacingOf, figureSurfaceKeptPlaceOf } from '@hatiolab/figure-model'\nimport type { FigureAuthoringAction, FigureFace, FigureSurfacePlace } from '@hatiolab/figure-model'\n\nimport { faceViewOf, type FaceView, type FastenMode, type FastenTarget } from './fasten-geometry.js'\nimport type { FastenChange } from './figure-fasten-drawing.js'\nimport {\n alignOf,\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 { 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 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 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 return own && !own.atOrigin ? ({ ...freshFasten(), ...own } as FastenForm) : freshFasten()\n}\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 ? { ...form, toPlane: false, target: to.part }\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 return { ...f, ...(aim ? placeKeptOf(revision, part, aim) : { align: {}, alignMm: {}, alignInput: {} }) }\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 aimFasten(revision, part, f, { targetFace: change.targetFace as FigureFace })\n if ('onSurface' in change) return aimFasten(revision, part, f, { surface: true })\n if ('surface' in change) return { ...f, surface: change.surface }\n if ('mode' in change) return { ...f, facing: change.mode }\n if ('gap' in change) return { ...f, gap: change.gap, gapKept: undefined }\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/** 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 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}\n\nexport function fastenPlanOf(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 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/** What the folded details hold that is not their default, said while folded; nothing for the default. */\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.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 */\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, 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, toPlane: false, target, surface: lands.surface, align: {}, alignMm: {}, alignInput: {} }, part: target }\n return { form: aimed, note: 'aim-not-here', part: target }\n}\n"]}
|
|
@@ -7,9 +7,10 @@ import './figure-fasten-drawing.js';
|
|
|
7
7
|
import type { FigureRevision } from './revision.js';
|
|
8
8
|
declare const FigureFastenDialog_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
9
9
|
/**
|
|
10
|
-
* 붙이기 창: what the selected part is fastened to, on which face, how
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* 붙이기 창: what the selected part is fastened to, on which face, how -- chosen with room to see it, in the standard
|
|
11
|
+
* popup the editor opens (`openPopup`: its title, its close, moved by its head; no backdrop, so the canvas can be
|
|
12
|
+
* pressed while it is open) (user 2026-10-17: 「속성에는 심플하고 직관적인 결과 위주로, 편집은 팝업에서 넓은 공간을
|
|
13
|
+
* 써서」; 2026-10-18: the standard popup, not a window of its own; put back 2026-10-20).
|
|
13
14
|
*
|
|
14
15
|
* Everything chosen here is tried, not done: the command it comes to is told to the editor as it changes
|
|
15
16
|
* (`preview-command`), and the canvas draws it. 「적용」 sends it once (`fasten-apply`), one step of the history;
|
|
@@ -29,6 +30,8 @@ export declare class FigureFastenDialog extends FigureFastenDialog_base {
|
|
|
29
30
|
/** Why the last 「적용」 was refused, in the editor's words. */
|
|
30
31
|
error: string;
|
|
31
32
|
private form?;
|
|
33
|
+
/** Why a press on the canvas aimed at nothing, or only at the part (ADR-0140 stage 4); cleared by the next choice. */
|
|
34
|
+
private aimNote;
|
|
32
35
|
/** The command the part's own fastening comes to, as opened: tried unchanged, nothing new is said of it. */
|
|
33
36
|
private opened?;
|
|
34
37
|
private told;
|
|
@@ -51,6 +54,17 @@ export declare class FigureFastenDialog extends FigureFastenDialog_base {
|
|
|
51
54
|
*/
|
|
52
55
|
private renderLoops;
|
|
53
56
|
private aim;
|
|
57
|
+
/**
|
|
58
|
+
* A part pressed on the canvas while the window is open (ADR-0140 stage 4): aimed at, on the face or the point pressed
|
|
59
|
+
* -- the kernel's reading of the press (`figureFastenTargetAt`), the point and normal brought into that part's own
|
|
60
|
+
* frame by the pose it is drawn at. The part itself, or one hanging from it, is not aimed at; and a press on a curve
|
|
61
|
+
* there is no fastening to aims at the part alone. Each is said.
|
|
62
|
+
*/
|
|
63
|
+
aimAt(at: {
|
|
64
|
+
id: string;
|
|
65
|
+
point?: number[];
|
|
66
|
+
normal?: number[];
|
|
67
|
+
}): void;
|
|
54
68
|
/** The part under the pointer or the focus in the list, for the canvas to light; the plane lights nothing. */
|
|
55
69
|
private hover;
|
|
56
70
|
private set;
|
|
@@ -10,14 +10,15 @@ import { CommonPopupStyles } from '@operato/styles';
|
|
|
10
10
|
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js';
|
|
11
11
|
import './figure-fasten-drawing.js';
|
|
12
12
|
import { turnsInPlace } from './fasten-geometry.js';
|
|
13
|
-
import { aimFasten, fastenChanged, fastenDetailWordsOf, fastenFormOf, fastenPlanOf, fastenSentenceOf, fastenTargetsOf } from './fasten-form.js';
|
|
14
|
-
import { fieldWordsOf, hasInstanceSize, heldBy, mountingPlaneOf } from './links.js';
|
|
13
|
+
import { aimFasten, aimFastenAt, fastenChanged, fastenDetailWordsOf, fastenFormOf, fastenPlanOf, fastenSentenceOf, fastenTargetsOf } from './fasten-form.js';
|
|
14
|
+
import { fieldWordsOf, hasInstanceSize, heldBy, mountingPlaneOf, surfaceOf } from './links.js';
|
|
15
15
|
/** The target list's entry for the plane the figure stands by. */
|
|
16
16
|
const PLANE = '__plane';
|
|
17
17
|
/**
|
|
18
|
-
* 붙이기 창: what the selected part is fastened to, on which face, how
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* 붙이기 창: what the selected part is fastened to, on which face, how -- chosen with room to see it, in the standard
|
|
19
|
+
* popup the editor opens (`openPopup`: its title, its close, moved by its head; no backdrop, so the canvas can be
|
|
20
|
+
* pressed while it is open) (user 2026-10-17: 「속성에는 심플하고 직관적인 결과 위주로, 편집은 팝업에서 넓은 공간을
|
|
21
|
+
* 써서」; 2026-10-18: the standard popup, not a window of its own; put back 2026-10-20).
|
|
21
22
|
*
|
|
22
23
|
* Everything chosen here is tried, not done: the command it comes to is told to the editor as it changes
|
|
23
24
|
* (`preview-command`), and the canvas draws it. 「적용」 sends it once (`fasten-apply`), one step of the history;
|
|
@@ -35,6 +36,8 @@ let FigureFastenDialog = class FigureFastenDialog extends localize(i18next)(LitE
|
|
|
35
36
|
this.previewWords = [];
|
|
36
37
|
/** Why the last 「적용」 was refused, in the editor's words. */
|
|
37
38
|
this.error = '';
|
|
39
|
+
/** Why a press on the canvas aimed at nothing, or only at the part (ADR-0140 stage 4); cleared by the next choice. */
|
|
40
|
+
this.aimNote = '';
|
|
38
41
|
this.told = '';
|
|
39
42
|
this.hovered = null;
|
|
40
43
|
this.onKey = async (e) => {
|
|
@@ -79,11 +82,16 @@ let FigureFastenDialog = class FigureFastenDialog extends localize(i18next)(LitE
|
|
|
79
82
|
SidePanelStyles,
|
|
80
83
|
CommonPopupStyles,
|
|
81
84
|
css `
|
|
85
|
+
/*
|
|
86
|
+
The popup holds it at its own height and cuts what is past the screen: the window scrolls itself, within the
|
|
87
|
+
screen less the popup's head and margins, 취소 · 적용 kept at its foot (below).
|
|
88
|
+
*/
|
|
82
89
|
:host {
|
|
83
90
|
display: grid;
|
|
84
91
|
gap: 16px;
|
|
85
92
|
padding: var(--popup-padding, 16px);
|
|
86
93
|
overflow: auto;
|
|
94
|
+
max-height: calc(100dvh - 32px - 56px);
|
|
87
95
|
}
|
|
88
96
|
/* The result in one line, at the top: it changes with every choice below it. */
|
|
89
97
|
p[fasten-sentence] {
|
|
@@ -137,7 +145,7 @@ let FigureFastenDialog = class FigureFastenDialog extends localize(i18next)(LitE
|
|
|
137
145
|
margin: 0;
|
|
138
146
|
color: var(--md-sys-color-error);
|
|
139
147
|
}
|
|
140
|
-
/* 취소 · 적용 stay in sight at the window's foot when what is above them is taller than the
|
|
148
|
+
/* 취소 · 적용 stay in sight at the window's foot when what is above them is taller than the screen. */
|
|
141
149
|
ox-popup-actions {
|
|
142
150
|
position: sticky;
|
|
143
151
|
bottom: calc(-1 * var(--popup-padding, 16px));
|
|
@@ -205,11 +213,16 @@ let FigureFastenDialog = class FigureFastenDialog extends localize(i18next)(LitE
|
|
|
205
213
|
.alignMm=${f.alignMm}
|
|
206
214
|
.gap=${f.gap}
|
|
207
215
|
.partName=${this.partId}
|
|
216
|
+
.surfaceForm=${f.toPlane || !f.target ? '' : (surfaceOf(asset, f.target)?.form ?? '')}
|
|
217
|
+
.surface=${plan.surface}
|
|
208
218
|
?locked=${this.locked}
|
|
209
|
-
@fasten-change=${(e) =>
|
|
219
|
+
@fasten-change=${(e) => {
|
|
220
|
+
this.aimNote = '';
|
|
221
|
+
this.form = fastenChanged(revision, this.partId, this.form, e.detail);
|
|
222
|
+
}}
|
|
210
223
|
></figure-fasten-drawing>`;
|
|
211
224
|
return html `
|
|
212
|
-
${plan.aim
|
|
225
|
+
${plan.aim || plan.surface
|
|
213
226
|
? html `<p fasten-sentence role="status">
|
|
214
227
|
${fastenSentenceOf(f, plan, planeWord, (key, params) => i18next.t(key, params))}
|
|
215
228
|
</p>`
|
|
@@ -218,13 +231,15 @@ let FigureFastenDialog = class FigureFastenDialog extends localize(i18next)(LitE
|
|
|
218
231
|
<h4 id="what">${i18next.t('figure.label.fasten-what')}</h4>
|
|
219
232
|
${this.renderTargets(revision, f, planeWord)}
|
|
220
233
|
</div>
|
|
221
|
-
${plan.aim
|
|
234
|
+
${plan.aim || plan.surface
|
|
222
235
|
? html `<div question="face">
|
|
223
236
|
<h4>
|
|
224
237
|
${
|
|
225
238
|
/* The face asked is the target's, named: the part meets it by its real shape, by no face of its own. */ f.toPlane
|
|
226
239
|
? i18next.t('figure.label.fasten-face-on-plane', { plane: planeWord })
|
|
227
|
-
:
|
|
240
|
+
: /* A sphere has no face to choose: where on its surface. */ plan.theirFaces.length
|
|
241
|
+
? i18next.t('figure.label.fasten-face-on-part', { part: f.target })
|
|
242
|
+
: i18next.t('figure.label.fasten-face-on-surface', { part: f.target })}
|
|
228
243
|
</h4>
|
|
229
244
|
${drawing('face')}
|
|
230
245
|
</div>
|
|
@@ -255,6 +270,7 @@ let FigureFastenDialog = class FigureFastenDialog extends localize(i18next)(LitE
|
|
|
255
270
|
</div>
|
|
256
271
|
${ /* The one it has, tried unchanged: that it stays where it is goes without saying. */JSON.stringify(plan.action ?? null) === this.opened ? nothing : this.renderPreviewWords()}`
|
|
257
272
|
: nothing}
|
|
273
|
+
${this.aimNote ? html `<p hint aim-note role="status">${this.aimNote}</p>` : nothing}
|
|
258
274
|
${this.error ? html `<p error role="alert">${this.error}</p>` : nothing}
|
|
259
275
|
<ox-popup-actions>
|
|
260
276
|
<md-text-button slot="cancel" cancel @click=${this.cancel}>${i18next.t('figure.button.cancel')}</md-text-button>
|
|
@@ -312,11 +328,26 @@ let FigureFastenDialog = class FigureFastenDialog extends localize(i18next)(LitE
|
|
|
312
328
|
aim(part) {
|
|
313
329
|
if (this.locked || !this.revision || !this.form)
|
|
314
330
|
return;
|
|
331
|
+
this.aimNote = '';
|
|
315
332
|
const chosen = this.form.toPlane ? PLANE : this.form.target;
|
|
316
333
|
if (part === chosen)
|
|
317
334
|
return;
|
|
318
335
|
this.form = aimFasten(this.revision, this.partId, this.form, part === PLANE ? { plane: true } : { part });
|
|
319
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* A part pressed on the canvas while the window is open (ADR-0140 stage 4): aimed at, on the face or the point pressed
|
|
339
|
+
* -- the kernel's reading of the press (`figureFastenTargetAt`), the point and normal brought into that part's own
|
|
340
|
+
* frame by the pose it is drawn at. The part itself, or one hanging from it, is not aimed at; and a press on a curve
|
|
341
|
+
* there is no fastening to aims at the part alone. Each is said.
|
|
342
|
+
*/
|
|
343
|
+
aimAt(at) {
|
|
344
|
+
if (this.locked || !this.revision || !this.form)
|
|
345
|
+
return;
|
|
346
|
+
const aimed = aimFastenAt(this.revision, this.partId, this.form, at);
|
|
347
|
+
this.aimNote = aimed.note ? i18next.t(`figure.text.${aimed.note}`, { part: aimed.part, mine: this.partId }) : '';
|
|
348
|
+
if (aimed.form)
|
|
349
|
+
this.form = aimed.form;
|
|
350
|
+
}
|
|
320
351
|
/** The part under the pointer or the focus in the list, for the canvas to light; the plane lights nothing. */
|
|
321
352
|
hover(part) {
|
|
322
353
|
const lit = part && part !== PLANE ? part : null;
|
|
@@ -398,6 +429,8 @@ let FigureFastenDialog = class FigureFastenDialog extends localize(i18next)(LitE
|
|
|
398
429
|
this.setAttribute('role', 'dialog');
|
|
399
430
|
this.setAttribute('aria-label', i18next.t('figure.label.fastening'));
|
|
400
431
|
this.addEventListener('keydown', this.onKey);
|
|
432
|
+
/* The popup's fitting width is the overlay's own setting; the window asks it for room for the face and the way side by side. */
|
|
433
|
+
this.closest('ox-floating-overlay')?.style.setProperty('--overlay-center-fit-width', '640px');
|
|
401
434
|
}
|
|
402
435
|
};
|
|
403
436
|
__decorate([
|
|
@@ -424,6 +457,10 @@ __decorate([
|
|
|
424
457
|
state(),
|
|
425
458
|
__metadata("design:type", Object)
|
|
426
459
|
], FigureFastenDialog.prototype, "form", void 0);
|
|
460
|
+
__decorate([
|
|
461
|
+
state(),
|
|
462
|
+
__metadata("design:type", Object)
|
|
463
|
+
], FigureFastenDialog.prototype, "aimNote", void 0);
|
|
427
464
|
FigureFastenDialog = __decorate([
|
|
428
465
|
customElement('figure-fasten-dialog')
|
|
429
466
|
], FigureFastenDialog);
|