@things-factory/figure-ui 10.1.46 → 10.1.48
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/angle-dial.ts +70 -0
- package/client/modeller/figure-settings.ts +328 -0
- package/client/modeller/figure-v3-capability.ts +137 -0
- package/client/modeller/figure-v3-clips.ts +253 -0
- package/client/modeller/figure-v3-controls.ts +212 -0
- package/client/modeller/figure-v3-links.ts +61 -81
- package/client/modeller/figure-v3-material.ts +108 -35
- package/client/modeller/figure-v3-parts.ts +96 -3
- package/client/modeller/figure-v3-preview.ts +326 -0
- package/client/modeller/figure-v3-properties.ts +29 -57
- package/client/modeller/figure-v3-verdict.ts +598 -60
- package/client/modeller/figure-view.ts +152 -0
- package/client/modeller/side-panel-styles.ts +326 -0
- package/client/modeller/v3-canvas.ts +254 -262
- package/client/modeller/v3-clips.ts +89 -0
- package/client/modeller/v3-controls.ts +54 -0
- package/client/modeller/v3-editor.ts +566 -223
- package/client/modeller/v3-fields.ts +84 -0
- package/client/modeller/v3-help.ts +41 -0
- package/client/modeller/v3-joint-fan.ts +292 -0
- package/client/modeller/v3-keys.ts +34 -0
- package/client/modeller/v3-links.ts +101 -9
- package/client/modeller/v3-live.ts +79 -0
- package/client/modeller/v3-maturity.ts +415 -0
- package/client/modeller/v3-palette.ts +45 -0
- package/client/modeller/v3-preview-model.ts +54 -0
- package/client/modeller/v3-stage-model.ts +159 -0
- package/client/modeller/v3-thumbnail.ts +8 -4
- package/client/pages/figure-list-page.ts +4 -4
- package/client/pages/figure-v3-modeller-page.ts +66 -134
- package/dist-client/modeller/angle-dial.d.ts +32 -0
- package/dist-client/modeller/angle-dial.js +41 -0
- package/dist-client/modeller/angle-dial.js.map +1 -0
- package/dist-client/modeller/figure-settings.d.ts +39 -0
- package/dist-client/modeller/figure-settings.js +317 -0
- package/dist-client/modeller/figure-settings.js.map +1 -0
- package/dist-client/modeller/figure-v3-capability.d.ts +22 -0
- package/dist-client/modeller/figure-v3-capability.js +141 -0
- package/dist-client/modeller/figure-v3-capability.js.map +1 -0
- package/dist-client/modeller/figure-v3-clips.d.ts +31 -0
- package/dist-client/modeller/figure-v3-clips.js +268 -0
- package/dist-client/modeller/figure-v3-clips.js.map +1 -0
- package/dist-client/modeller/figure-v3-controls.d.ts +36 -0
- package/dist-client/modeller/figure-v3-controls.js +226 -0
- package/dist-client/modeller/figure-v3-controls.js.map +1 -0
- package/dist-client/modeller/figure-v3-links.d.ts +4 -1
- package/dist-client/modeller/figure-v3-links.js +64 -82
- package/dist-client/modeller/figure-v3-links.js.map +1 -1
- package/dist-client/modeller/figure-v3-material.d.ts +4 -1
- package/dist-client/modeller/figure-v3-material.js +109 -35
- package/dist-client/modeller/figure-v3-material.js.map +1 -1
- package/dist-client/modeller/figure-v3-parts.d.ts +10 -0
- package/dist-client/modeller/figure-v3-parts.js +88 -3
- package/dist-client/modeller/figure-v3-parts.js.map +1 -1
- package/dist-client/modeller/figure-v3-preview.d.ts +34 -0
- package/dist-client/modeller/figure-v3-preview.js +322 -0
- package/dist-client/modeller/figure-v3-preview.js.map +1 -0
- package/dist-client/modeller/figure-v3-properties.d.ts +1 -1
- package/dist-client/modeller/figure-v3-properties.js +29 -57
- package/dist-client/modeller/figure-v3-properties.js.map +1 -1
- package/dist-client/modeller/figure-v3-verdict.d.ts +31 -17
- package/dist-client/modeller/figure-v3-verdict.js +579 -59
- package/dist-client/modeller/figure-v3-verdict.js.map +1 -1
- package/dist-client/modeller/figure-view.d.ts +118 -0
- package/dist-client/modeller/figure-view.js +78 -0
- package/dist-client/modeller/figure-view.js.map +1 -0
- package/dist-client/modeller/side-panel-styles.d.ts +23 -0
- package/dist-client/modeller/side-panel-styles.js +326 -0
- package/dist-client/modeller/side-panel-styles.js.map +1 -0
- package/dist-client/modeller/v3-canvas.d.ts +44 -37
- package/dist-client/modeller/v3-canvas.js +230 -260
- package/dist-client/modeller/v3-canvas.js.map +1 -1
- package/dist-client/modeller/v3-clips.d.ts +39 -0
- package/dist-client/modeller/v3-clips.js +58 -0
- package/dist-client/modeller/v3-clips.js.map +1 -0
- package/dist-client/modeller/v3-controls.d.ts +28 -0
- package/dist-client/modeller/v3-controls.js +28 -0
- package/dist-client/modeller/v3-controls.js.map +1 -0
- package/dist-client/modeller/v3-editor.d.ts +73 -1
- package/dist-client/modeller/v3-editor.js +593 -218
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-fields.d.ts +39 -0
- package/dist-client/modeller/v3-fields.js +69 -0
- package/dist-client/modeller/v3-fields.js.map +1 -0
- package/dist-client/modeller/v3-help.d.ts +18 -0
- package/dist-client/modeller/v3-help.js +37 -0
- package/dist-client/modeller/v3-help.js.map +1 -0
- package/dist-client/modeller/v3-joint-fan.d.ts +9 -0
- package/dist-client/modeller/v3-joint-fan.js +264 -0
- package/dist-client/modeller/v3-joint-fan.js.map +1 -0
- package/dist-client/modeller/v3-keys.d.ts +14 -0
- package/dist-client/modeller/v3-keys.js +23 -0
- package/dist-client/modeller/v3-keys.js.map +1 -0
- package/dist-client/modeller/v3-links.d.ts +35 -1
- package/dist-client/modeller/v3-links.js +101 -8
- package/dist-client/modeller/v3-links.js.map +1 -1
- package/dist-client/modeller/v3-live.d.ts +31 -0
- package/dist-client/modeller/v3-live.js +58 -0
- package/dist-client/modeller/v3-live.js.map +1 -0
- package/dist-client/modeller/v3-maturity.d.ts +120 -0
- package/dist-client/modeller/v3-maturity.js +317 -0
- package/dist-client/modeller/v3-maturity.js.map +1 -0
- package/dist-client/modeller/v3-palette.d.ts +7 -0
- package/dist-client/modeller/v3-palette.js +42 -0
- package/dist-client/modeller/v3-palette.js.map +1 -0
- package/dist-client/modeller/v3-preview-model.d.ts +32 -0
- package/dist-client/modeller/v3-preview-model.js +48 -0
- package/dist-client/modeller/v3-preview-model.js.map +1 -0
- package/dist-client/modeller/v3-stage-model.d.ts +31 -0
- package/dist-client/modeller/v3-stage-model.js +121 -0
- package/dist-client/modeller/v3-stage-model.js.map +1 -0
- package/dist-client/modeller/v3-thumbnail.js +6 -3
- package/dist-client/modeller/v3-thumbnail.js.map +1 -1
- package/dist-client/pages/figure-list-page.js +4 -4
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -6
- package/dist-client/pages/figure-v3-modeller-page.js +64 -136
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.chk.tsbuildinfo +1 -0
- package/dist-client/tsconfig.scratch-fm.tsbuildinfo +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/test/v3-clips.test.ts +70 -0
- package/test/v3-controls.test.ts +68 -0
- package/test/v3-fields.test.ts +71 -0
- package/test/v3-gizmo.test.ts +3 -5
- package/test/v3-joint-fan.test.ts +210 -0
- package/test/v3-keys.test.ts +37 -0
- package/test/v3-layout.test.ts +78 -0
- package/test/v3-links.test.ts +89 -6
- package/test/v3-live.test.ts +99 -0
- package/test/v3-material.test.ts +6 -0
- package/test/v3-maturity.test.ts +268 -0
- package/test/v3-palette-icons.test.ts +22 -0
- package/test/v3-parts-wiring.test.ts +3 -1
- package/test/v3-preview.test.ts +42 -0
- package/test/v3-properties.test.ts +4 -2
- package/test/v3-scene-canvas.test.ts +85 -0
- package/test/v3-stage-model.test.ts +25 -0
- package/test/v3-theme.test.ts +16 -25
- package/test/v3-thumbnail.test.ts +29 -0
- package/test/v3-verdict.test.ts +12 -6
- package/test/v3-view.test.ts +20 -0
- package/translations/en.json +90 -6
- package/translations/ja.json +92 -8
- package/translations/ko.json +90 -6
- package/translations/ms.json +92 -8
- package/translations/zh.json +92 -8
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as THREE from 'three'
|
|
2
|
+
import { buildV3Geometry } from '@hatiolab/things-scene'
|
|
3
|
+
|
|
4
|
+
import type { GizmoResult } from './v3-gizmo.js'
|
|
5
|
+
import type { V3RenderPart } from './v3-renderer.js'
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* V3 bodies <-> scene components, in one place (V2's `figure-source.ts` was that place for V2 parts).
|
|
9
|
+
*
|
|
10
|
+
* The modeller stands on a things-scene board in editing mode, as V2's did: each body figure-model evaluates is a
|
|
11
|
+
* `figure-v3-part` component, and what the scene's gizmo writes back comes here to become a change of the part's
|
|
12
|
+
* inputs. The two vocabularies:
|
|
13
|
+
*
|
|
14
|
+
* V3 (asset frame, mm, y up) scene component
|
|
15
|
+
* ──────────────────────────────── ─────────────────────────────────────────────
|
|
16
|
+
* body box extent x · y · z width · depth · height
|
|
17
|
+
* body box centre x left + width/2 - board width/2
|
|
18
|
+
* y zPos + depth/2
|
|
19
|
+
* z top + height/2 - board height/2
|
|
20
|
+
* body rotation (rigid@1, Rx·Ry·Rz) rotationX · -rotation · rotationY (radians; three 'XYZ')
|
|
21
|
+
*
|
|
22
|
+
* The box is the body's own bounding box in its local frame, so a turned body keeps its size and the gizmo's handles
|
|
23
|
+
* sit on its faces. Its centre is not always the body's origin (a portal rises from its bottom), so a turn about the
|
|
24
|
+
* centre moves the origin: the result gives the origin where the turned body puts it.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export interface Board {
|
|
28
|
+
width: number
|
|
29
|
+
height: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface BodyFrame {
|
|
33
|
+
/** The body's box: its centre in the body's own frame, and its extent. */
|
|
34
|
+
centre: THREE.Vector3
|
|
35
|
+
extent: THREE.Vector3
|
|
36
|
+
rotation: THREE.Matrix4
|
|
37
|
+
origin: THREE.Vector3
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const round = (n: number) => Math.round(n * 1e6) / 1e6
|
|
41
|
+
|
|
42
|
+
export function frameOf(part: V3RenderPart): BodyFrame | null {
|
|
43
|
+
/* The box things-scene's `figure-v3-part` centres the body on: the same builder, measured the same way. */
|
|
44
|
+
let built: THREE.BufferGeometry | undefined
|
|
45
|
+
try {
|
|
46
|
+
built = buildV3Geometry(part.shape as never, part.segments) as THREE.BufferGeometry | undefined
|
|
47
|
+
} catch {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
if (!built?.getAttribute('position')) return null
|
|
51
|
+
built.computeBoundingBox()
|
|
52
|
+
const box = built.boundingBox!
|
|
53
|
+
const unit = { centre: box.getCenter(new THREE.Vector3()), extent: box.getSize(new THREE.Vector3()) }
|
|
54
|
+
built.dispose()
|
|
55
|
+
const r = part.pose.r
|
|
56
|
+
const rotation = new THREE.Matrix4().set(r[0]![0]!, r[0]![1]!, r[0]![2]!, 0, r[1]![0]!, r[1]![1]!, r[1]![2]!, 0, r[2]![0]!, r[2]![1]!, r[2]![2]!, 0, 0, 0, 0, 1)
|
|
57
|
+
return { centre: unit.centre, extent: unit.extent, rotation, origin: new THREE.Vector3(...(part.pose.t as [number, number, number])) }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The colours a body is drawn in, from the palette the modeller resolved (the asset's, over which the theme wins). */
|
|
61
|
+
function colours(part: V3RenderPart, palette: Record<string, string>) {
|
|
62
|
+
const m = part.material
|
|
63
|
+
const glow = m.emissive?.on ? { color: palette[m.emissive.token] ?? palette[m.token] ?? '#9a9a9a', intensity: m.emissive.intensity } : undefined
|
|
64
|
+
return { color: palette[m.token] ?? '#9a9a9a', preset: m.preset, transparent: !!m.transparent, ...(glow ? { glow } : {}) }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The component state that draws a body where figure-model put it. */
|
|
68
|
+
export function componentOf(part: V3RenderPart, palette: Record<string, string>, board: Board): Record<string, unknown> | null {
|
|
69
|
+
const frame = frameOf(part)
|
|
70
|
+
if (!frame) return null
|
|
71
|
+
const { centre, extent, rotation, origin } = frame
|
|
72
|
+
const world = centre.clone().applyMatrix4(rotation).add(origin)
|
|
73
|
+
const euler = new THREE.Euler().setFromRotationMatrix(rotation, 'XYZ')
|
|
74
|
+
const width = extent.x || 1,
|
|
75
|
+
depth = extent.y || 1,
|
|
76
|
+
height = extent.z || 1
|
|
77
|
+
return {
|
|
78
|
+
type: 'figure-v3-part',
|
|
79
|
+
name: part.id,
|
|
80
|
+
shape: part.shape,
|
|
81
|
+
...(part.segments !== undefined ? { segments: part.segments } : {}),
|
|
82
|
+
...colours(part, palette),
|
|
83
|
+
width: round(width),
|
|
84
|
+
depth: round(depth),
|
|
85
|
+
height: round(height),
|
|
86
|
+
left: round(world.x + board.width / 2 - width / 2),
|
|
87
|
+
top: round(world.z + board.height / 2 - height / 2),
|
|
88
|
+
zPos: round(world.y - depth / 2),
|
|
89
|
+
rotationX: round(euler.x),
|
|
90
|
+
rotation: round(-euler.y),
|
|
91
|
+
rotationY: round(euler.z)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const num = (v: unknown, fallback = 0) => (typeof v === 'number' && Number.isFinite(v) ? v : fallback)
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* What the gizmo did, in the inputs' terms: where the body's origin is now, how it is turned (degrees, rigid@1's
|
|
99
|
+
* order) and how much each of its own axes grew. Only what changed is given; a component the gizmo did not move gives
|
|
100
|
+
* nothing.
|
|
101
|
+
*/
|
|
102
|
+
export function gizmoResultOf(part: V3RenderPart, after: Record<string, unknown>, board: Board): GizmoResult {
|
|
103
|
+
const frame = frameOf(part)
|
|
104
|
+
if (!frame) return {}
|
|
105
|
+
const { centre, extent, rotation, origin } = frame
|
|
106
|
+
const width = num(after.width, extent.x),
|
|
107
|
+
depth = num(after.depth, extent.y),
|
|
108
|
+
height = num(after.height, extent.z)
|
|
109
|
+
const scale = new THREE.Vector3(width / (extent.x || 1), depth / (extent.y || 1), height / (extent.z || 1))
|
|
110
|
+
const world = new THREE.Vector3(num(after.left) + width / 2 - board.width / 2, num(after.zPos) + depth / 2, num(after.top) + height / 2 - board.height / 2)
|
|
111
|
+
const euler = new THREE.Euler(num(after.rotationX), -num(after.rotation), num(after.rotationY), 'XYZ')
|
|
112
|
+
const turnedTo = new THREE.Matrix4().makeRotationFromEuler(euler)
|
|
113
|
+
|
|
114
|
+
const before = new THREE.Quaternion().setFromRotationMatrix(rotation)
|
|
115
|
+
const now = new THREE.Quaternion().setFromEuler(euler)
|
|
116
|
+
const turned = before.angleTo(now) > 1e-4
|
|
117
|
+
const sized = [scale.x, scale.y, scale.z].some(s => Math.abs(s - 1) > 1e-4)
|
|
118
|
+
/* The origin that puts the (scaled, turned) box centre where the scene has it. */
|
|
119
|
+
const newOrigin = world.clone().sub(centre.clone().multiply(scale).applyMatrix4(turnedTo))
|
|
120
|
+
const moved = newOrigin.distanceTo(origin) > 0.5
|
|
121
|
+
|
|
122
|
+
const result: GizmoResult = {}
|
|
123
|
+
if (moved || turned) result.position = newOrigin.toArray() as [number, number, number]
|
|
124
|
+
if (turned) result.rotation = [euler.x, euler.y, euler.z].map(THREE.MathUtils.radToDeg) as [number, number, number]
|
|
125
|
+
if (sized) result.scale = scale.toArray() as [number, number, number]
|
|
126
|
+
return result
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The keys of `next` whose values differ from `current` -- only those are set, so the scene raises no echo. */
|
|
130
|
+
export function changedKeys(current: Record<string, unknown>, next: Record<string, unknown>): Record<string, unknown> | null {
|
|
131
|
+
const diff: Record<string, unknown> = {}
|
|
132
|
+
for (const [key, value] of Object.entries(next)) {
|
|
133
|
+
if (key === 'type') continue
|
|
134
|
+
const was = current[key]
|
|
135
|
+
const same = typeof value === 'object' && value !== null ? JSON.stringify(was) === JSON.stringify(value) : was === value
|
|
136
|
+
if (!same) diff[key] = value
|
|
137
|
+
}
|
|
138
|
+
return Object.keys(diff).length ? diff : null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** A board wide and deep enough for the figure, with room around it; its size only grows while a figure is edited. */
|
|
142
|
+
export function boardFor(parts: readonly V3RenderPart[], at: Board & { depth?: number } = { width: 0, height: 0 }): Board & { depth: number } {
|
|
143
|
+
const box = new THREE.Box3()
|
|
144
|
+
for (const part of parts) {
|
|
145
|
+
const frame = frameOf(part)
|
|
146
|
+
if (!frame) continue
|
|
147
|
+
const half = frame.extent.clone().multiplyScalar(0.5)
|
|
148
|
+
const corners = [-1, 1].flatMap(x => [-1, 1].flatMap(y => [-1, 1].map(z => new THREE.Vector3(x * half.x, y * half.y, z * half.z))))
|
|
149
|
+
for (const corner of corners) box.expandByPoint(corner.add(frame.centre).applyMatrix4(frame.rotation).add(frame.origin))
|
|
150
|
+
}
|
|
151
|
+
const size = box.isEmpty() ? new THREE.Vector3(1000, 1000, 1000) : box.getSize(new THREE.Vector3())
|
|
152
|
+
const reach = (axis: 'x' | 'z') => 2 * Math.max(Math.abs(box.isEmpty() ? 500 : box.min[axis]), Math.abs(box.isEmpty() ? 500 : box.max[axis]))
|
|
153
|
+
return {
|
|
154
|
+
width: Math.max(at.width, Math.ceil(reach('x') * 1.2), 200),
|
|
155
|
+
height: Math.max(at.height, Math.ceil(reach('z') * 1.2), 200),
|
|
156
|
+
/* Only grows, as the plan does: a raised arm made the board taller than it was, and a new board lost the camera. */
|
|
157
|
+
depth: Math.max(at.depth ?? 0, Math.ceil(size.y * 1.1), 200)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -11,8 +11,9 @@ import type { V3RenderPart } from './v3-renderer.js'
|
|
|
11
11
|
*
|
|
12
12
|
* 저작면의 캔버스는 저작자가 돌려 둔 각도와 확대로 서 있다. 그것을 찍으면 카드마다 프레임이
|
|
13
13
|
* 달라지고, 방금 확대해 보던 나사 하나가 그 자산의 얼굴이 된다. 목록은 견주는 자리이므로 그림은
|
|
14
|
-
* **정해진 크기와 정해진 카메라**로
|
|
15
|
-
*
|
|
14
|
+
* **정해진 크기와 정해진 카메라**로 찍는다 — 새로 세운 캔버스(`viewOnly`)가 스스로 `fit()` 한 자리에서
|
|
15
|
+
* `snapshot()` 으로 찍는다. 캔버스가 씬 위로 옮겨 가면서 찍는 길이 빠졌고, 그동안 저장은 그림 없이 갔다
|
|
16
|
+
* (사용자 2026-09-24).
|
|
16
17
|
*
|
|
17
18
|
* ## 왜 서버가 아니라 브라우저인가
|
|
18
19
|
*
|
|
@@ -37,7 +38,8 @@ interface Shooter extends HTMLElement {
|
|
|
37
38
|
parts?: V3RenderPart[]
|
|
38
39
|
palette?: Record<string, string>
|
|
39
40
|
context?: string
|
|
40
|
-
|
|
41
|
+
viewOnly?: boolean
|
|
42
|
+
snapshot?: (width?: number, height?: number) => Promise<string | undefined>
|
|
41
43
|
fit?: () => void
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -74,6 +76,7 @@ export async function captureV3Thumbnail(
|
|
|
74
76
|
shooter.parts = parts
|
|
75
77
|
shooter.palette = palette
|
|
76
78
|
shooter.context = 'thumbnail'
|
|
79
|
+
shooter.viewOnly = true
|
|
77
80
|
shooter.style.cssText = 'display: block; width: 100%; height: 100%;'
|
|
78
81
|
|
|
79
82
|
try {
|
|
@@ -82,7 +85,8 @@ export async function captureV3Thumbnail(
|
|
|
82
85
|
(async () => {
|
|
83
86
|
await settle()
|
|
84
87
|
shooter.fit?.()
|
|
85
|
-
|
|
88
|
+
await settle()
|
|
89
|
+
return shooter.snapshot ? await shooter.snapshot(WIDTH, HEIGHT) : undefined
|
|
86
90
|
})(),
|
|
87
91
|
PATIENCE
|
|
88
92
|
)
|
|
@@ -428,12 +428,12 @@ export class FigureListPage extends FigureListPageBase {
|
|
|
428
428
|
카드에 남긴 하나뿐인 숫자. **이름표를 달지 않으므로** 무엇인지는 손이 갔을 때 뜨는
|
|
429
429
|
말이 알려 준다.
|
|
430
430
|
|
|
431
|
-
색칠한 배지로 두지 않는다.
|
|
432
|
-
|
|
433
|
-
|
|
431
|
+
색칠한 배지로 두지 않는다. 옛 draw call 한도 3 으로 재던 때는 카탈로그 거의 모두가 0 점이라
|
|
432
|
+
붉은 덩어리가 카드마다 섰고 아무것도 가르지 못했다. V3 는 「한 화면 N 대」 목표에 견준
|
|
433
|
+
여유라(`v3ScoreOf`, 2026-09-24) 퍼지지만, 숫자는 조용히 적고 낮은 것에만 **점 하나**를 붙인다.
|
|
434
434
|
|
|
435
435
|
아직 재지 않은 것을 0 으로 보여 주지 않는다 — 0 은 「가볍다」는 뜻이 되어 버린다.
|
|
436
|
-
등급 문자(A~D)도 여기서 만들지 않는다: 경계는 `figure-model` 의 `scoreOf` 가 정하고,
|
|
436
|
+
등급 문자(A~D)도 여기서 만들지 않는다: 경계는 `figure-model` 의 `scoreOf` · `v3ScoreOf` 가 정하고,
|
|
437
437
|
화면이 그것을 베끼면 두 벌이 되어 목록과 저작면이 다른 등급을 말한다.
|
|
438
438
|
*/
|
|
439
439
|
renderer: (value: number | null) =>
|
|
@@ -12,6 +12,7 @@ import '@material/web/icon/icon.js'
|
|
|
12
12
|
import '../modeller/figure-history-panel.js'
|
|
13
13
|
import '../modeller/figure-v3-verdict.js'
|
|
14
14
|
import '../modeller/figure-v3-properties.js'
|
|
15
|
+
import type { V3BudgetLine } from '../modeller/v3-maturity.js'
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* The V3 product page has exactly one authoritative workspace for UI and AI input transactions.
|
|
@@ -39,31 +40,25 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
39
40
|
min-width: 0;
|
|
40
41
|
min-height: 0;
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
-
width: 320px;
|
|
44
|
-
flex: none;
|
|
45
|
-
min-height: 0;
|
|
46
|
-
overflow: auto;
|
|
47
|
-
display: flex;
|
|
48
|
-
flex-direction: column;
|
|
49
|
-
border-left: 1px solid var(--md-sys-color-outline-variant);
|
|
50
|
-
}
|
|
43
|
+
/* The first field of the properties tab, in the side panel's look (side-panel-styles): a label above the field. */
|
|
51
44
|
label[type-field] {
|
|
52
45
|
display: grid;
|
|
53
|
-
gap:
|
|
54
|
-
|
|
55
|
-
font-size:
|
|
56
|
-
|
|
46
|
+
gap: 2px;
|
|
47
|
+
margin: 0 0 5px;
|
|
48
|
+
font-size: 0.68rem;
|
|
49
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
57
50
|
}
|
|
58
51
|
label[type-field] input,
|
|
59
52
|
label[type-field] code {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
padding:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
background: var(--md-sys-color-surface);
|
|
53
|
+
box-sizing: border-box;
|
|
54
|
+
min-height: 30px;
|
|
55
|
+
padding: 4px 7px;
|
|
56
|
+
font-family: var(--mono-font, monospace);
|
|
57
|
+
font-size: 0.76rem;
|
|
66
58
|
color: var(--md-sys-color-on-surface);
|
|
59
|
+
background: var(--md-sys-color-surface-container-lowest);
|
|
60
|
+
border: 1px solid var(--md-sys-color-outline-variant);
|
|
61
|
+
border-radius: 6px;
|
|
67
62
|
}
|
|
68
63
|
label[type-field] small {
|
|
69
64
|
color: var(--md-sys-color-on-surface-variant);
|
|
@@ -77,57 +72,9 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
77
72
|
label[type-field] small[role='alert'] {
|
|
78
73
|
color: var(--md-sys-color-error);
|
|
79
74
|
}
|
|
80
|
-
/* The V2 side panel's tabs, drawn the same way: one icon per tab, the name in title and aria-label. */
|
|
81
|
-
div[tabs] {
|
|
82
|
-
display: flex;
|
|
83
|
-
flex: none;
|
|
84
|
-
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
85
|
-
}
|
|
86
|
-
button[tab] {
|
|
87
|
-
flex: 1;
|
|
88
|
-
position: relative;
|
|
89
|
-
display: flex;
|
|
90
|
-
align-items: center;
|
|
91
|
-
justify-content: center;
|
|
92
|
-
padding: 7px 6px;
|
|
93
|
-
color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
|
|
94
|
-
background: none;
|
|
95
|
-
border: none;
|
|
96
|
-
border-bottom: 2px solid transparent;
|
|
97
|
-
cursor: pointer;
|
|
98
|
-
}
|
|
99
|
-
button[tab]:hover:not([on]) {
|
|
100
|
-
background-color: var(--md-sys-color-surface-container);
|
|
101
|
-
}
|
|
102
|
-
button[tab][on] {
|
|
103
|
-
color: var(--md-sys-color-primary);
|
|
104
|
-
border-bottom-color: var(--md-sys-color-primary);
|
|
105
|
-
}
|
|
106
|
-
button[tab] md-icon {
|
|
107
|
-
font-size: 20px;
|
|
108
|
-
width: 20px;
|
|
109
|
-
height: 20px;
|
|
110
|
-
}
|
|
111
|
-
span[badge] {
|
|
112
|
-
position: absolute;
|
|
113
|
-
top: 5px;
|
|
114
|
-
right: 2px;
|
|
115
|
-
padding: 0 5px;
|
|
116
|
-
font-size: 0.62rem;
|
|
117
|
-
font-weight: 700;
|
|
118
|
-
line-height: 1.5;
|
|
119
|
-
color: var(--md-sys-color-on-primary-container);
|
|
120
|
-
background-color: var(--md-sys-color-primary-container);
|
|
121
|
-
border-radius: 999em;
|
|
122
|
-
pointer-events: none;
|
|
123
|
-
}
|
|
124
|
-
/* Hidden, not removed: the verdict keeps its last answer and the panel its list while the other tab is open. */
|
|
125
|
-
[off] {
|
|
126
|
-
display: none;
|
|
127
|
-
}
|
|
128
75
|
.error {
|
|
129
76
|
padding: 16px;
|
|
130
|
-
color:
|
|
77
|
+
color: var(--md-sys-color-error);
|
|
131
78
|
}
|
|
132
79
|
`
|
|
133
80
|
private readonly session = createV3ProductSession(v3ProductStore)
|
|
@@ -149,12 +96,9 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
149
96
|
@state() private typeError = ''
|
|
150
97
|
/** Why a command from the side did nothing. Shown beside the tabs — the editor stays, since nothing in it was lost. */
|
|
151
98
|
@state() private commandError = ''
|
|
152
|
-
/**
|
|
153
|
-
* Which tab the side shows. The verdict comes first: it is what the author needs while editing, and the count on
|
|
154
|
-
* its tab stays in sight while the history is open — as on the V2 page.
|
|
155
|
-
*/
|
|
156
|
-
@state() private tab: 'properties' | 'verdict' | 'history' = 'verdict'
|
|
99
|
+
/** The verdict's count, handed to the editor's verdict tab. */
|
|
157
100
|
@state() private instances?: number
|
|
101
|
+
@state() private budget?: V3BudgetLine
|
|
158
102
|
/**
|
|
159
103
|
* The source the verdict judges. The session refuses to export while it loads or saves, so the last one read is
|
|
160
104
|
* kept rather than blanked — the verdict would otherwise flicker to 「waiting」 on every save.
|
|
@@ -364,66 +308,54 @@ export class FigureV3ModellerPage extends PageView {
|
|
|
364
308
|
render() {
|
|
365
309
|
if (this.error) return html`<p role="alert" class="error">${this.error}</p>`
|
|
366
310
|
const view = this.session.read()
|
|
367
|
-
return html`<figure-v3-editor
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
.dirty=${!!view.dirty}
|
|
416
|
-
@fit-base-to-parts=${this.fitToParts}
|
|
417
|
-
@figure-released=${this.released}
|
|
418
|
-
@figure-reverted=${this.reverted}
|
|
419
|
-
></figure-history-panel>
|
|
420
|
-
</aside>`
|
|
421
|
-
}
|
|
422
|
-
private renderTab(tab: 'properties' | 'verdict' | 'history', icon: string, label: string, badge?: number) {
|
|
423
|
-
return html`
|
|
424
|
-
<button tab ?on=${this.tab === tab} title=${label} aria-label=${label} @click=${() => (this.tab = tab)}>
|
|
425
|
-
<md-icon>${icon}</md-icon>${badge == null ? '' : html`<span badge>${badge.toLocaleString()}</span>`}
|
|
426
|
-
</button>
|
|
427
|
-
`
|
|
311
|
+
return html`<figure-v3-editor .session=${this.session} .revision=${this.revision} .instances=${this.instances} .budget=${this.budget} @v3-changed=${this.changed}>
|
|
312
|
+
${this.commandError ? html`<p slot="notice" fit-error role="alert">${this.commandError}</p>` : ''}
|
|
313
|
+
<figure-v3-properties
|
|
314
|
+
slot="figure"
|
|
315
|
+
.source=${this.draftSource()}
|
|
316
|
+
.figureId=${view.id}
|
|
317
|
+
.figureCategory=${this.row.category}
|
|
318
|
+
.figureTags=${this.row.tags}
|
|
319
|
+
@v3-command=${this.onCommand}
|
|
320
|
+
@v3-catalog=${this.onCatalog}
|
|
321
|
+
>
|
|
322
|
+
<label slot="type" type-field>
|
|
323
|
+
<span>${i18next.t('figure.label.figure-type')}</span>
|
|
324
|
+
${this.row.version > 0
|
|
325
|
+
? html`<code>${this.row.type}</code><small>${i18next.t('figure.text.type-name-cannot-change')}</small>`
|
|
326
|
+
: html`<input
|
|
327
|
+
.value=${this.row.type ?? ''}
|
|
328
|
+
placeholder=${i18next.t('figure.text.figure-type-placeholder')}
|
|
329
|
+
?disabled=${!view.id}
|
|
330
|
+
@change=${this.renameType}
|
|
331
|
+
/>`}
|
|
332
|
+
${this.typeError ? html`<small role="alert">${this.typeError}</small>` : ''}
|
|
333
|
+
</label>
|
|
334
|
+
</figure-v3-properties>
|
|
335
|
+
<figure-v3-verdict
|
|
336
|
+
slot="verdict"
|
|
337
|
+
.source=${this.draftSource()}
|
|
338
|
+
.figureId=${view.id}
|
|
339
|
+
.figureType=${this.row.type}
|
|
340
|
+
@capacity-changed=${(event: CustomEvent) => {
|
|
341
|
+
this.instances = event.detail.instances
|
|
342
|
+
this.budget = event.detail.budget
|
|
343
|
+
}}
|
|
344
|
+
@fit-base-to-parts=${this.fitToParts}
|
|
345
|
+
></figure-v3-verdict>
|
|
346
|
+
<figure-history-panel
|
|
347
|
+
slot="history"
|
|
348
|
+
format="v3"
|
|
349
|
+
.figureId=${view.id}
|
|
350
|
+
.figureState=${this.row.state}
|
|
351
|
+
.figureVersion=${this.row.version}
|
|
352
|
+
.figureType=${this.row.type}
|
|
353
|
+
.dirty=${!!view.dirty}
|
|
354
|
+
@fit-base-to-parts=${this.fitToParts}
|
|
355
|
+
@figure-released=${this.released}
|
|
356
|
+
@figure-reverted=${this.reverted}
|
|
357
|
+
></figure-history-panel>
|
|
358
|
+
</figure-v3-editor>`
|
|
428
359
|
}
|
|
360
|
+
|
|
429
361
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 막대 옆에 서는 **각도 눈**. 지금 각이 범위의 어디인지를 한 눈에 보인다.
|
|
3
|
+
*
|
|
4
|
+
* ## 왜 3D 로는 모자랐나
|
|
5
|
+
*
|
|
6
|
+
* 관절이 돌아도 화면이 그대로인 경우가 있다 — 제 축에서 대칭인 부품이 그렇다. 그래서 관절이
|
|
7
|
+
* 모는 부품에 바늘을 달아 미리보기에서 함께 돌게 했는데(`joint-gizmo.ts` 의 `jointNeedles`),
|
|
8
|
+
* **미리보기에서는 그 바늘이 4 × 2 픽셀이다**(잰 값: 부품 100mm · 바늘 60 × 12mm · 여덟 대를
|
|
9
|
+
* 한 화면에 세운 상태). 미리보기는 크기 반응을 보려고 여덟 대를 나란히 놓는 자리라 한 대가
|
|
10
|
+
* 20 픽셀쯤이고, 그 위의 어떤 표시도 몇 픽셀을 넘지 못한다.
|
|
11
|
+
*
|
|
12
|
+
* 각을 읽는 일은 3D 의 배율에서 떼어 놓는다. 눈은 막대 옆 24 픽셀 자리에 서고, 도형이 아무리
|
|
13
|
+
* 작게 서 있어도 같은 크기로 읽힌다.
|
|
14
|
+
*/
|
|
15
|
+
export interface DialShape {
|
|
16
|
+
/** 가동 범위를 채운 부채꼴. SVG path 의 `d`. */
|
|
17
|
+
sector: string;
|
|
18
|
+
/** 지금 값이 가리키는 끝점. */
|
|
19
|
+
needle: {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
};
|
|
23
|
+
centre: {
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
};
|
|
27
|
+
radius: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 눈의 모양. 값이 범위를 벗어나면 범위 끝에 세운다 — 밖을 가리키는 바늘은 없는 값을 말한다.
|
|
31
|
+
*/
|
|
32
|
+
export declare function dialShape(min: number, max: number, value: number, radius?: number): DialShape;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/** 0 도가 위를 가리키고, 값이 커지면 시계 방향으로 돈다 — 화면의 각과 같은 방향이다. */
|
|
5
|
+
function at(centre, radius, degrees) {
|
|
6
|
+
const rad = (degrees * Math.PI) / 180;
|
|
7
|
+
return {
|
|
8
|
+
x: +(centre.x + radius * Math.sin(rad)).toFixed(2),
|
|
9
|
+
y: +(centre.y - radius * Math.cos(rad)).toFixed(2)
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** 한 바퀴에 가까우면 부채꼴 대신 온전한 원이다. */
|
|
13
|
+
const FULL = 359.9;
|
|
14
|
+
/**
|
|
15
|
+
* 눈의 모양. 값이 범위를 벗어나면 범위 끝에 세운다 — 밖을 가리키는 바늘은 없는 값을 말한다.
|
|
16
|
+
*/
|
|
17
|
+
export function dialShape(min, max, value, radius = 9) {
|
|
18
|
+
const centre = { x: radius + 1, y: radius + 1 };
|
|
19
|
+
const span = Math.abs(max - min);
|
|
20
|
+
const held = Math.min(Math.max(value, Math.min(min, max)), Math.max(min, max));
|
|
21
|
+
if (span >= FULL) {
|
|
22
|
+
const top = at(centre, radius, 0);
|
|
23
|
+
const bottom = at(centre, radius, 180);
|
|
24
|
+
return {
|
|
25
|
+
sector: `M ${top.x} ${top.y} A ${radius} ${radius} 0 1 1 ${bottom.x} ${bottom.y} A ${radius} ${radius} 0 1 1 ${top.x} ${top.y} Z`,
|
|
26
|
+
needle: at(centre, radius, held),
|
|
27
|
+
centre,
|
|
28
|
+
radius
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const from = at(centre, radius, min);
|
|
32
|
+
const to = at(centre, radius, max);
|
|
33
|
+
const large = span > 180 ? 1 : 0;
|
|
34
|
+
return {
|
|
35
|
+
sector: `M ${centre.x} ${centre.y} L ${from.x} ${from.y} A ${radius} ${radius} 0 ${large} 1 ${to.x} ${to.y} Z`,
|
|
36
|
+
needle: at(centre, radius, held),
|
|
37
|
+
centre,
|
|
38
|
+
radius
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=angle-dial.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angle-dial.js","sourceRoot":"","sources":["../../client/modeller/angle-dial.ts"],"names":[],"mappings":"AAAA;;GAEG;AA0BH,wDAAwD;AACxD,SAAS,EAAE,CAAC,MAAgC,EAAE,MAAc,EAAE,OAAe;IAC3E,MAAM,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAA;IACrC,OAAO;QACL,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QAClD,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;KACnD,CAAA;AACH,CAAC;AAED,iCAAiC;AACjC,MAAM,IAAI,GAAG,KAAK,CAAA;AAElB;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,GAAW,EAAE,KAAa,EAAE,MAAM,GAAG,CAAC;IAC3E,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAAA;IAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAA;IAChC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;IAE9E,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACjB,MAAM,GAAG,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;QACjC,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACtC,OAAO;YACL,MAAM,EAAE,KAAK,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,MAAM,MAAM,IAAI,MAAM,UAAU,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,MAAM,IAAI,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI;YACjI,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC;YAChC,MAAM;YACN,MAAM;SACP,CAAA;IACH,CAAC;IAED,MAAM,IAAI,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;IACpC,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;IAClC,MAAM,KAAK,GAAG,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAEhC,OAAO;QACL,MAAM,EAAE,KAAK,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,MAAM,IAAI,MAAM,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI;QAC9G,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC;QAChC,MAAM;QACN,MAAM;KACP,CAAA;AACH,CAAC","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\n/**\n * 막대 옆에 서는 **각도 눈**. 지금 각이 범위의 어디인지를 한 눈에 보인다.\n *\n * ## 왜 3D 로는 모자랐나\n *\n * 관절이 돌아도 화면이 그대로인 경우가 있다 — 제 축에서 대칭인 부품이 그렇다. 그래서 관절이\n * 모는 부품에 바늘을 달아 미리보기에서 함께 돌게 했는데(`joint-gizmo.ts` 의 `jointNeedles`),\n * **미리보기에서는 그 바늘이 4 × 2 픽셀이다**(잰 값: 부품 100mm · 바늘 60 × 12mm · 여덟 대를\n * 한 화면에 세운 상태). 미리보기는 크기 반응을 보려고 여덟 대를 나란히 놓는 자리라 한 대가\n * 20 픽셀쯤이고, 그 위의 어떤 표시도 몇 픽셀을 넘지 못한다.\n *\n * 각을 읽는 일은 3D 의 배율에서 떼어 놓는다. 눈은 막대 옆 24 픽셀 자리에 서고, 도형이 아무리\n * 작게 서 있어도 같은 크기로 읽힌다.\n */\n\nexport interface DialShape {\n /** 가동 범위를 채운 부채꼴. SVG path 의 `d`. */\n sector: string\n /** 지금 값이 가리키는 끝점. */\n needle: { x: number; y: number }\n centre: { x: number; y: number }\n radius: number\n}\n\n/** 0 도가 위를 가리키고, 값이 커지면 시계 방향으로 돈다 — 화면의 각과 같은 방향이다. */\nfunction at(centre: { x: number; y: number }, radius: number, degrees: number) {\n const rad = (degrees * Math.PI) / 180\n return {\n x: +(centre.x + radius * Math.sin(rad)).toFixed(2),\n y: +(centre.y - radius * Math.cos(rad)).toFixed(2)\n }\n}\n\n/** 한 바퀴에 가까우면 부채꼴 대신 온전한 원이다. */\nconst FULL = 359.9\n\n/**\n * 눈의 모양. 값이 범위를 벗어나면 범위 끝에 세운다 — 밖을 가리키는 바늘은 없는 값을 말한다.\n */\nexport function dialShape(min: number, max: number, value: number, radius = 9): DialShape {\n const centre = { x: radius + 1, y: radius + 1 }\n const span = Math.abs(max - min)\n const held = Math.min(Math.max(value, Math.min(min, max)), Math.max(min, max))\n\n if (span >= FULL) {\n const top = at(centre, radius, 0)\n const bottom = at(centre, radius, 180)\n return {\n sector: `M ${top.x} ${top.y} A ${radius} ${radius} 0 1 1 ${bottom.x} ${bottom.y} A ${radius} ${radius} 0 1 1 ${top.x} ${top.y} Z`,\n needle: at(centre, radius, held),\n centre,\n radius\n }\n }\n\n const from = at(centre, radius, min)\n const to = at(centre, radius, max)\n const large = span > 180 ? 1 : 0\n\n return {\n sector: `M ${centre.x} ${centre.y} L ${from.x} ${from.y} A ${radius} ${radius} 0 ${large} 1 ${to.x} ${to.y} Z`,\n needle: at(centre, radius, held),\n centre,\n radius\n }\n}\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import type { ViewSettings } from './figure-view.js';
|
|
3
|
+
declare const FigureSettings_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
4
|
+
/**
|
|
5
|
+
* 저작 화면의 **보기 설정**.
|
|
6
|
+
*
|
|
7
|
+
* ## 자산이 아니라 작업대다
|
|
8
|
+
*
|
|
9
|
+
* 여기서 고치는 것은 저장되지 않는다. 조명을 밝게 하고 바닥을 켠 것은 지금 보기 편한
|
|
10
|
+
* 상태일 뿐이다. 그래서 「저장 안 됨」을 화면에 적어 둔다 — 안 적으면 저작자가 조명을
|
|
11
|
+
* 맞춰 두고 그것이 자산에 딸려 간다고 여긴다.
|
|
12
|
+
*
|
|
13
|
+
* ## 값을 들고 있지 않는다
|
|
14
|
+
*
|
|
15
|
+
* 이 패널은 받은 것을 그리고 바뀐 것을 알린다. 정본은 모델러 페이지가 갖는다 — 속성
|
|
16
|
+
* 패널과 같은 방식이다. 여기서 들고 있으면 페이지가 다시 그릴 때 두 벌이 갈린다.
|
|
17
|
+
*/
|
|
18
|
+
export declare class FigureSettings extends FigureSettings_base {
|
|
19
|
+
static styles: import("lit").CSSResult[];
|
|
20
|
+
view: ViewSettings;
|
|
21
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
22
|
+
private toggle;
|
|
23
|
+
private slider;
|
|
24
|
+
/** 처음 값으로 되돌린다. 만지다 길을 잃었을 때 돌아올 자리가 있어야 한다. */
|
|
25
|
+
private reset;
|
|
26
|
+
private put;
|
|
27
|
+
/**
|
|
28
|
+
* 바뀐 것을 알린다.
|
|
29
|
+
*
|
|
30
|
+
* **알리기 전에 내 것도 바꿔 둔다.** Lit 은 속성을 다음 렌더에 넣으므로, 같은 프레임에
|
|
31
|
+
* 두 번 고치면 두 번째가 `this.view` 의 옛 값을 읽어 첫 번째를 덮는다. 실제로 그랬다 —
|
|
32
|
+
* 축 가이드를 끄고 바닥판을 켰더니 축 가이드가 도로 켜져 있었고, 화면의 체크 상자와
|
|
33
|
+
* 씬의 상태가 서로 다른 말을 했다.
|
|
34
|
+
*
|
|
35
|
+
* 정본은 여전히 모델러 페이지가 갖는다. 여기 값은 다음 렌더에 같은 것으로 덮인다.
|
|
36
|
+
*/
|
|
37
|
+
private tell;
|
|
38
|
+
}
|
|
39
|
+
export {};
|