@things-factory/figure-ui 10.1.39 → 10.1.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/graphql/index.ts +1 -0
- package/client/modeller/figure-v3-material.ts +248 -0
- package/client/modeller/figure-v3-parts.ts +16 -1
- package/client/modeller/figure-v3-properties.ts +254 -0
- package/client/modeller/modeller-route.ts +25 -0
- package/client/modeller/v3-align.ts +84 -0
- package/client/modeller/v3-canvas.ts +167 -5
- package/client/modeller/v3-editor.ts +207 -47
- package/client/modeller/v3-figure-facts.ts +53 -0
- package/client/modeller/v3-gizmo.ts +80 -0
- package/client/modeller/v3-occupancy.ts +6 -2
- package/client/pages/figure-list-page.ts +5 -3
- package/client/pages/figure-modeller-page.ts +9 -0
- package/client/pages/figure-v3-modeller-page.ts +68 -13
- package/client/types.ts +2 -0
- package/dist-client/graphql/index.js +1 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/figure-v3-material.d.ts +36 -0
- package/dist-client/modeller/figure-v3-material.js +249 -0
- package/dist-client/modeller/figure-v3-material.js.map +1 -0
- package/dist-client/modeller/figure-v3-parts.js +16 -1
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/figure-v3-properties.d.ts +46 -0
- package/dist-client/modeller/figure-v3-properties.js +259 -0
- package/dist-client/modeller/figure-v3-properties.js.map +1 -0
- package/dist-client/modeller/modeller-route.d.ts +16 -0
- package/dist-client/modeller/modeller-route.js +27 -0
- package/dist-client/modeller/modeller-route.js.map +1 -0
- package/dist-client/modeller/v3-align.d.ts +26 -0
- package/dist-client/modeller/v3-align.js +65 -0
- package/dist-client/modeller/v3-align.js.map +1 -0
- package/dist-client/modeller/v3-canvas.d.ts +24 -0
- package/dist-client/modeller/v3-canvas.js +181 -5
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-editor.d.ts +24 -0
- package/dist-client/modeller/v3-editor.js +218 -44
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-figure-facts.d.ts +26 -0
- package/dist-client/modeller/v3-figure-facts.js +34 -0
- package/dist-client/modeller/v3-figure-facts.js.map +1 -0
- package/dist-client/modeller/v3-gizmo.d.ts +48 -0
- package/dist-client/modeller/v3-gizmo.js +40 -0
- package/dist-client/modeller/v3-gizmo.js.map +1 -0
- package/dist-client/modeller/v3-occupancy.d.ts +5 -1
- package/dist-client/modeller/v3-occupancy.js +4 -2
- package/dist-client/modeller/v3-occupancy.js.map +1 -1
- package/dist-client/pages/figure-list-page.d.ts +1 -0
- package/dist-client/pages/figure-list-page.js +5 -3
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.js +9 -0
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +16 -2
- package/dist-client/pages/figure-v3-modeller-page.js +78 -22
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +2 -0
- package/dist-client/types.js.map +1 -1
- package/package.json +4 -4
- package/test/modeller-route.test.ts +42 -0
- package/test/v3-align.test.ts +64 -0
- package/test/v3-gizmo.test.ts +97 -0
- package/test/v3-material.test.ts +68 -0
- package/test/v3-parts-wiring.test.ts +56 -0
- package/test/v3-properties.test.ts +93 -0
- package/test/v3-theme.test.ts +67 -0
- package/translations/en.json +11 -0
- package/translations/ja.json +11 -0
- package/translations/ko.json +11 -0
- package/translations/ms.json +11 -0
- package/translations/zh.json +11 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import { LitElement, html, css } from 'lit';
|
|
3
3
|
import { customElement, property } from 'lit/decorators.js';
|
|
4
|
+
import { i18next } from '@operato/i18n';
|
|
4
5
|
import * as THREE from 'three';
|
|
5
6
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
7
|
+
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
|
|
6
8
|
import { replaceV3Meshes, disposeV3Meshes } from './v3-renderer.js';
|
|
7
9
|
/** Renders evaluated V3 geometry directly. There is no parts/base or things-scene conversion. */
|
|
8
10
|
let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
@@ -11,16 +13,72 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
11
13
|
this.parts = [];
|
|
12
14
|
this.palette = {};
|
|
13
15
|
this.context = '';
|
|
16
|
+
/** The selected part, and what the gizmo may write for it (`gizmoReachOf`). Nothing reachable, no gizmo. */
|
|
17
|
+
this.selected = '';
|
|
18
|
+
this.reach = { translate: false, rotate: false, scale: false };
|
|
19
|
+
/** Move · turn · size — W · E · R, as in the V2 modeller. */
|
|
20
|
+
this.mode = 'translate';
|
|
21
|
+
/** What the gizmo holds: a stand-in at the body's origin, so the body's own matrix stays the evaluation's. */
|
|
22
|
+
this.proxy = new THREE.Object3D();
|
|
23
|
+
/** The pointerup that ends a drag is not a pick — releasing off the body would otherwise deselect it. */
|
|
24
|
+
this.justDragged = false;
|
|
14
25
|
this.scene = new THREE.Scene();
|
|
15
26
|
this.group = new THREE.Group();
|
|
16
27
|
this.camera = new THREE.PerspectiveCamera(40, 1, 0.1, 1000000);
|
|
17
28
|
this.initialized = false;
|
|
18
29
|
this.contextAtRender = '';
|
|
30
|
+
this.key = (event) => {
|
|
31
|
+
const mode = { w: 'translate', e: 'rotate', r: 'scale' }[event.key.toLowerCase()];
|
|
32
|
+
if (mode && this.reach[mode])
|
|
33
|
+
this.mode = mode;
|
|
34
|
+
};
|
|
35
|
+
this.dragging = (event) => {
|
|
36
|
+
const on = !!event.value;
|
|
37
|
+
if (this.controls)
|
|
38
|
+
this.controls.enabled = !on;
|
|
39
|
+
const body = this.group.children.find(child => child.name === this.selected);
|
|
40
|
+
if (on && body) {
|
|
41
|
+
this.proxy.updateMatrix();
|
|
42
|
+
this.dragStart = { proxy: this.proxy.matrix.clone(), mesh: body.matrix.clone(), body };
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const start = this.dragStart;
|
|
46
|
+
this.dragStart = undefined;
|
|
47
|
+
if (!start)
|
|
48
|
+
return;
|
|
49
|
+
/* Either listener may see the release first; the flag lasts past both, then clears. */
|
|
50
|
+
this.justDragged = true;
|
|
51
|
+
setTimeout(() => (this.justDragged = false));
|
|
52
|
+
const result = {};
|
|
53
|
+
if (this.mode === 'translate')
|
|
54
|
+
result.position = this.proxy.position.toArray();
|
|
55
|
+
if (this.mode === 'rotate') {
|
|
56
|
+
/* rigid@1 composes Rx·Ry·Rz — three's 'XYZ' order — so the angles come back as the inputs mean them. */
|
|
57
|
+
const euler = new THREE.Euler().setFromQuaternion(this.proxy.quaternion, 'XYZ');
|
|
58
|
+
result.rotation = [euler.x, euler.y, euler.z].map(THREE.MathUtils.radToDeg);
|
|
59
|
+
}
|
|
60
|
+
if (this.mode === 'scale')
|
|
61
|
+
result.scale = this.proxy.scale.toArray();
|
|
62
|
+
this.dispatchEvent(new CustomEvent('part-transformed', { detail: { id: this.selected, result }, bubbles: true, composed: true }));
|
|
63
|
+
};
|
|
64
|
+
/** While dragging, the body follows the stand-in; the next evaluation replaces it with where the inputs put it. */
|
|
65
|
+
this.follow = () => {
|
|
66
|
+
const start = this.dragStart;
|
|
67
|
+
if (!start)
|
|
68
|
+
return;
|
|
69
|
+
this.proxy.updateMatrix();
|
|
70
|
+
const delta = this.proxy.matrix.clone().multiply(start.proxy.clone().invert());
|
|
71
|
+
start.body.matrix.copy(delta.multiply(start.mesh));
|
|
72
|
+
start.body.matrixWorldNeedsUpdate = true;
|
|
73
|
+
this.group.updateMatrixWorld(true);
|
|
74
|
+
};
|
|
19
75
|
this.draw = () => {
|
|
20
76
|
if (this.renderer)
|
|
21
77
|
this.renderer.render(this.scene, this.camera);
|
|
22
78
|
};
|
|
23
79
|
this.pick = (event) => {
|
|
80
|
+
if (this.justDragged || this.transform?.dragging)
|
|
81
|
+
return;
|
|
24
82
|
const r = event.currentTarget.getBoundingClientRect(), ray = new THREE.Raycaster();
|
|
25
83
|
ray.setFromCamera(new THREE.Vector2(((event.clientX - r.left) / r.width) * 2 - 1, (-(event.clientY - r.top) / r.height) * 2 + 1), this.camera);
|
|
26
84
|
const hit = ray.intersectObjects(this.group.children)[0];
|
|
@@ -33,24 +91,70 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
33
91
|
min-width: 0;
|
|
34
92
|
min-height: 0;
|
|
35
93
|
position: relative;
|
|
36
|
-
|
|
94
|
+
/*
|
|
95
|
+
The working board is one step above the surface in both themes (UX, 2026-09-24): surface-container is 238
|
|
96
|
+
in light and 31 in dark. The 3D background is read from this same value (paintBackground), so the
|
|
97
|
+
first paint and the scene agree and there is no flash between them.
|
|
98
|
+
*/
|
|
99
|
+
background: var(--md-sys-color-surface-container);
|
|
37
100
|
}
|
|
38
101
|
canvas {
|
|
39
102
|
display: block;
|
|
40
103
|
width: 100%;
|
|
41
104
|
height: 100%;
|
|
42
105
|
}
|
|
106
|
+
div[gizmo] {
|
|
107
|
+
position: absolute;
|
|
108
|
+
top: 8px;
|
|
109
|
+
left: 8px;
|
|
110
|
+
display: flex;
|
|
111
|
+
gap: 2px;
|
|
112
|
+
padding: 2px;
|
|
113
|
+
border-radius: 6px;
|
|
114
|
+
background: var(--md-sys-color-surface);
|
|
115
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
116
|
+
}
|
|
117
|
+
div[gizmo] button {
|
|
118
|
+
font: inherit;
|
|
119
|
+
font-size: 12px;
|
|
120
|
+
padding: 3px 8px;
|
|
121
|
+
border: none;
|
|
122
|
+
border-radius: 4px;
|
|
123
|
+
background: none;
|
|
124
|
+
color: var(--md-sys-color-on-surface);
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
}
|
|
127
|
+
div[gizmo] button[on] {
|
|
128
|
+
background: var(--md-sys-color-secondary-container);
|
|
129
|
+
color: var(--md-sys-color-on-secondary-container);
|
|
130
|
+
}
|
|
131
|
+
div[gizmo] button:disabled {
|
|
132
|
+
opacity: 0.4;
|
|
133
|
+
cursor: default;
|
|
134
|
+
}
|
|
43
135
|
p {
|
|
44
136
|
position: absolute;
|
|
45
137
|
inset: 45% 1rem auto;
|
|
46
138
|
text-align: center;
|
|
47
139
|
pointer-events: none;
|
|
48
|
-
color:
|
|
140
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
49
141
|
}
|
|
50
142
|
`; }
|
|
51
143
|
render() {
|
|
52
|
-
|
|
53
|
-
|
|
144
|
+
const any = this.selected && (this.reach.translate || this.reach.rotate || this.reach.scale);
|
|
145
|
+
return html `<canvas aria-label="Figure 3D 모델" tabindex="0" @pointerup=${this.pick} @keydown=${this.key}></canvas
|
|
146
|
+
>${any
|
|
147
|
+
? html `<div gizmo>
|
|
148
|
+
${['translate', 'rotate', 'scale'].map((mode, i) => html `<button
|
|
149
|
+
?on=${this.mode === mode}
|
|
150
|
+
?disabled=${!this.reach[mode]}
|
|
151
|
+
title=${`${i18next.t('figure.label.gizmo-' + mode)} (${'WER'[i]})`}
|
|
152
|
+
@click=${() => (this.mode = mode)}
|
|
153
|
+
>
|
|
154
|
+
${i18next.t('figure.label.gizmo-' + mode)}
|
|
155
|
+
</button>`)}
|
|
156
|
+
</div>`
|
|
157
|
+
: null}${this.parts.length ? null : html `<p>도형을 추가해 모델링을 시작하세요.</p>`}`;
|
|
54
158
|
}
|
|
55
159
|
firstUpdated() {
|
|
56
160
|
this.initialize();
|
|
@@ -66,13 +170,28 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
66
170
|
const canvas = this.renderRoot.querySelector('canvas');
|
|
67
171
|
this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true, preserveDrawingBuffer: true });
|
|
68
172
|
this.renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
|
|
69
|
-
this.
|
|
173
|
+
this.paintBackground();
|
|
174
|
+
/*
|
|
175
|
+
The app switches theme by the body's class (light · dark). Reading the colour once at start left a light
|
|
176
|
+
board in the middle of a dark screen after every switch (seen 2026-09-24), so it is read again then.
|
|
177
|
+
*/
|
|
178
|
+
this.themeObserver = new MutationObserver(() => {
|
|
179
|
+
this.paintBackground();
|
|
180
|
+
this.draw();
|
|
181
|
+
});
|
|
182
|
+
this.themeObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });
|
|
70
183
|
this.scene.add(this.group, new THREE.HemisphereLight(0xffffff, 0x62717d, 2.5));
|
|
71
184
|
const light = new THREE.DirectionalLight(0xffffff, 3);
|
|
72
185
|
light.position.set(1, 2, 3);
|
|
73
186
|
this.scene.add(light);
|
|
74
187
|
this.controls = new OrbitControls(this.camera, canvas);
|
|
75
188
|
this.controls.addEventListener('change', this.draw);
|
|
189
|
+
this.transform = new TransformControls(this.camera, canvas);
|
|
190
|
+
this.transform.setSpace('world');
|
|
191
|
+
this.transform.addEventListener('change', this.draw);
|
|
192
|
+
this.transform.addEventListener('dragging-changed', this.dragging);
|
|
193
|
+
this.transform.addEventListener('objectChange', this.follow);
|
|
194
|
+
this.scene.add(this.proxy, this.transform.getHelper());
|
|
76
195
|
this.observer = new ResizeObserver(() => {
|
|
77
196
|
const r = this.getBoundingClientRect();
|
|
78
197
|
if (r.width && r.height) {
|
|
@@ -89,6 +208,38 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
89
208
|
updated(changed) {
|
|
90
209
|
if (this.initialized && (changed.has('parts') || changed.has('palette') || changed.has('context')))
|
|
91
210
|
this.updateGeometry(this.contextAtRender !== this.context);
|
|
211
|
+
if (this.initialized && ['parts', 'selected', 'reach', 'mode', 'context'].some(key => changed.has(key)))
|
|
212
|
+
this.placeGizmo();
|
|
213
|
+
}
|
|
214
|
+
/** Puts the gizmo on the selected body, in a mode it may write — or takes it away. */
|
|
215
|
+
placeGizmo() {
|
|
216
|
+
const transform = this.transform;
|
|
217
|
+
if (!transform || this.dragStart)
|
|
218
|
+
return;
|
|
219
|
+
if (!this.reach[this.mode]) {
|
|
220
|
+
const first = ['translate', 'rotate', 'scale'].find(mode => this.reach[mode]);
|
|
221
|
+
if (first && first !== this.mode) {
|
|
222
|
+
this.mode = first;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const body = this.group.children.find(child => child.name === this.selected);
|
|
227
|
+
if (!body || !this.reach[this.mode]) {
|
|
228
|
+
transform.detach();
|
|
229
|
+
this.draw();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const position = new THREE.Vector3(), quaternion = new THREE.Quaternion(), scale = new THREE.Vector3();
|
|
233
|
+
body.matrix.decompose(position, quaternion, scale);
|
|
234
|
+
this.proxy.position.copy(position);
|
|
235
|
+
this.proxy.quaternion.copy(quaternion);
|
|
236
|
+
this.proxy.scale.set(1, 1, 1);
|
|
237
|
+
this.proxy.updateMatrixWorld(true);
|
|
238
|
+
transform.setMode(this.mode);
|
|
239
|
+
/* Size follows the body's own axes; moving and turning read in the asset's axes, which the inputs are in. */
|
|
240
|
+
transform.setSpace(this.mode === 'scale' ? 'local' : 'world');
|
|
241
|
+
transform.attach(this.proxy);
|
|
242
|
+
this.draw();
|
|
92
243
|
}
|
|
93
244
|
updateGeometry(fit) {
|
|
94
245
|
try {
|
|
@@ -123,13 +274,26 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
123
274
|
this.controls?.update();
|
|
124
275
|
this.draw();
|
|
125
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* The scene's background, taken from the host's computed background — the token resolved and normalised to
|
|
279
|
+
* `rgb(r, g, b)`, which THREE.Color reads. The token's own text may use the space-separated form it does not.
|
|
280
|
+
*/
|
|
281
|
+
paintBackground() {
|
|
282
|
+
const colour = getComputedStyle(this).backgroundColor;
|
|
283
|
+
if (colour && colour !== 'rgba(0, 0, 0, 0)' && colour !== 'transparent')
|
|
284
|
+
this.scene.background = new THREE.Color(colour);
|
|
285
|
+
}
|
|
126
286
|
thumbnail() {
|
|
127
287
|
this.draw();
|
|
128
288
|
return this.renderer?.domElement.toDataURL('image/png');
|
|
129
289
|
}
|
|
130
290
|
disconnectedCallback() {
|
|
131
291
|
this.observer?.disconnect();
|
|
292
|
+
this.themeObserver?.disconnect();
|
|
132
293
|
this.controls?.dispose();
|
|
294
|
+
this.transform?.detach();
|
|
295
|
+
this.transform?.dispose();
|
|
296
|
+
this.transform = undefined;
|
|
133
297
|
disposeV3Meshes(this.group);
|
|
134
298
|
this.renderer?.dispose();
|
|
135
299
|
this.renderer = undefined;
|
|
@@ -150,6 +314,18 @@ __decorate([
|
|
|
150
314
|
property(),
|
|
151
315
|
__metadata("design:type", Object)
|
|
152
316
|
], FigureV3Canvas.prototype, "context", void 0);
|
|
317
|
+
__decorate([
|
|
318
|
+
property(),
|
|
319
|
+
__metadata("design:type", Object)
|
|
320
|
+
], FigureV3Canvas.prototype, "selected", void 0);
|
|
321
|
+
__decorate([
|
|
322
|
+
property({ attribute: false }),
|
|
323
|
+
__metadata("design:type", Object)
|
|
324
|
+
], FigureV3Canvas.prototype, "reach", void 0);
|
|
325
|
+
__decorate([
|
|
326
|
+
property(),
|
|
327
|
+
__metadata("design:type", String)
|
|
328
|
+
], FigureV3Canvas.prototype, "mode", void 0);
|
|
153
329
|
FigureV3Canvas = __decorate([
|
|
154
330
|
customElement('figure-v3-canvas')
|
|
155
331
|
], FigureV3Canvas);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v3-canvas.js","sourceRoot":"","sources":["../../client/modeller/v3-canvas.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, type PropertyValues } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport * as THREE from 'three'\nimport { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'\nimport { replaceV3Meshes, disposeV3Meshes, type V3RenderPart } from './v3-renderer.js'\n\n/** Renders evaluated V3 geometry directly. There is no parts/base or things-scene conversion. */\n@customElement('figure-v3-canvas')\nexport class FigureV3Canvas extends LitElement {\n static styles = css`\n :host {\n display: block;\n min-width: 0;\n min-height: 0;\n position: relative;\n background: #eef2f4;\n }\n canvas {\n display: block;\n width: 100%;\n height: 100%;\n }\n p {\n position: absolute;\n inset: 45% 1rem auto;\n text-align: center;\n pointer-events: none;\n color: #526773;\n }\n `\n @property({ attribute: false }) parts: V3RenderPart[] = []\n @property({ attribute: false }) palette: Record<string, string> = {}\n @property() context = ''\n private renderer?: THREE.WebGLRenderer\n private readonly scene = new THREE.Scene()\n private readonly group = new THREE.Group()\n private readonly camera = new THREE.PerspectiveCamera(40, 1, 0.1, 1000000)\n private controls?: OrbitControls\n private observer?: ResizeObserver\n private initialized = false\n private contextAtRender = ''\n render() {\n return html`<canvas aria-label=\"Figure 3D 모델\" @pointerup=${this.pick}></canvas\n >${this.parts.length ? null : html`<p>도형을 추가해 모델링을 시작하세요.</p>`}`\n }\n protected firstUpdated() {\n this.initialize()\n }\n connectedCallback() {\n super.connectedCallback()\n if (this.hasUpdated) void this.updateComplete.then(() => this.initialize())\n }\n private initialize() {\n if (this.initialized || !this.isConnected) return\n const canvas = this.renderRoot.querySelector('canvas')!\n this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true, preserveDrawingBuffer: true })\n this.renderer.setPixelRatio(Math.min(devicePixelRatio, 2))\n this.scene.background = new THREE.Color('#eef2f4')\n this.scene.add(this.group, new THREE.HemisphereLight(0xffffff, 0x62717d, 2.5))\n const light = new THREE.DirectionalLight(0xffffff, 3)\n light.position.set(1, 2, 3)\n this.scene.add(light)\n this.controls = new OrbitControls(this.camera, canvas)\n this.controls.addEventListener('change', this.draw)\n this.observer = new ResizeObserver(() => {\n const r = this.getBoundingClientRect()\n if (r.width && r.height) {\n this.renderer!.setSize(r.width, r.height, false)\n this.camera.aspect = r.width / r.height\n this.camera.updateProjectionMatrix()\n this.draw()\n }\n })\n this.observer.observe(this)\n this.initialized = true\n this.updateGeometry(true)\n }\n protected updated(changed: PropertyValues) {\n if (this.initialized && (changed.has('parts') || changed.has('palette') || changed.has('context')))\n this.updateGeometry(this.contextAtRender !== this.context)\n }\n private updateGeometry(fit: boolean) {\n try {\n const wasEmpty = this.group.children.length === 0\n replaceV3Meshes(this.group, this.parts, this.palette)\n this.contextAtRender = this.context\n if (fit || wasEmpty) this.fit()\n this.draw()\n } catch (error) {\n // Do not leave another model's geometry visible after a failed context switch.\n if (this.contextAtRender !== this.context) {\n disposeV3Meshes(this.group)\n this.draw()\n }\n this.dispatchEvent(new CustomEvent('render-error', { detail: error, bubbles: true, composed: true }))\n }\n }\n fit() {\n const bounds = new THREE.Box3().setFromObject(this.group),\n center = new THREE.Vector3(),\n size = new THREE.Vector3()\n if (!bounds.isEmpty()) {\n bounds.getCenter(center)\n bounds.getSize(size)\n }\n const radius = Math.max(size.length() / 2, 100),\n distance = (radius / Math.sin(THREE.MathUtils.degToRad(this.camera.fov / 2))) * 1.3\n this.camera.position.copy(center).add(new THREE.Vector3(1, 0.8, 1).normalize().multiplyScalar(distance))\n this.camera.near = Math.max(0.01, distance / 10000)\n this.camera.far = Math.max(10000, distance * 100)\n this.camera.updateProjectionMatrix()\n this.controls?.target.copy(center)\n this.controls?.update()\n this.draw()\n }\n private draw = () => {\n if (this.renderer) this.renderer.render(this.scene, this.camera)\n }\n private pick = (event: PointerEvent) => {\n const r = (event.currentTarget as HTMLCanvasElement).getBoundingClientRect(),\n ray = new THREE.Raycaster()\n ray.setFromCamera(\n new THREE.Vector2(((event.clientX - r.left) / r.width) * 2 - 1, (-(event.clientY - r.top) / r.height) * 2 + 1),\n this.camera\n )\n const hit = ray.intersectObjects(this.group.children)[0]\n this.dispatchEvent(\n new CustomEvent('part-selected', { detail: hit?.object.name ?? '', bubbles: true, composed: true })\n )\n }\n thumbnail() {\n this.draw()\n return this.renderer?.domElement.toDataURL('image/png')\n }\n disconnectedCallback() {\n this.observer?.disconnect()\n this.controls?.dispose()\n disposeV3Meshes(this.group)\n this.renderer?.dispose()\n this.renderer = undefined\n this.scene.clear()\n this.initialized = false\n super.disconnectedCallback()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"v3-canvas.js","sourceRoot":"","sources":["../../client/modeller/v3-canvas.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, html, css, type PropertyValues } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { i18next } from '@operato/i18n'\nimport * as THREE from 'three'\nimport { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'\nimport { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'\nimport type { GizmoReach, GizmoResult } from './v3-gizmo.js'\nimport { replaceV3Meshes, disposeV3Meshes, type V3RenderPart } from './v3-renderer.js'\n\n/** Renders evaluated V3 geometry directly. There is no parts/base or things-scene conversion. */\n@customElement('figure-v3-canvas')\nexport class FigureV3Canvas extends LitElement {\n static styles = css`\n :host {\n display: block;\n min-width: 0;\n min-height: 0;\n position: relative;\n /*\n The working board is one step above the surface in both themes (UX, 2026-09-24): surface-container is 238\n in light and 31 in dark. The 3D background is read from this same value (paintBackground), so the\n first paint and the scene agree and there is no flash between them.\n */\n background: var(--md-sys-color-surface-container);\n }\n canvas {\n display: block;\n width: 100%;\n height: 100%;\n }\n div[gizmo] {\n position: absolute;\n top: 8px;\n left: 8px;\n display: flex;\n gap: 2px;\n padding: 2px;\n border-radius: 6px;\n background: var(--md-sys-color-surface);\n border: 1px solid var(--md-sys-color-outline-variant);\n }\n div[gizmo] button {\n font: inherit;\n font-size: 12px;\n padding: 3px 8px;\n border: none;\n border-radius: 4px;\n background: none;\n color: var(--md-sys-color-on-surface);\n cursor: pointer;\n }\n div[gizmo] button[on] {\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n }\n div[gizmo] button:disabled {\n opacity: 0.4;\n cursor: default;\n }\n p {\n position: absolute;\n inset: 45% 1rem auto;\n text-align: center;\n pointer-events: none;\n color: var(--md-sys-color-on-surface-variant);\n }\n `\n @property({ attribute: false }) parts: V3RenderPart[] = []\n @property({ attribute: false }) palette: Record<string, string> = {}\n @property() context = ''\n /** The selected part, and what the gizmo may write for it (`gizmoReachOf`). Nothing reachable, no gizmo. */\n @property() selected = ''\n @property({ attribute: false }) reach: GizmoReach = { translate: false, rotate: false, scale: false }\n /** Move · turn · size — W · E · R, as in the V2 modeller. */\n @property() mode: 'translate' | 'rotate' | 'scale' = 'translate'\n private transform?: TransformControls\n /** What the gizmo holds: a stand-in at the body's origin, so the body's own matrix stays the evaluation's. */\n private readonly proxy = new THREE.Object3D()\n private dragStart?: { proxy: THREE.Matrix4; mesh: THREE.Matrix4; body: THREE.Mesh }\n /** The pointerup that ends a drag is not a pick — releasing off the body would otherwise deselect it. */\n private justDragged = false\n private renderer?: THREE.WebGLRenderer\n private readonly scene = new THREE.Scene()\n private readonly group = new THREE.Group()\n private readonly camera = new THREE.PerspectiveCamera(40, 1, 0.1, 1000000)\n private controls?: OrbitControls\n private observer?: ResizeObserver\n private themeObserver?: MutationObserver\n private initialized = false\n private contextAtRender = ''\n render() {\n const any = this.selected && (this.reach.translate || this.reach.rotate || this.reach.scale)\n return html`<canvas aria-label=\"Figure 3D 모델\" tabindex=\"0\" @pointerup=${this.pick} @keydown=${this.key}></canvas\n >${any\n ? html`<div gizmo>\n ${(['translate', 'rotate', 'scale'] as const).map(\n (mode, i) => html`<button\n ?on=${this.mode === mode}\n ?disabled=${!this.reach[mode]}\n title=${`${i18next.t('figure.label.gizmo-' + mode)} (${'WER'[i]})`}\n @click=${() => (this.mode = mode)}\n >\n ${i18next.t('figure.label.gizmo-' + mode)}\n </button>`\n )}\n </div>`\n : null}${this.parts.length ? null : html`<p>도형을 추가해 모델링을 시작하세요.</p>`}`\n }\n private key = (event: KeyboardEvent) => {\n const mode = ({ w: 'translate', e: 'rotate', r: 'scale' } as const)[event.key.toLowerCase() as 'w' | 'e' | 'r']\n if (mode && this.reach[mode]) this.mode = mode\n }\n protected firstUpdated() {\n this.initialize()\n }\n connectedCallback() {\n super.connectedCallback()\n if (this.hasUpdated) void this.updateComplete.then(() => this.initialize())\n }\n private initialize() {\n if (this.initialized || !this.isConnected) return\n const canvas = this.renderRoot.querySelector('canvas')!\n this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true, preserveDrawingBuffer: true })\n this.renderer.setPixelRatio(Math.min(devicePixelRatio, 2))\n this.paintBackground()\n /*\n The app switches theme by the body's class (light · dark). Reading the colour once at start left a light\n board in the middle of a dark screen after every switch (seen 2026-09-24), so it is read again then.\n */\n this.themeObserver = new MutationObserver(() => {\n this.paintBackground()\n this.draw()\n })\n this.themeObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] })\n this.scene.add(this.group, new THREE.HemisphereLight(0xffffff, 0x62717d, 2.5))\n const light = new THREE.DirectionalLight(0xffffff, 3)\n light.position.set(1, 2, 3)\n this.scene.add(light)\n this.controls = new OrbitControls(this.camera, canvas)\n this.controls.addEventListener('change', this.draw)\n this.transform = new TransformControls(this.camera, canvas)\n this.transform.setSpace('world')\n this.transform.addEventListener('change', this.draw)\n this.transform.addEventListener('dragging-changed', this.dragging)\n this.transform.addEventListener('objectChange', this.follow)\n this.scene.add(this.proxy, this.transform.getHelper())\n this.observer = new ResizeObserver(() => {\n const r = this.getBoundingClientRect()\n if (r.width && r.height) {\n this.renderer!.setSize(r.width, r.height, false)\n this.camera.aspect = r.width / r.height\n this.camera.updateProjectionMatrix()\n this.draw()\n }\n })\n this.observer.observe(this)\n this.initialized = true\n this.updateGeometry(true)\n }\n protected updated(changed: PropertyValues) {\n if (this.initialized && (changed.has('parts') || changed.has('palette') || changed.has('context')))\n this.updateGeometry(this.contextAtRender !== this.context)\n if (this.initialized && ['parts', 'selected', 'reach', 'mode', 'context'].some(key => changed.has(key))) this.placeGizmo()\n }\n /** Puts the gizmo on the selected body, in a mode it may write — or takes it away. */\n private placeGizmo() {\n const transform = this.transform\n if (!transform || this.dragStart) return\n if (!this.reach[this.mode]) {\n const first = (['translate', 'rotate', 'scale'] as const).find(mode => this.reach[mode])\n if (first && first !== this.mode) {\n this.mode = first\n return\n }\n }\n const body = this.group.children.find(child => child.name === this.selected) as THREE.Mesh | undefined\n if (!body || !this.reach[this.mode]) {\n transform.detach()\n this.draw()\n return\n }\n const position = new THREE.Vector3(),\n quaternion = new THREE.Quaternion(),\n scale = new THREE.Vector3()\n body.matrix.decompose(position, quaternion, scale)\n this.proxy.position.copy(position)\n this.proxy.quaternion.copy(quaternion)\n this.proxy.scale.set(1, 1, 1)\n this.proxy.updateMatrixWorld(true)\n transform.setMode(this.mode)\n /* Size follows the body's own axes; moving and turning read in the asset's axes, which the inputs are in. */\n transform.setSpace(this.mode === 'scale' ? 'local' : 'world')\n transform.attach(this.proxy)\n this.draw()\n }\n private dragging = (event: { value: unknown }) => {\n const on = !!event.value\n if (this.controls) this.controls.enabled = !on\n const body = this.group.children.find(child => child.name === this.selected) as THREE.Mesh | undefined\n if (on && body) {\n this.proxy.updateMatrix()\n this.dragStart = { proxy: this.proxy.matrix.clone(), mesh: body.matrix.clone(), body }\n return\n }\n const start = this.dragStart\n this.dragStart = undefined\n if (!start) return\n /* Either listener may see the release first; the flag lasts past both, then clears. */\n this.justDragged = true\n setTimeout(() => (this.justDragged = false))\n const result: GizmoResult = {}\n if (this.mode === 'translate') result.position = this.proxy.position.toArray() as [number, number, number]\n if (this.mode === 'rotate') {\n /* rigid@1 composes Rx·Ry·Rz — three's 'XYZ' order — so the angles come back as the inputs mean them. */\n const euler = new THREE.Euler().setFromQuaternion(this.proxy.quaternion, 'XYZ')\n result.rotation = [euler.x, euler.y, euler.z].map(THREE.MathUtils.radToDeg) as [number, number, number]\n }\n if (this.mode === 'scale') result.scale = this.proxy.scale.toArray() as [number, number, number]\n this.dispatchEvent(new CustomEvent('part-transformed', { detail: { id: this.selected, result }, bubbles: true, composed: true }))\n }\n /** While dragging, the body follows the stand-in; the next evaluation replaces it with where the inputs put it. */\n private follow = () => {\n const start = this.dragStart\n if (!start) return\n this.proxy.updateMatrix()\n const delta = this.proxy.matrix.clone().multiply(start.proxy.clone().invert())\n start.body.matrix.copy(delta.multiply(start.mesh))\n start.body.matrixWorldNeedsUpdate = true\n this.group.updateMatrixWorld(true)\n }\n private updateGeometry(fit: boolean) {\n try {\n const wasEmpty = this.group.children.length === 0\n replaceV3Meshes(this.group, this.parts, this.palette)\n this.contextAtRender = this.context\n if (fit || wasEmpty) this.fit()\n this.draw()\n } catch (error) {\n // Do not leave another model's geometry visible after a failed context switch.\n if (this.contextAtRender !== this.context) {\n disposeV3Meshes(this.group)\n this.draw()\n }\n this.dispatchEvent(new CustomEvent('render-error', { detail: error, bubbles: true, composed: true }))\n }\n }\n fit() {\n const bounds = new THREE.Box3().setFromObject(this.group),\n center = new THREE.Vector3(),\n size = new THREE.Vector3()\n if (!bounds.isEmpty()) {\n bounds.getCenter(center)\n bounds.getSize(size)\n }\n const radius = Math.max(size.length() / 2, 100),\n distance = (radius / Math.sin(THREE.MathUtils.degToRad(this.camera.fov / 2))) * 1.3\n this.camera.position.copy(center).add(new THREE.Vector3(1, 0.8, 1).normalize().multiplyScalar(distance))\n this.camera.near = Math.max(0.01, distance / 10000)\n this.camera.far = Math.max(10000, distance * 100)\n this.camera.updateProjectionMatrix()\n this.controls?.target.copy(center)\n this.controls?.update()\n this.draw()\n }\n /**\n * The scene's background, taken from the host's computed background — the token resolved and normalised to\n * `rgb(r, g, b)`, which THREE.Color reads. The token's own text may use the space-separated form it does not.\n */\n private paintBackground() {\n const colour = getComputedStyle(this).backgroundColor\n if (colour && colour !== 'rgba(0, 0, 0, 0)' && colour !== 'transparent') this.scene.background = new THREE.Color(colour)\n }\n private draw = () => {\n if (this.renderer) this.renderer.render(this.scene, this.camera)\n }\n private pick = (event: PointerEvent) => {\n if (this.justDragged || this.transform?.dragging) return\n const r = (event.currentTarget as HTMLCanvasElement).getBoundingClientRect(),\n ray = new THREE.Raycaster()\n ray.setFromCamera(\n new THREE.Vector2(((event.clientX - r.left) / r.width) * 2 - 1, (-(event.clientY - r.top) / r.height) * 2 + 1),\n this.camera\n )\n const hit = ray.intersectObjects(this.group.children)[0]\n this.dispatchEvent(\n new CustomEvent('part-selected', { detail: hit?.object.name ?? '', bubbles: true, composed: true })\n )\n }\n thumbnail() {\n this.draw()\n return this.renderer?.domElement.toDataURL('image/png')\n }\n disconnectedCallback() {\n this.observer?.disconnect()\n this.themeObserver?.disconnect()\n this.controls?.dispose()\n this.transform?.detach()\n this.transform?.dispose()\n this.transform = undefined\n disposeV3Meshes(this.group)\n this.renderer?.dispose()\n this.renderer = undefined\n this.scene.clear()\n this.initialized = false\n super.disconnectedCallback()\n }\n}\n"]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { LitElement, type PropertyValues } from 'lit';
|
|
2
2
|
import type { V3ProductSession } from './v3-product-session.js';
|
|
3
3
|
import './v3-canvas.js';
|
|
4
|
+
import './figure-v3-parts.js';
|
|
5
|
+
import './figure-v3-material.js';
|
|
4
6
|
/** Product editor surface. The injected session is shared by page persistence and AI commands. */
|
|
5
7
|
export declare class FigureV3Editor extends LitElement {
|
|
6
8
|
static styles: import("lit").CSSResult;
|
|
@@ -8,12 +10,34 @@ export declare class FigureV3Editor extends LitElement {
|
|
|
8
10
|
revision: number;
|
|
9
11
|
private selected;
|
|
10
12
|
private error;
|
|
13
|
+
/**
|
|
14
|
+
* Parts hidden on the workbench — the author's view while working, not the asset's visibility. Not saved, as in
|
|
15
|
+
* the V2 modeller: a part hidden here is still in the figure, still checked, still drawn on a board.
|
|
16
|
+
*/
|
|
17
|
+
private hiddenParts;
|
|
11
18
|
private lastContext;
|
|
12
19
|
protected willUpdate(_changed: PropertyValues): void;
|
|
13
20
|
private run;
|
|
14
21
|
private changed;
|
|
15
22
|
private save;
|
|
16
23
|
private add;
|
|
24
|
+
/**
|
|
25
|
+
* The list's own operations, as V3 part edits — each one step of the session's history. The copy and the moved
|
|
26
|
+
* part stay selected, as they do in the V2 list.
|
|
27
|
+
*/
|
|
28
|
+
private duplicatePart;
|
|
29
|
+
private movePart;
|
|
30
|
+
private removePart;
|
|
31
|
+
private toggleHiddenPart;
|
|
32
|
+
/** What the gizmo may write for the selected part — read off the asset being edited. */
|
|
33
|
+
private reachOf;
|
|
34
|
+
/** A drag, written back as the part's own inputs — one step of the history, like typing the numbers. */
|
|
35
|
+
private transformed;
|
|
36
|
+
/** 맞추기 — one tool, one step of the history, through the same inputs as the gizmo. */
|
|
37
|
+
private align;
|
|
38
|
+
/** A mirrored copy: duplicate, then place the copy on the other side — two steps, as the V2 list makes it. */
|
|
39
|
+
private mirror;
|
|
40
|
+
private renderTools;
|
|
17
41
|
private changeField;
|
|
18
42
|
render(): import("lit-html").TemplateResult<1>;
|
|
19
43
|
}
|