@things-factory/figure-ui 10.1.92 → 10.1.94
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/fasten-geometry.ts +152 -0
- package/client/modeller/figure-cavity-drawing.ts +259 -0
- package/client/modeller/figure-fasten-drawing.ts +412 -0
- package/client/modeller/figure-links.ts +71 -35
- package/client/modeller/figure-section-panel.ts +2 -0
- package/client/modeller/section-controls.ts +19 -0
- package/dist-client/modeller/fasten-geometry.d.ts +60 -0
- package/dist-client/modeller/fasten-geometry.js +138 -0
- package/dist-client/modeller/fasten-geometry.js.map +1 -0
- package/dist-client/modeller/figure-cavity-drawing.d.ts +48 -0
- package/dist-client/modeller/figure-cavity-drawing.js +253 -0
- package/dist-client/modeller/figure-cavity-drawing.js.map +1 -0
- package/dist-client/modeller/figure-fasten-drawing.d.ts +74 -0
- package/dist-client/modeller/figure-fasten-drawing.js +460 -0
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -0
- package/dist-client/modeller/figure-links.d.ts +7 -0
- package/dist-client/modeller/figure-links.js +70 -33
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-section-panel.d.ts +1 -0
- package/dist-client/modeller/figure-section-panel.js +2 -0
- package/dist-client/modeller/figure-section-panel.js.map +1 -1
- package/dist-client/modeller/section-controls.d.ts +10 -0
- package/dist-client/modeller/section-controls.js +17 -0
- package/dist-client/modeller/section-controls.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/test/browser/acceptance-page.js +10 -0
- package/test/cavity-drawing.test.ts +69 -0
- package/test/fasten-drawing.test.ts +45 -0
- package/test/fasten-geometry.test.ts +79 -0
- package/test/links.test.ts +3 -1
- package/translations/en.json +11 -1
- package/translations/ja.json +11 -1
- package/translations/ko.json +11 -1
- package/translations/ms.json +11 -1
- package/translations/zh.json +11 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { figurePartFaceOf } from '@hatiolab/figure-model';
|
|
2
|
+
import { boxOf } from './align.js';
|
|
3
|
+
import { alignAxesOf, facesOf, faceAxisOf, hasInstanceSize, mountingPlaneOf } from './links.js';
|
|
4
|
+
const AXES = ['x', 'y', 'z'];
|
|
5
|
+
const OPPOSITE = { left: 'right', right: 'left', bottom: 'top', top: 'bottom', back: 'front', front: 'back' };
|
|
6
|
+
const NORMAL = { left: ['x', -1], right: ['x', 1], bottom: ['y', -1], top: ['y', 1], back: ['z', -1], front: ['z', 1] };
|
|
7
|
+
/** The world direction a part's face looks along now, turns and all; by its name when the face's outline is not read. */
|
|
8
|
+
function directionOf(asset, part, face) {
|
|
9
|
+
const area = (() => {
|
|
10
|
+
try {
|
|
11
|
+
return figurePartFaceOf(asset, part, face);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
17
|
+
if (area) {
|
|
18
|
+
const k = area.normal.findIndex(v => Math.abs(v) > 0.5);
|
|
19
|
+
if (k >= 0)
|
|
20
|
+
return [AXES[k], area.normal[k] > 0 ? 1 : -1];
|
|
21
|
+
}
|
|
22
|
+
return face in NORMAL ? NORMAL[face] : undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The part's own face a fastening takes: the one looking against the target's face (올려놓기), with it (같은 면), or its
|
|
26
|
+
* centre. Read from where each face looks now, so a part standing turned offers the face that is really there.
|
|
27
|
+
* Undefined when the part has no such face.
|
|
28
|
+
*/
|
|
29
|
+
export function myFaceFor(asset, part, target, mode) {
|
|
30
|
+
if (mode === 'centre')
|
|
31
|
+
return target === 'plane' ? undefined : 'centre';
|
|
32
|
+
const want = target === 'plane'
|
|
33
|
+
? /* The plane is met from above on a floor, from in front on a wall. */ [mountingPlaneOf(asset).normal, -1]
|
|
34
|
+
: (() => {
|
|
35
|
+
const d = directionOf(asset, target.part, target.face);
|
|
36
|
+
return d && [d[0], (mode === 'meet' ? -d[1] : d[1])];
|
|
37
|
+
})();
|
|
38
|
+
if (!want)
|
|
39
|
+
return undefined;
|
|
40
|
+
const mine = facesOf(asset, part).filter((f) => f in NORMAL);
|
|
41
|
+
const hit = mine.find(f => {
|
|
42
|
+
const d = directionOf(asset, part, f);
|
|
43
|
+
return d && d[0] === want[0] && d[1] === want[1];
|
|
44
|
+
});
|
|
45
|
+
return hit ?? (target !== 'plane' && mode === 'meet' && target.face in OPPOSITE ? OPPOSITE[target.face] : undefined);
|
|
46
|
+
}
|
|
47
|
+
/** Which way the fastening is, read back from the part's face and facing. */
|
|
48
|
+
export const modeOf = (face, facing) => (face === 'centre' ? 'centre' : facing);
|
|
49
|
+
export function faceViewOf(asset, part, target) {
|
|
50
|
+
const across = alignAxesOf(asset, part, target);
|
|
51
|
+
if (across.length !== 2)
|
|
52
|
+
return undefined;
|
|
53
|
+
const box = boxOf(asset, part);
|
|
54
|
+
if (!box)
|
|
55
|
+
return undefined;
|
|
56
|
+
const axes = [across[0].axis, across[1].axis];
|
|
57
|
+
const face = {};
|
|
58
|
+
if (target === 'plane') {
|
|
59
|
+
for (const a of axes)
|
|
60
|
+
if (hasInstanceSize(asset, a))
|
|
61
|
+
face[a] = asset.designInputs[`size.${a}`];
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
const theirs = boxOf(asset, target.part);
|
|
65
|
+
const normal = faceAxisOf(asset, target.part, target.face);
|
|
66
|
+
for (const a of axes)
|
|
67
|
+
if (theirs && a !== normal)
|
|
68
|
+
face[a] = theirs.extent[a];
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
axes,
|
|
72
|
+
face,
|
|
73
|
+
part: { x: box.extent.x, y: box.extent.y, z: box.extent.z },
|
|
74
|
+
edges: Object.fromEntries(across.map(o => [o.axis, o.edges]))
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Where the part's centre is from the face's centre along one axis, for the way it is aligned there. */
|
|
78
|
+
export function offsetOf(how, mm, face, part) {
|
|
79
|
+
if (how === 'min' && face !== undefined)
|
|
80
|
+
return -(face - part) / 2;
|
|
81
|
+
if (how === 'max' && face !== undefined)
|
|
82
|
+
return (face - part) / 2;
|
|
83
|
+
if (how === 'mm' || how === 'share')
|
|
84
|
+
return mm ?? 0;
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A place dropped on the face, as the form's alignment: caught at the centre, at an edge from inside (level with it:
|
|
89
|
+
* `min` / `max`, where the edges can be lined up with), or at an edge from outside (touching it, as a distance), when
|
|
90
|
+
* within `catchMm`; anywhere else a distance off centre, whole millimetres.
|
|
91
|
+
*/
|
|
92
|
+
export function alignmentAt(d, face, part, edges, catchMm) {
|
|
93
|
+
if (Math.abs(d) <= catchMm)
|
|
94
|
+
return { how: 'centre', caught: 'centre' };
|
|
95
|
+
if (face !== undefined) {
|
|
96
|
+
const inside = (face - part) / 2, outside = (face + part) / 2;
|
|
97
|
+
for (const [at, how] of [
|
|
98
|
+
[-inside, 'min'],
|
|
99
|
+
[inside, 'max']
|
|
100
|
+
])
|
|
101
|
+
if (Math.abs(d - at) <= catchMm && inside > 0)
|
|
102
|
+
return edges ? { how, caught: 'inside' } : { how: 'mm', mm: Math.round(at), caught: 'inside' };
|
|
103
|
+
for (const at of [-outside, outside])
|
|
104
|
+
if (Math.abs(d - at) <= catchMm)
|
|
105
|
+
return { how: 'mm', mm: Math.round(at), caught: 'outside' };
|
|
106
|
+
}
|
|
107
|
+
return { how: 'mm', mm: Math.round(d) };
|
|
108
|
+
}
|
|
109
|
+
/** How far the footprint comes past the face's edges, per side, and whether it touches the face at all. */
|
|
110
|
+
export function overhangOf(view, at) {
|
|
111
|
+
const sides = [];
|
|
112
|
+
let touching = true;
|
|
113
|
+
for (const a of view.axes) {
|
|
114
|
+
const face = view.face[a];
|
|
115
|
+
if (face === undefined)
|
|
116
|
+
continue;
|
|
117
|
+
const lo = at[a] - view.part[a] / 2, hi = at[a] + view.part[a] / 2;
|
|
118
|
+
if (lo >= face / 2 || hi <= -face / 2)
|
|
119
|
+
touching = false;
|
|
120
|
+
if (-face / 2 - lo > 0.5)
|
|
121
|
+
sides.push({ axis: a, side: 'low', mm: Math.round(-face / 2 - lo) });
|
|
122
|
+
if (hi - face / 2 > 0.5)
|
|
123
|
+
sides.push({ axis: a, side: 'high', mm: Math.round(hi - face / 2) });
|
|
124
|
+
}
|
|
125
|
+
return { touching, sides };
|
|
126
|
+
}
|
|
127
|
+
/** Whether the part turns about a joint: then 가운데를 면에 is offered -- a roller spinning in place. */
|
|
128
|
+
export const turnsInPlace = (asset, part) => (asset.joints ?? []).some(j => j.body1.part === part && (j.type === 'revolute' || j.body1.face === 'centre'));
|
|
129
|
+
/**
|
|
130
|
+
* 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
|
|
131
|
+
* for Y, which rises, so a front face is seen as it stands and not upside down.
|
|
132
|
+
*/
|
|
133
|
+
export function screenOf(axes) {
|
|
134
|
+
const h = axes.includes('x') ? 'x' : 'z';
|
|
135
|
+
const v = axes.find(a => a !== h);
|
|
136
|
+
return { h, v, sign: { x: 1, y: 1, z: 1, [v]: v === 'y' ? -1 : 1 } };
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=fasten-geometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fasten-geometry.js","sourceRoot":"","sources":["../../client/modeller/fasten-geometry.ts"],"names":[],"mappings":"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","sourcesContent":["import { figurePartFaceOf } from '@hatiolab/figure-model'\nimport type { FigureAsset, FigureFace } from '@hatiolab/figure-model'\n\nimport { boxOf } from './align.js'\nimport { alignAxesOf, facesOf, faceAxisOf, hasInstanceSize, mountingPlaneOf, type Alignment } from './links.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's\n * own face follows from those -- the kernel takes no other pairing (figure-model `attach`: meet is the facing face,\n * flush the same one, a face along another axis would need a turn).\n */\n\ntype Axis = 'x' | 'y' | 'z'\nconst AXES: Axis[] = ['x', 'y', 'z']\nexport type FastenMode = 'meet' | 'flush' | 'centre'\nexport type FastenTarget = { part: string; face: string } | 'plane'\n\nconst OPPOSITE: Record<FigureFace, FigureFace> = { left: 'right', right: 'left', bottom: 'top', top: 'bottom', back: 'front', front: 'back' }\nconst NORMAL: Record<FigureFace, [Axis, 1 | -1]> = { left: ['x', -1], right: ['x', 1], bottom: ['y', -1], top: ['y', 1], back: ['z', -1], front: ['z', 1] }\n\n/** The world direction a part's face looks along now, turns and all; by its name when the face's outline is not read. */\nfunction directionOf(asset: FigureAsset, part: string, face: string): [Axis, 1 | -1] | undefined {\n const area = (() => {\n try {\n return figurePartFaceOf(asset, part, face)\n } catch {\n return undefined\n }\n })()\n if (area) {\n const k = area.normal.findIndex(v => Math.abs(v) > 0.5)\n if (k >= 0) return [AXES[k]!, area.normal[k]! > 0 ? 1 : -1]\n }\n return face in NORMAL ? NORMAL[face as FigureFace] : undefined\n}\n\n/**\n * The part's own face a fastening takes: the one looking against the target's face (올려놓기), with it (같은 면), or its\n * centre. Read from where each face looks now, so a part standing turned offers the face that is really there.\n * Undefined when the part has no such face.\n */\nexport function myFaceFor(asset: FigureAsset, part: string, target: FastenTarget, mode: FastenMode): FigureFace | 'centre' | undefined {\n if (mode === 'centre') return target === 'plane' ? undefined : 'centre'\n const want: [Axis, 1 | -1] | undefined =\n target === 'plane'\n ? /* The plane is met from above on a floor, from in front on a wall. */ [mountingPlaneOf(asset).normal, -1]\n : (() => {\n const d = directionOf(asset, target.part, target.face)\n return d && [d[0], (mode === 'meet' ? -d[1] : d[1]) as 1 | -1]\n })()\n if (!want) return undefined\n const mine = facesOf(asset, part).filter((f): f is FigureFace => f in NORMAL)\n const hit = mine.find(f => {\n const d = directionOf(asset, part, f)\n return d && d[0] === want[0] && d[1] === want[1]\n })\n return hit ?? (target !== 'plane' && mode === 'meet' && target.face in OPPOSITE ? OPPOSITE[target.face as FigureFace] : undefined)\n}\n\n/** Which way the fastening is, read back from the part's face and facing. */\nexport const modeOf = (face: string, facing: 'meet' | 'flush'): FastenMode => (face === 'centre' ? 'centre' : facing)\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 -- none\n * for a plane with no declared size -- the part's footprint on each, and whether its edges can be lined up with.\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}\n\nexport function faceViewOf(asset: FigureAsset, part: string, target: FastenTarget): FaceView | undefined {\n const across = alignAxesOf(asset, part, target)\n if (across.length !== 2) return undefined\n const box = boxOf(asset, part)\n if (!box) return undefined\n const axes = [across[0]!.axis, across[1]!.axis] as [Axis, Axis]\n const face: Partial<Record<Axis, number>> = {}\n if (target === 'plane') {\n for (const a of axes) if (hasInstanceSize(asset, a)) face[a] = asset.designInputs[`size.${a}`]!\n } else {\n const theirs = boxOf(asset, target.part)\n const normal = faceAxisOf(asset, target.part, target.face)\n for (const a of axes) if (theirs && a !== normal) face[a] = theirs.extent[a]\n }\n return {\n axes,\n face,\n part: { x: box.extent.x, y: box.extent.y, z: box.extent.z },\n edges: Object.fromEntries(across.map(o => [o.axis, o.edges])) as Record<Axis, boolean>\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. */\nexport function offsetOf(how: Alignment | undefined, mm: number | undefined, face: number | undefined, part: number): number {\n if (how === 'min' && face !== undefined) return -(face - part) / 2\n if (how === 'max' && face !== undefined) return (face - part) / 2\n if (how === 'mm' || how === 'share') return mm ?? 0\n return 0\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 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/** How far the footprint comes past the face's edges, per side, and whether it touches the face at all. */\nexport function overhangOf(view: FaceView, at: Record<Axis, number>): { touching: boolean; sides: Array<{ axis: Axis; side: 'low' | 'high'; mm: number }> } {\n const sides: Array<{ axis: Axis; side: 'low' | 'high'; mm: number }> = []\n let touching = true\n for (const a of view.axes) {\n const face = view.face[a]\n if (face === undefined) continue\n const lo = at[a] - view.part[a] / 2,\n hi = at[a] + view.part[a] / 2\n if (lo >= face / 2 || hi <= -face / 2) touching = false\n if (-face / 2 - lo > 0.5) sides.push({ axis: a, side: 'low', mm: Math.round(-face / 2 - lo) })\n if (hi - face / 2 > 0.5) sides.push({ axis: a, side: 'high', mm: Math.round(hi - face / 2) })\n }\n return { touching, sides }\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) => (asset.joints ?? []).some(j => j.body1.part === part && (j.type === 'revolute' || j.body1.face === '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"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { LitElement, nothing } from 'lit';
|
|
2
|
+
import type { FigureEvaluatedShape } from '@hatiolab/figure-model';
|
|
3
|
+
type Body = {
|
|
4
|
+
id: string;
|
|
5
|
+
shape: FigureEvaluatedShape;
|
|
6
|
+
};
|
|
7
|
+
declare const FigureCavityDrawing_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
8
|
+
/**
|
|
9
|
+
* 속 공간 그림 — the inside of a pushed part drawn, and edited by drawing (user 2026-10-01: 「이해와 편집이 직관적이지 않다」).
|
|
10
|
+
*
|
|
11
|
+
* Seen along the push, the section and its inside: a handle in the middle of each wall is dragged to its thickness,
|
|
12
|
+
* every wall together unless 면마다 벽 is on. Cut along the push, the walls and the two ends: an end pressed opens or
|
|
13
|
+
* closes it, a closed end's inner line is dragged to its thickness. While dragging, the figure behind shows what the
|
|
14
|
+
* inside makes (`command-live`); letting go sends one `set-cavity`. What the kernel would refuse is drawn in the error
|
|
15
|
+
* colour and said, by the kernel's own check, and is not sent.
|
|
16
|
+
*/
|
|
17
|
+
export declare class FigureCavityDrawing extends FigureCavityDrawing_base {
|
|
18
|
+
static styles: import("lit").CSSResult;
|
|
19
|
+
body?: Body;
|
|
20
|
+
locked: boolean;
|
|
21
|
+
/** Walls one per side: a handle moves its own wall only. */
|
|
22
|
+
perSide: boolean;
|
|
23
|
+
/** What is being dragged, and the inside it makes; undefined when nothing is. */
|
|
24
|
+
private working?;
|
|
25
|
+
private refusal;
|
|
26
|
+
private dragging;
|
|
27
|
+
private pressedAt;
|
|
28
|
+
private get now();
|
|
29
|
+
private request;
|
|
30
|
+
private action;
|
|
31
|
+
private judge;
|
|
32
|
+
/** The working inside while dragging: drawn, judged, and previewed behind when the kernel would take it. */
|
|
33
|
+
private hold;
|
|
34
|
+
private release;
|
|
35
|
+
private emit;
|
|
36
|
+
private point;
|
|
37
|
+
render(): import("lit-html").TemplateResult<1> | typeof nothing;
|
|
38
|
+
/** Seen along the push: the section, its inside and a handle on each wall. */
|
|
39
|
+
private top;
|
|
40
|
+
/** Cut along the push: the walls across the first plane axis, and the two ends -- pressed to open or close. */
|
|
41
|
+
private side;
|
|
42
|
+
private grab;
|
|
43
|
+
private drag;
|
|
44
|
+
private dragEnd;
|
|
45
|
+
/** A press on an end that did not move is a toggle; a drag was a thickness. */
|
|
46
|
+
private releaseEnd;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import { css, html, LitElement, nothing, svg } from 'lit';
|
|
3
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
4
|
+
import { i18next, localize } from '@operato/i18n';
|
|
5
|
+
import { figureCavitySectionOf } from '@hatiolab/figure-model';
|
|
6
|
+
import { cavityRefusalOf, cavityWorkingOf, END_FACE, endToggled, mm, SIDE_FACE, wallDragged } from './section-controls.js';
|
|
7
|
+
/** How the section is laid on the screen, by the axis it is pushed along: across to the right, then down. */
|
|
8
|
+
const SCREEN = { y: ['x', 'z'], x: ['z', 'y'], z: ['x', 'y'] };
|
|
9
|
+
/**
|
|
10
|
+
* 속 공간 그림 — the inside of a pushed part drawn, and edited by drawing (user 2026-10-01: 「이해와 편집이 직관적이지 않다」).
|
|
11
|
+
*
|
|
12
|
+
* Seen along the push, the section and its inside: a handle in the middle of each wall is dragged to its thickness,
|
|
13
|
+
* every wall together unless 면마다 벽 is on. Cut along the push, the walls and the two ends: an end pressed opens or
|
|
14
|
+
* closes it, a closed end's inner line is dragged to its thickness. While dragging, the figure behind shows what the
|
|
15
|
+
* inside makes (`command-live`); letting go sends one `set-cavity`. What the kernel would refuse is drawn in the error
|
|
16
|
+
* colour and said, by the kernel's own check, and is not sent.
|
|
17
|
+
*/
|
|
18
|
+
let FigureCavityDrawing = class FigureCavityDrawing extends localize(i18next)(LitElement) {
|
|
19
|
+
constructor() {
|
|
20
|
+
super(...arguments);
|
|
21
|
+
this.locked = false;
|
|
22
|
+
/** Walls one per side: a handle moves its own wall only. */
|
|
23
|
+
this.perSide = false;
|
|
24
|
+
this.refusal = '';
|
|
25
|
+
this.dragging = null;
|
|
26
|
+
this.pressedAt = 0;
|
|
27
|
+
}
|
|
28
|
+
static { this.styles = css `
|
|
29
|
+
:host {
|
|
30
|
+
display: grid;
|
|
31
|
+
gap: 6px;
|
|
32
|
+
}
|
|
33
|
+
svg {
|
|
34
|
+
width: 100%;
|
|
35
|
+
display: block;
|
|
36
|
+
touch-action: none;
|
|
37
|
+
user-select: none;
|
|
38
|
+
}
|
|
39
|
+
svg text {
|
|
40
|
+
font: inherit;
|
|
41
|
+
}
|
|
42
|
+
small[refusal] {
|
|
43
|
+
color: var(--md-sys-color-error);
|
|
44
|
+
}
|
|
45
|
+
`; }
|
|
46
|
+
get now() {
|
|
47
|
+
return this.working ?? (this.body ? cavityWorkingOf(this.body.shape) : undefined);
|
|
48
|
+
}
|
|
49
|
+
request(w) {
|
|
50
|
+
return { wall: { ...w.wall }, start: w.start, end: w.end };
|
|
51
|
+
}
|
|
52
|
+
action(w) {
|
|
53
|
+
return { kind: 'set-cavity', part: this.body.id, cavity: this.request(w) };
|
|
54
|
+
}
|
|
55
|
+
judge(w) {
|
|
56
|
+
const ask = cavityRefusalOf(this.body.shape, this.request(w));
|
|
57
|
+
if (!ask)
|
|
58
|
+
return '';
|
|
59
|
+
if (ask.code === 'wall-too-thick')
|
|
60
|
+
return i18next.t('figure.text.section-wall-over', { max: String(ask.max) });
|
|
61
|
+
if (ask.code === 'ends-too-thick')
|
|
62
|
+
return i18next.t('figure.text.section-ends-over', { max: String(ask.max) });
|
|
63
|
+
return i18next.t('figure.text.section-not-positive');
|
|
64
|
+
}
|
|
65
|
+
/** The working inside while dragging: drawn, judged, and previewed behind when the kernel would take it. */
|
|
66
|
+
hold(w) {
|
|
67
|
+
this.working = w;
|
|
68
|
+
this.refusal = this.judge(w);
|
|
69
|
+
this.emit('command-live', this.refusal ? null : this.action(w));
|
|
70
|
+
}
|
|
71
|
+
release() {
|
|
72
|
+
const w = this.working;
|
|
73
|
+
this.dragging = null;
|
|
74
|
+
this.working = undefined;
|
|
75
|
+
if (!w)
|
|
76
|
+
return;
|
|
77
|
+
/* Refused: nothing is sent, and the reason stays said until the next drag -- letting go must not look like taking it. */
|
|
78
|
+
if (this.refusal) {
|
|
79
|
+
this.emit('command-live', null);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.emit('command', this.action(w));
|
|
83
|
+
}
|
|
84
|
+
emit(name, action) {
|
|
85
|
+
this.dispatchEvent(new CustomEvent(name, { detail: { action }, bubbles: true, composed: true }));
|
|
86
|
+
}
|
|
87
|
+
point(ev, el) {
|
|
88
|
+
const pt = el.createSVGPoint();
|
|
89
|
+
pt.x = ev.clientX;
|
|
90
|
+
pt.y = ev.clientY;
|
|
91
|
+
return pt.matrixTransform(el.getScreenCTM().inverse());
|
|
92
|
+
}
|
|
93
|
+
render() {
|
|
94
|
+
const e = this.body?.shape.extrusion;
|
|
95
|
+
const w = this.now;
|
|
96
|
+
if (!e || !w || (e.section.kind !== 'rectangle' && e.section.kind !== 'ellipse'))
|
|
97
|
+
return nothing;
|
|
98
|
+
return html `${this.top(e, w)}${this.side(e, w)}
|
|
99
|
+
${this.refusal ? html `<small refusal role="alert">${this.refusal}</small>` : nothing}`;
|
|
100
|
+
}
|
|
101
|
+
/** Seen along the push: the section, its inside and a handle on each wall. */
|
|
102
|
+
top(e, w) {
|
|
103
|
+
const [h, v] = SCREEN[e.axis];
|
|
104
|
+
const s = e.section;
|
|
105
|
+
const across = (a) => (s.kind === 'rectangle' ? s.size[a] : 2 * s.radius[a]);
|
|
106
|
+
const W = 300, H = 170, pad = 26;
|
|
107
|
+
const k = Math.min((W - 2 * pad) / across(h), (H - 2 * pad) / across(v));
|
|
108
|
+
const ox = W / 2, oy = H / 2;
|
|
109
|
+
const inside = figureCavitySectionOf(s, e.axis, w.wall);
|
|
110
|
+
const bad = !!this.refusal || !('section' in inside);
|
|
111
|
+
const shape = (sec, c, attrs) => {
|
|
112
|
+
const cx = ox + (c[h] ?? 0) * k, cy = oy + (c[v] ?? 0) * k;
|
|
113
|
+
if (sec.kind === 'ellipse')
|
|
114
|
+
return svg `<ellipse cx=${cx} cy=${cy} rx=${sec.radius[h] * k} ry=${sec.radius[v] * k} style=${attrs} />`;
|
|
115
|
+
const r = sec;
|
|
116
|
+
return svg `<rect x=${cx - (r.size[h] * k) / 2} y=${cy - (r.size[v] * k) / 2} width=${r.size[h] * k} height=${r.size[v] * k}
|
|
117
|
+
rx=${(r.corner?.[h] ?? 0) * k} ry=${(r.corner?.[v] ?? 0) * k} style=${attrs} />`;
|
|
118
|
+
};
|
|
119
|
+
const half = { [h]: (across(h) * k) / 2, [v]: (across(v) * k) / 2 };
|
|
120
|
+
/* Each wall's handle sits on the inside's edge, half way along it; the value is the wall's own. */
|
|
121
|
+
const handles = [`${h}-`, `${h}+`, `${v}-`, `${v}+`].map(side => {
|
|
122
|
+
const a = side[0], low = side[1] === '-';
|
|
123
|
+
const edge = (low ? -1 : 1) * (half[a] - w.wall[side] * k);
|
|
124
|
+
const x = a === h ? ox + edge : ox, y = a === v ? oy + edge : oy;
|
|
125
|
+
/* The number stands in the wall itself, half way through it; a wall too thin for it has it just outside. */
|
|
126
|
+
const band = w.wall[side] * k;
|
|
127
|
+
const mid = (low ? -1 : 1) * (half[a] - (band < 16 ? -9 : band / 2));
|
|
128
|
+
const tx = a === h ? ox + mid : x + 14, ty = a === v ? oy + mid + 4 : y - 10;
|
|
129
|
+
return svg `<g wall=${side} style=${`cursor:${a === h ? 'ew-resize' : 'ns-resize'}`}
|
|
130
|
+
@pointerdown=${(ev) => this.grab(ev, { kind: 'wall', side })}>
|
|
131
|
+
<rect x=${x - 6} y=${y - 6} width="12" height="12" rx="2" style="fill:var(--md-sys-color-surface);stroke:var(--md-sys-color-primary);stroke-width:1.5" />
|
|
132
|
+
<text x=${tx} y=${ty} font-size="11" text-anchor=${a === h ? 'middle' : 'start'} style="fill:var(--md-sys-color-on-surface)">${mm(w.wall[side])}</text>
|
|
133
|
+
<title>${i18next.t('figure.label.face-' + SIDE_FACE[side])} ${i18next.t('figure.label.section-wall')}</title>
|
|
134
|
+
</g>`;
|
|
135
|
+
});
|
|
136
|
+
return svg `<svg viewBox=${`0 0 ${W} ${H}`} role="img" aria-label=${i18next.t('figure.label.section-cavity-top')}
|
|
137
|
+
@pointermove=${(ev) => this.drag(ev, e, k, ox, oy, half)}
|
|
138
|
+
@pointerup=${() => this.release()} @pointercancel=${() => this.release()}>
|
|
139
|
+
${shape(s, {}, 'fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary);stroke-width:1.5')}
|
|
140
|
+
${'section' in inside
|
|
141
|
+
? shape(inside.section, inside.centre, `fill:var(--md-sys-color-surface);stroke:${bad ? 'var(--md-sys-color-error)' : 'var(--md-sys-color-primary)'};stroke-width:1.2;stroke-dasharray:4 3`)
|
|
142
|
+
: nothing}
|
|
143
|
+
${this.locked ? nothing : handles}
|
|
144
|
+
</svg>`;
|
|
145
|
+
}
|
|
146
|
+
/** Cut along the push: the walls across the first plane axis, and the two ends -- pressed to open or close. */
|
|
147
|
+
side(e, w) {
|
|
148
|
+
const [h] = SCREEN[e.axis];
|
|
149
|
+
const s = e.section;
|
|
150
|
+
const width = s.kind === 'rectangle' ? s.size[h] : 2 * s.radius[h];
|
|
151
|
+
const length = e.end - e.start;
|
|
152
|
+
const W = 300, H = 170, padX = 40, padY = 26;
|
|
153
|
+
const k = Math.min((W - 2 * padX) / width, (H - 2 * padY) / length);
|
|
154
|
+
const x0 = W / 2 - (width * k) / 2, x1 = W / 2 + (width * k) / 2, yTop = H / 2 - (length * k) / 2, yBot = H / 2 + (length * k) / 2;
|
|
155
|
+
const wl = w.wall[`${h}-`] * k, wr = w.wall[`${h}+`] * k;
|
|
156
|
+
const thick = (end) => (w[end] === 'open' ? 0 : w[end].closed * k);
|
|
157
|
+
/* The push's start is drawn at the bottom: a tote stands on its floor. */
|
|
158
|
+
const floor = thick('start'), lid = thick('end');
|
|
159
|
+
const fill = 'fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary);stroke-width:1.2';
|
|
160
|
+
const endZone = (end, y, t) => {
|
|
161
|
+
const open = w[end] === 'open';
|
|
162
|
+
const words = open
|
|
163
|
+
? i18next.t('figure.label.section-end-open')
|
|
164
|
+
: `${i18next.t('figure.label.section-end-closed')} ${mm(w[end].closed)}`;
|
|
165
|
+
const label = `${i18next.t('figure.label.face-' + END_FACE(e.axis, end))}: ${words}`;
|
|
166
|
+
const inner = end === 'start' ? y - t : y + t;
|
|
167
|
+
return svg `<g end=${end} style=${`cursor:${open ? 'pointer' : 'ns-resize'}`}
|
|
168
|
+
@pointerdown=${(ev) => this.grab(ev, { kind: 'end', end })}>
|
|
169
|
+
<rect x=${x0 + wl} y=${Math.min(y, inner) - 8} width=${x1 - x0 - wl - wr} height=${Math.abs(t) + 16} style="fill:transparent" />
|
|
170
|
+
${open ? svg `<line x1=${x0 + wl} y1=${y} x2=${x1 - wr} y2=${y} style="stroke:var(--md-sys-color-outline);stroke-dasharray:3 3" />` : nothing}
|
|
171
|
+
<text x=${W / 2} y=${end === 'start' ? yBot + 16 : yTop - 8} font-size="11" text-anchor="middle" style="fill:var(--md-sys-color-primary)">${label}</text>
|
|
172
|
+
<title>${label}</title>
|
|
173
|
+
</g>`;
|
|
174
|
+
};
|
|
175
|
+
return svg `<svg viewBox=${`0 0 ${W} ${H + 8}`} role="img" aria-label=${i18next.t('figure.label.section-cavity-side')}
|
|
176
|
+
@pointermove=${(ev) => this.dragEnd(ev, k, yTop, yBot)}
|
|
177
|
+
@pointerup=${(ev) => this.releaseEnd(ev)} @pointercancel=${() => this.release()}>
|
|
178
|
+
<rect x=${x0} y=${yTop} width=${wl} height=${yBot - yTop} style=${fill} />
|
|
179
|
+
<rect x=${x1 - wr} y=${yTop} width=${wr} height=${yBot - yTop} style=${fill} />
|
|
180
|
+
${floor ? svg `<rect x=${x0 + wl} y=${yBot - floor} width=${x1 - x0 - wl - wr} height=${floor} style=${fill} />` : nothing}
|
|
181
|
+
${lid ? svg `<rect x=${x0 + wl} y=${yTop} width=${x1 - x0 - wl - wr} height=${lid} style=${fill} />` : nothing}
|
|
182
|
+
${this.locked ? nothing : [endZone('start', yBot, floor), endZone('end', yTop, lid)]}
|
|
183
|
+
</svg>`;
|
|
184
|
+
}
|
|
185
|
+
grab(ev, what) {
|
|
186
|
+
if (this.locked)
|
|
187
|
+
return;
|
|
188
|
+
ev.stopPropagation();
|
|
189
|
+
ev.currentTarget.closest('svg').setPointerCapture(ev.pointerId);
|
|
190
|
+
this.dragging = what;
|
|
191
|
+
this.pressedAt = ev.clientY;
|
|
192
|
+
this.refusal = '';
|
|
193
|
+
}
|
|
194
|
+
drag(ev, e, k, ox, oy, half) {
|
|
195
|
+
const d = this.dragging;
|
|
196
|
+
if (d?.kind !== 'wall')
|
|
197
|
+
return;
|
|
198
|
+
const [h] = SCREEN[e.axis];
|
|
199
|
+
const p = this.point(ev, ev.currentTarget);
|
|
200
|
+
const a = d.side[0], low = d.side[1] === '-';
|
|
201
|
+
const at = a === h ? p.x - ox : p.y - oy;
|
|
202
|
+
const value = (half[a] - (low ? -at : at)) / k;
|
|
203
|
+
this.hold({ ...this.now, wall: wallDragged(this.now.wall, d.side, value, this.perSide) });
|
|
204
|
+
}
|
|
205
|
+
dragEnd(ev, k, yTop, yBot) {
|
|
206
|
+
const d = this.dragging;
|
|
207
|
+
if (d?.kind !== 'end' || this.now[d.end] === 'open')
|
|
208
|
+
return;
|
|
209
|
+
if (Math.abs(ev.clientY - this.pressedAt) < 3 && !this.working)
|
|
210
|
+
return;
|
|
211
|
+
const p = this.point(ev, ev.currentTarget);
|
|
212
|
+
const t = d.end === 'start' ? (yBot - p.y) / k : (p.y - yTop) / k;
|
|
213
|
+
this.hold({ ...this.now, [d.end]: { closed: Math.max(1, Math.round(t)) } });
|
|
214
|
+
}
|
|
215
|
+
/** A press on an end that did not move is a toggle; a drag was a thickness. */
|
|
216
|
+
releaseEnd(ev) {
|
|
217
|
+
const d = this.dragging;
|
|
218
|
+
if (d?.kind === 'end' && !this.working && Math.abs(ev.clientY - this.pressedAt) < 3) {
|
|
219
|
+
this.dragging = null;
|
|
220
|
+
const next = { ...this.now, [d.end]: endToggled(this.body.shape, this.now[d.end]) };
|
|
221
|
+
this.refusal = this.judge(next);
|
|
222
|
+
if (!this.refusal)
|
|
223
|
+
this.emit('command', this.action(next));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this.release();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
__decorate([
|
|
230
|
+
property({ attribute: false }),
|
|
231
|
+
__metadata("design:type", Object)
|
|
232
|
+
], FigureCavityDrawing.prototype, "body", void 0);
|
|
233
|
+
__decorate([
|
|
234
|
+
property({ type: Boolean }),
|
|
235
|
+
__metadata("design:type", Object)
|
|
236
|
+
], FigureCavityDrawing.prototype, "locked", void 0);
|
|
237
|
+
__decorate([
|
|
238
|
+
property({ type: Boolean }),
|
|
239
|
+
__metadata("design:type", Object)
|
|
240
|
+
], FigureCavityDrawing.prototype, "perSide", void 0);
|
|
241
|
+
__decorate([
|
|
242
|
+
state(),
|
|
243
|
+
__metadata("design:type", void 0)
|
|
244
|
+
], FigureCavityDrawing.prototype, "working", void 0);
|
|
245
|
+
__decorate([
|
|
246
|
+
state(),
|
|
247
|
+
__metadata("design:type", Object)
|
|
248
|
+
], FigureCavityDrawing.prototype, "refusal", void 0);
|
|
249
|
+
FigureCavityDrawing = __decorate([
|
|
250
|
+
customElement('figure-cavity-drawing')
|
|
251
|
+
], FigureCavityDrawing);
|
|
252
|
+
export { FigureCavityDrawing };
|
|
253
|
+
//# sourceMappingURL=figure-cavity-drawing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figure-cavity-drawing.js","sourceRoot":"","sources":["../../client/modeller/figure-cavity-drawing.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement, nothing, svg } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { figureCavitySectionOf } from '@hatiolab/figure-model'\nimport type { FigureAuthoringAction, FigureCavityRequest, FigureEvaluatedShape, FigureSection } from '@hatiolab/figure-model'\nimport { cavityRefusalOf, cavityWorkingOf, END_FACE, endToggled, mm, SIDE_FACE, wallDragged } from './section-controls.js'\n\ntype Axis = 'x' | 'y' | 'z'\ntype End = 'start' | 'end'\ntype Body = { id: string; shape: FigureEvaluatedShape }\n\n/** How the section is laid on the screen, by the axis it is pushed along: across to the right, then down. */\nconst SCREEN: Record<Axis, [Axis, Axis]> = { y: ['x', 'z'], x: ['z', 'y'], z: ['x', 'y'] }\n\n/**\n * 속 공간 그림 — the inside of a pushed part drawn, and edited by drawing (user 2026-10-01: 「이해와 편집이 직관적이지 않다」).\n *\n * Seen along the push, the section and its inside: a handle in the middle of each wall is dragged to its thickness,\n * every wall together unless 면마다 벽 is on. Cut along the push, the walls and the two ends: an end pressed opens or\n * closes it, a closed end's inner line is dragged to its thickness. While dragging, the figure behind shows what the\n * inside makes (`command-live`); letting go sends one `set-cavity`. What the kernel would refuse is drawn in the error\n * colour and said, by the kernel's own check, and is not sent.\n */\n@customElement('figure-cavity-drawing')\nexport class FigureCavityDrawing extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: grid;\n gap: 6px;\n }\n svg {\n width: 100%;\n display: block;\n touch-action: none;\n user-select: none;\n }\n svg text {\n font: inherit;\n }\n small[refusal] {\n color: var(--md-sys-color-error);\n }\n `\n\n @property({ attribute: false }) body?: Body\n @property({ type: Boolean }) locked = false\n /** Walls one per side: a handle moves its own wall only. */\n @property({ type: Boolean }) perSide = false\n\n /** What is being dragged, and the inside it makes; undefined when nothing is. */\n @state() private working?: ReturnType<typeof cavityWorkingOf>\n @state() private refusal = ''\n private dragging: { kind: 'wall'; side: string } | { kind: 'end'; end: End } | null = null\n private pressedAt = 0\n\n private get now() {\n return this.working ?? (this.body ? cavityWorkingOf(this.body.shape) : undefined)\n }\n\n private request(w: NonNullable<ReturnType<typeof cavityWorkingOf>>): FigureCavityRequest {\n return { wall: { ...w.wall }, start: w.start, end: w.end }\n }\n\n private action(w: NonNullable<ReturnType<typeof cavityWorkingOf>>): FigureAuthoringAction {\n return { kind: 'set-cavity', part: this.body!.id, cavity: this.request(w) }\n }\n\n private judge(w: NonNullable<ReturnType<typeof cavityWorkingOf>>): string {\n const ask = cavityRefusalOf(this.body!.shape, this.request(w))\n if (!ask) return ''\n if (ask.code === 'wall-too-thick') return i18next.t('figure.text.section-wall-over', { max: String(ask.max) })\n if (ask.code === 'ends-too-thick') return i18next.t('figure.text.section-ends-over', { max: String(ask.max) })\n return i18next.t('figure.text.section-not-positive')\n }\n\n /** The working inside while dragging: drawn, judged, and previewed behind when the kernel would take it. */\n private hold(w: NonNullable<ReturnType<typeof cavityWorkingOf>>) {\n this.working = w\n this.refusal = this.judge(w)\n this.emit('command-live', this.refusal ? null : this.action(w))\n }\n\n private release() {\n const w = this.working\n this.dragging = null\n this.working = undefined\n if (!w) return\n /* Refused: nothing is sent, and the reason stays said until the next drag -- letting go must not look like taking it. */\n if (this.refusal) {\n this.emit('command-live', null)\n return\n }\n this.emit('command', this.action(w))\n }\n\n private emit(name: 'command' | 'command-live', action: FigureAuthoringAction | null) {\n this.dispatchEvent(new CustomEvent(name, { detail: { action }, bubbles: true, composed: true }))\n }\n\n private point(ev: PointerEvent, el: SVGSVGElement) {\n const pt = el.createSVGPoint()\n pt.x = ev.clientX\n pt.y = ev.clientY\n return pt.matrixTransform(el.getScreenCTM()!.inverse())\n }\n\n render() {\n const e = this.body?.shape.extrusion\n const w = this.now\n if (!e || !w || (e.section.kind !== 'rectangle' && e.section.kind !== 'ellipse')) return nothing\n return html`${this.top(e, w)}${this.side(e, w)}\n ${this.refusal ? html`<small refusal role=\"alert\">${this.refusal}</small>` : nothing}`\n }\n\n /** Seen along the push: the section, its inside and a handle on each wall. */\n private top(e: NonNullable<FigureEvaluatedShape['extrusion']>, w: NonNullable<ReturnType<typeof cavityWorkingOf>>) {\n const [h, v] = SCREEN[e.axis]\n const s = e.section\n const across = (a: Axis) => (s.kind === 'rectangle' ? s.size[a]! : 2 * (s as { radius: Record<string, number> }).radius[a]!)\n const W = 300,\n H = 170,\n pad = 26\n const k = Math.min((W - 2 * pad) / across(h), (H - 2 * pad) / across(v))\n const ox = W / 2,\n oy = H / 2\n const inside = figureCavitySectionOf(s as FigureSection, e.axis, w.wall)\n const bad = !!this.refusal || !('section' in inside)\n const shape = (sec: FigureSection, c: Partial<Record<Axis, number>>, attrs: string) => {\n const cx = ox + (c[h] ?? 0) * k,\n cy = oy + (c[v] ?? 0) * k\n if (sec.kind === 'ellipse') return svg`<ellipse cx=${cx} cy=${cy} rx=${sec.radius[h]! * k} ry=${sec.radius[v]! * k} style=${attrs} />`\n const r = sec as { size: Record<string, number>; corner?: Record<string, number> }\n return svg`<rect x=${cx - (r.size[h]! * k) / 2} y=${cy - (r.size[v]! * k) / 2} width=${r.size[h]! * k} height=${r.size[v]! * k}\n rx=${(r.corner?.[h] ?? 0) * k} ry=${(r.corner?.[v] ?? 0) * k} style=${attrs} />`\n }\n const half = { [h]: (across(h) * k) / 2, [v]: (across(v) * k) / 2 } as Record<Axis, number>\n /* Each wall's handle sits on the inside's edge, half way along it; the value is the wall's own. */\n const handles = [`${h}-`, `${h}+`, `${v}-`, `${v}+`].map(side => {\n const a = side[0] as Axis,\n low = side[1] === '-'\n const edge = (low ? -1 : 1) * (half[a] - w.wall[side]! * k)\n const x = a === h ? ox + edge : ox,\n y = a === v ? oy + edge : oy\n /* The number stands in the wall itself, half way through it; a wall too thin for it has it just outside. */\n const band = w.wall[side]! * k\n const mid = (low ? -1 : 1) * (half[a] - (band < 16 ? -9 : band / 2))\n const tx = a === h ? ox + mid : x + 14,\n ty = a === v ? oy + mid + 4 : y - 10\n return svg`<g wall=${side} style=${`cursor:${a === h ? 'ew-resize' : 'ns-resize'}`}\n @pointerdown=${(ev: PointerEvent) => this.grab(ev, { kind: 'wall', side })}>\n <rect x=${x - 6} y=${y - 6} width=\"12\" height=\"12\" rx=\"2\" style=\"fill:var(--md-sys-color-surface);stroke:var(--md-sys-color-primary);stroke-width:1.5\" />\n <text x=${tx} y=${ty} font-size=\"11\" text-anchor=${a === h ? 'middle' : 'start'} style=\"fill:var(--md-sys-color-on-surface)\">${mm(w.wall[side]!)}</text>\n <title>${i18next.t('figure.label.face-' + SIDE_FACE[side])} ${i18next.t('figure.label.section-wall')}</title>\n </g>`\n })\n return svg`<svg viewBox=${`0 0 ${W} ${H}`} role=\"img\" aria-label=${i18next.t('figure.label.section-cavity-top')}\n @pointermove=${(ev: PointerEvent) => this.drag(ev, e, k, ox, oy, half)}\n @pointerup=${() => this.release()} @pointercancel=${() => this.release()}>\n ${shape(s as FigureSection, {}, 'fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary);stroke-width:1.5')}\n ${'section' in inside\n ? shape(inside.section, inside.centre, `fill:var(--md-sys-color-surface);stroke:${bad ? 'var(--md-sys-color-error)' : 'var(--md-sys-color-primary)'};stroke-width:1.2;stroke-dasharray:4 3`)\n : nothing}\n ${this.locked ? nothing : handles}\n </svg>`\n }\n\n /** Cut along the push: the walls across the first plane axis, and the two ends -- pressed to open or close. */\n private side(e: NonNullable<FigureEvaluatedShape['extrusion']>, w: NonNullable<ReturnType<typeof cavityWorkingOf>>) {\n const [h] = SCREEN[e.axis]\n const s = e.section\n const width = s.kind === 'rectangle' ? s.size[h]! : 2 * (s as { radius: Record<string, number> }).radius[h]!\n const length = e.end - e.start\n const W = 300,\n H = 170,\n padX = 40,\n padY = 26\n const k = Math.min((W - 2 * padX) / width, (H - 2 * padY) / length)\n const x0 = W / 2 - (width * k) / 2,\n x1 = W / 2 + (width * k) / 2,\n yTop = H / 2 - (length * k) / 2,\n yBot = H / 2 + (length * k) / 2\n const wl = w.wall[`${h}-`]! * k,\n wr = w.wall[`${h}+`]! * k\n const thick = (end: End) => (w[end] === 'open' ? 0 : (w[end] as { closed: number }).closed * k)\n /* The push's start is drawn at the bottom: a tote stands on its floor. */\n const floor = thick('start'),\n lid = thick('end')\n const fill = 'fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary);stroke-width:1.2'\n const endZone = (end: End, y: number, t: number) => {\n const open = w[end] === 'open'\n const words = open\n ? i18next.t('figure.label.section-end-open')\n : `${i18next.t('figure.label.section-end-closed')} ${mm((w[end] as { closed: number }).closed)}`\n const label = `${i18next.t('figure.label.face-' + END_FACE(e.axis, end))}: ${words}`\n const inner = end === 'start' ? y - t : y + t\n return svg`<g end=${end} style=${`cursor:${open ? 'pointer' : 'ns-resize'}`}\n @pointerdown=${(ev: PointerEvent) => this.grab(ev, { kind: 'end', end })}>\n <rect x=${x0 + wl} y=${Math.min(y, inner) - 8} width=${x1 - x0 - wl - wr} height=${Math.abs(t) + 16} style=\"fill:transparent\" />\n ${open ? svg`<line x1=${x0 + wl} y1=${y} x2=${x1 - wr} y2=${y} style=\"stroke:var(--md-sys-color-outline);stroke-dasharray:3 3\" />` : nothing}\n <text x=${W / 2} y=${end === 'start' ? yBot + 16 : yTop - 8} font-size=\"11\" text-anchor=\"middle\" style=\"fill:var(--md-sys-color-primary)\">${label}</text>\n <title>${label}</title>\n </g>`\n }\n return svg`<svg viewBox=${`0 0 ${W} ${H + 8}`} role=\"img\" aria-label=${i18next.t('figure.label.section-cavity-side')}\n @pointermove=${(ev: PointerEvent) => this.dragEnd(ev, k, yTop, yBot)}\n @pointerup=${(ev: PointerEvent) => this.releaseEnd(ev)} @pointercancel=${() => this.release()}>\n <rect x=${x0} y=${yTop} width=${wl} height=${yBot - yTop} style=${fill} />\n <rect x=${x1 - wr} y=${yTop} width=${wr} height=${yBot - yTop} style=${fill} />\n ${floor ? svg`<rect x=${x0 + wl} y=${yBot - floor} width=${x1 - x0 - wl - wr} height=${floor} style=${fill} />` : nothing}\n ${lid ? svg`<rect x=${x0 + wl} y=${yTop} width=${x1 - x0 - wl - wr} height=${lid} style=${fill} />` : nothing}\n ${this.locked ? nothing : [endZone('start', yBot, floor), endZone('end', yTop, lid)]}\n </svg>`\n }\n\n private grab(ev: PointerEvent, what: NonNullable<FigureCavityDrawing['dragging']>) {\n if (this.locked) return\n ev.stopPropagation()\n ;(ev.currentTarget as Element).closest('svg')!.setPointerCapture(ev.pointerId)\n this.dragging = what\n this.pressedAt = ev.clientY\n this.refusal = ''\n }\n\n private drag(ev: PointerEvent, e: NonNullable<FigureEvaluatedShape['extrusion']>, k: number, ox: number, oy: number, half: Record<Axis, number>) {\n const d = this.dragging\n if (d?.kind !== 'wall') return\n const [h] = SCREEN[e.axis]\n const p = this.point(ev, ev.currentTarget as SVGSVGElement)\n const a = d.side[0] as Axis,\n low = d.side[1] === '-'\n const at = a === h ? p.x - ox : p.y - oy\n const value = (half[a] - (low ? -at : at)) / k\n this.hold({ ...this.now!, wall: wallDragged(this.now!.wall, d.side, value, this.perSide) })\n }\n\n private dragEnd(ev: PointerEvent, k: number, yTop: number, yBot: number) {\n const d = this.dragging\n if (d?.kind !== 'end' || this.now![d.end] === 'open') return\n if (Math.abs(ev.clientY - this.pressedAt) < 3 && !this.working) return\n const p = this.point(ev, ev.currentTarget as SVGSVGElement)\n const t = d.end === 'start' ? (yBot - p.y) / k : (p.y - yTop) / k\n this.hold({ ...this.now!, [d.end]: { closed: Math.max(1, Math.round(t)) } })\n }\n\n /** A press on an end that did not move is a toggle; a drag was a thickness. */\n private releaseEnd(ev: PointerEvent) {\n const d = this.dragging\n if (d?.kind === 'end' && !this.working && Math.abs(ev.clientY - this.pressedAt) < 3) {\n this.dragging = null\n const next = { ...this.now!, [d.end]: endToggled(this.body!.shape, this.now![d.end]) }\n this.refusal = this.judge(next)\n if (!this.refusal) this.emit('command', this.action(next))\n return\n }\n this.release()\n }\n}\n\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { type FaceView, type FastenMode } from './fasten-geometry.js';
|
|
3
|
+
import type { Alignment } from './links.js';
|
|
4
|
+
type Axis = 'x' | 'y' | 'z';
|
|
5
|
+
/** A change the drawing asks of the fastening form. */
|
|
6
|
+
export type FastenChange = {
|
|
7
|
+
targetFace: string;
|
|
8
|
+
} | {
|
|
9
|
+
mode: FastenMode;
|
|
10
|
+
} | {
|
|
11
|
+
axis: Axis;
|
|
12
|
+
how: Alignment;
|
|
13
|
+
mm?: number;
|
|
14
|
+
} | {
|
|
15
|
+
gap: number;
|
|
16
|
+
};
|
|
17
|
+
declare const FigureFastenDrawing_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
18
|
+
/**
|
|
19
|
+
* 붙이기 그림 (user 2026-10-01) — which face of what the part is fastened to, how, and where on it; each of them chosen
|
|
20
|
+
* by pressing or dragging the drawing, none by a list of faces.
|
|
21
|
+
*
|
|
22
|
+
* The target is drawn as a box whose faces are pressed (the three it hides as tags beside it), or as the one plane the
|
|
23
|
+
* figure stands by. How is three small side views -- set on it, flush with it, its centre on it (only for a part that
|
|
24
|
+
* turns about a joint) -- and the chosen one's gap is dragged. Where is the face seen from above with the part's
|
|
25
|
+
* footprint on it: dragged, caught at the centre and at the edges from inside and outside, a guide drawn where it is
|
|
26
|
+
* caught and a dimension where it is not, which is pressed to type the number. It may go past the edges: the part that
|
|
27
|
+
* does is shaded, and a part off the face altogether is said so. Nothing is decided here: every change goes to the form
|
|
28
|
+
* (`fasten-change`), and a press or a drag let go says so (`fasten-release`), for the form to apply a fastening it holds.
|
|
29
|
+
*/
|
|
30
|
+
export declare class FigureFastenDrawing extends FigureFastenDrawing_base {
|
|
31
|
+
static styles: import("lit").CSSResult;
|
|
32
|
+
/** The target's name, or empty for the plane the figure stands by. */
|
|
33
|
+
target: string;
|
|
34
|
+
/** The plane's name by the figure's placement: 바닥 · 천장 · 벽 · 중심. */
|
|
35
|
+
planeWord: string;
|
|
36
|
+
faces: string[];
|
|
37
|
+
face: string;
|
|
38
|
+
modes: FastenMode[];
|
|
39
|
+
mode: FastenMode;
|
|
40
|
+
view?: FaceView;
|
|
41
|
+
align: Partial<Record<Axis, Alignment>>;
|
|
42
|
+
alignMm: Partial<Record<Axis, number>>;
|
|
43
|
+
gap: number;
|
|
44
|
+
/** The part being fastened (not `part`: that is every element's own). */
|
|
45
|
+
partName: string;
|
|
46
|
+
locked: boolean;
|
|
47
|
+
/** The form already says how the fastening it has lies on the face: the drawing says it only while dragging. */
|
|
48
|
+
said: boolean;
|
|
49
|
+
/** Where the footprint is while dragging, and what it is caught by; undefined when nothing is dragged. */
|
|
50
|
+
private dragged?;
|
|
51
|
+
private typing?;
|
|
52
|
+
private scale;
|
|
53
|
+
private gapFrom?;
|
|
54
|
+
private change;
|
|
55
|
+
private released;
|
|
56
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
57
|
+
/** The target box, its faces pressed; or the plane. */
|
|
58
|
+
private renderTarget;
|
|
59
|
+
/** How: three side views; the chosen one's gap is dragged. */
|
|
60
|
+
private renderModes;
|
|
61
|
+
/** Where the part's centre is on each axis now: the drag's, or the form's. */
|
|
62
|
+
private centreOf;
|
|
63
|
+
/** What each axis is caught by now: at the centre, level with an edge, touching one from outside, or nothing. */
|
|
64
|
+
private caughtOf;
|
|
65
|
+
private renderWhere;
|
|
66
|
+
private renderTyping;
|
|
67
|
+
private grab;
|
|
68
|
+
/** The footprint follows the pointer, caught at the centre and edges within a few pixels. */
|
|
69
|
+
private drag;
|
|
70
|
+
private drop;
|
|
71
|
+
/** Arrows move the part a millimetre, ten with Shift; the centre and edges still catch it. */
|
|
72
|
+
private keys;
|
|
73
|
+
}
|
|
74
|
+
export {};
|