@things-factory/figure-ui 10.1.40 → 10.1.43
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/modeller-route.ts +25 -0
- package/client/modeller/v3-align.ts +84 -0
- package/client/modeller/v3-canvas.ts +140 -2
- package/client/modeller/v3-editor.ts +213 -39
- package/client/modeller/v3-gizmo.ts +80 -0
- package/client/pages/figure-list-page.ts +5 -3
- package/client/pages/figure-modeller-page.ts +9 -0
- 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/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 +18 -0
- package/dist-client/modeller/v3-canvas.js +154 -2
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-editor.d.ts +29 -0
- package/dist-client/modeller/v3-editor.js +225 -36
- package/dist-client/modeller/v3-editor.js.map +1 -1
- 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/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/route.d.ts +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 +66 -0
- package/translations/en.json +7 -0
- package/translations/ja.json +7 -0
- package/translations/ko.json +7 -0
- package/translations/ms.json +7 -0
- package/translations/zh.json +7 -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];
|
|
@@ -45,6 +103,35 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
45
103
|
width: 100%;
|
|
46
104
|
height: 100%;
|
|
47
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
|
+
}
|
|
48
135
|
p {
|
|
49
136
|
position: absolute;
|
|
50
137
|
inset: 45% 1rem auto;
|
|
@@ -54,8 +141,20 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
54
141
|
}
|
|
55
142
|
`; }
|
|
56
143
|
render() {
|
|
57
|
-
|
|
58
|
-
|
|
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>`}`;
|
|
59
158
|
}
|
|
60
159
|
firstUpdated() {
|
|
61
160
|
this.initialize();
|
|
@@ -87,6 +186,12 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
87
186
|
this.scene.add(light);
|
|
88
187
|
this.controls = new OrbitControls(this.camera, canvas);
|
|
89
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());
|
|
90
195
|
this.observer = new ResizeObserver(() => {
|
|
91
196
|
const r = this.getBoundingClientRect();
|
|
92
197
|
if (r.width && r.height) {
|
|
@@ -103,6 +208,38 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
103
208
|
updated(changed) {
|
|
104
209
|
if (this.initialized && (changed.has('parts') || changed.has('palette') || changed.has('context')))
|
|
105
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();
|
|
106
243
|
}
|
|
107
244
|
updateGeometry(fit) {
|
|
108
245
|
try {
|
|
@@ -154,6 +291,9 @@ let FigureV3Canvas = class FigureV3Canvas extends LitElement {
|
|
|
154
291
|
this.observer?.disconnect();
|
|
155
292
|
this.themeObserver?.disconnect();
|
|
156
293
|
this.controls?.dispose();
|
|
294
|
+
this.transform?.detach();
|
|
295
|
+
this.transform?.dispose();
|
|
296
|
+
this.transform = undefined;
|
|
157
297
|
disposeV3Meshes(this.group);
|
|
158
298
|
this.renderer?.dispose();
|
|
159
299
|
this.renderer = undefined;
|
|
@@ -174,6 +314,18 @@ __decorate([
|
|
|
174
314
|
property(),
|
|
175
315
|
__metadata("design:type", Object)
|
|
176
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);
|
|
177
329
|
FigureV3Canvas = __decorate([
|
|
178
330
|
customElement('figure-v3-canvas')
|
|
179
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 /*\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 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 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 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.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.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 /**\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 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 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,39 @@ 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;
|
|
41
|
+
/**
|
|
42
|
+
* A part's name — the V2 part name field. It renames every value the part owns, and what refers to them follows
|
|
43
|
+
* (figure-model's rename-part), as one step of the history.
|
|
44
|
+
*/
|
|
45
|
+
private renamePart;
|
|
17
46
|
private changeField;
|
|
18
47
|
render(): import("lit-html").TemplateResult<1>;
|
|
19
48
|
}
|