@things-factory/figure-ui 10.1.43 → 10.1.45
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/figure-v3-links.ts +303 -0
- package/client/modeller/modeller-route.ts +6 -21
- package/client/modeller/v3-editor.ts +44 -3
- package/client/modeller/v3-gizmo.ts +13 -2
- package/client/modeller/v3-links.ts +138 -0
- package/client/pages/figure-v3-modeller-page.ts +2 -5
- package/client/route.ts +2 -6
- package/dist-client/modeller/figure-v3-links.d.ts +42 -0
- package/dist-client/modeller/figure-v3-links.js +306 -0
- package/dist-client/modeller/figure-v3-links.js.map +1 -0
- package/dist-client/modeller/modeller-route.d.ts +4 -11
- package/dist-client/modeller/modeller-route.js +5 -22
- package/dist-client/modeller/modeller-route.js.map +1 -1
- package/dist-client/modeller/v3-editor.d.ts +5 -0
- package/dist-client/modeller/v3-editor.js +46 -2
- package/dist-client/modeller/v3-editor.js.map +1 -1
- package/dist-client/modeller/v3-gizmo.js +11 -1
- package/dist-client/modeller/v3-gizmo.js.map +1 -1
- package/dist-client/modeller/v3-links.d.ts +54 -0
- package/dist-client/modeller/v3-links.js +110 -0
- package/dist-client/modeller/v3-links.js.map +1 -0
- package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -5
- package/dist-client/pages/figure-v3-modeller-page.js +2 -5
- package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +2 -5
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/test/ai-proposal-contract.test.ts +0 -88
- package/test/figure-source.test.ts +0 -102
- package/test/i18n-prefix-guard.test.ts +0 -24
- package/test/modeller-route.test.ts +14 -25
- package/test/part-edits.test.ts +0 -7
- package/test/scene-budget.test.ts +0 -7
- package/test/v3-gizmo.test.ts +10 -0
- package/test/v3-links.test.ts +123 -0
- package/things-factory.config.js +2 -5
- package/translations/en.json +6 -0
- package/translations/ja.json +11 -1
- package/translations/ko.json +6 -0
- package/translations/ms.json +11 -1
- package/translations/zh.json +11 -1
- package/client/modeller/angle-dial.ts +0 -70
- package/client/modeller/base-box.ts +0 -175
- package/client/modeller/figure-animations.ts +0 -1249
- package/client/modeller/figure-canvas.ts +0 -567
- package/client/modeller/figure-capabilities.ts +0 -379
- package/client/modeller/figure-inspector.ts +0 -2208
- package/client/modeller/figure-parts.ts +0 -479
- package/client/modeller/figure-preview.ts +0 -598
- package/client/modeller/figure-report.ts +0 -966
- package/client/modeller/figure-settings.ts +0 -328
- package/client/modeller/figure-side.ts +0 -272
- package/client/modeller/figure-thumbnail.ts +0 -123
- package/client/modeller/figure-view.ts +0 -152
- package/client/modeller/joint-edits.ts +0 -262
- package/client/modeller/joint-gizmo.ts +0 -180
- package/client/modeller/maturity.ts +0 -288
- package/client/modeller/new-figure-entry.ts +0 -4
- package/client/modeller/pose-defaults.ts +0 -67
- package/client/pages/figure-modeller-page.ts +0 -2082
- package/dist-client/modeller/angle-dial.d.ts +0 -32
- package/dist-client/modeller/angle-dial.js +0 -41
- package/dist-client/modeller/angle-dial.js.map +0 -1
- package/dist-client/modeller/base-box.d.ts +0 -71
- package/dist-client/modeller/base-box.js +0 -110
- package/dist-client/modeller/base-box.js.map +0 -1
- package/dist-client/modeller/figure-animations.d.ts +0 -159
- package/dist-client/modeller/figure-animations.js +0 -1132
- package/dist-client/modeller/figure-animations.js.map +0 -1
- package/dist-client/modeller/figure-canvas.d.ts +0 -150
- package/dist-client/modeller/figure-canvas.js +0 -506
- package/dist-client/modeller/figure-canvas.js.map +0 -1
- package/dist-client/modeller/figure-capabilities.d.ts +0 -42
- package/dist-client/modeller/figure-capabilities.js +0 -349
- package/dist-client/modeller/figure-capabilities.js.map +0 -1
- package/dist-client/modeller/figure-inspector.d.ts +0 -329
- package/dist-client/modeller/figure-inspector.js +0 -2095
- package/dist-client/modeller/figure-inspector.js.map +0 -1
- package/dist-client/modeller/figure-parts.d.ts +0 -64
- package/dist-client/modeller/figure-parts.js +0 -478
- package/dist-client/modeller/figure-parts.js.map +0 -1
- package/dist-client/modeller/figure-preview.d.ts +0 -138
- package/dist-client/modeller/figure-preview.js +0 -549
- package/dist-client/modeller/figure-preview.js.map +0 -1
- package/dist-client/modeller/figure-report.d.ts +0 -165
- package/dist-client/modeller/figure-report.js +0 -952
- package/dist-client/modeller/figure-report.js.map +0 -1
- package/dist-client/modeller/figure-settings.d.ts +0 -39
- package/dist-client/modeller/figure-settings.js +0 -317
- package/dist-client/modeller/figure-settings.js.map +0 -1
- package/dist-client/modeller/figure-side.d.ts +0 -78
- package/dist-client/modeller/figure-side.js +0 -317
- package/dist-client/modeller/figure-side.js.map +0 -1
- package/dist-client/modeller/figure-thumbnail.d.ts +0 -3
- package/dist-client/modeller/figure-thumbnail.js +0 -95
- package/dist-client/modeller/figure-thumbnail.js.map +0 -1
- package/dist-client/modeller/figure-view.d.ts +0 -118
- package/dist-client/modeller/figure-view.js +0 -78
- package/dist-client/modeller/figure-view.js.map +0 -1
- package/dist-client/modeller/joint-edits.d.ts +0 -49
- package/dist-client/modeller/joint-edits.js +0 -223
- package/dist-client/modeller/joint-edits.js.map +0 -1
- package/dist-client/modeller/joint-gizmo.d.ts +0 -34
- package/dist-client/modeller/joint-gizmo.js +0 -158
- package/dist-client/modeller/joint-gizmo.js.map +0 -1
- package/dist-client/modeller/maturity.d.ts +0 -55
- package/dist-client/modeller/maturity.js +0 -212
- package/dist-client/modeller/maturity.js.map +0 -1
- package/dist-client/modeller/new-figure-entry.d.ts +0 -2
- package/dist-client/modeller/new-figure-entry.js +0 -5
- package/dist-client/modeller/new-figure-entry.js.map +0 -1
- package/dist-client/modeller/pose-defaults.d.ts +0 -9
- package/dist-client/modeller/pose-defaults.js +0 -35
- package/dist-client/modeller/pose-defaults.js.map +0 -1
- package/dist-client/pages/figure-modeller-page.d.ts +0 -419
- package/dist-client/pages/figure-modeller-page.js +0 -2060
- package/dist-client/pages/figure-modeller-page.js.map +0 -1
- package/test/angle-dial.test.ts +0 -78
- package/test/base-box.test.ts +0 -235
- package/test/joint-edits.test.ts +0 -165
- package/test/joint-gizmo.test.ts +0 -174
- package/test/joint-needle.test.ts +0 -162
- package/test/modeller-undo-redo.test.ts +0 -33
- package/test/new-figure-entry.test.ts +0 -15
- package/test/pose-defaults.test.ts +0 -71
- package/test/preview-board-depth.test.ts +0 -27
package/test/joint-edits.test.ts
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { describe, it } from 'node:test'
|
|
3
|
-
|
|
4
|
-
import { validate } from '@hatiolab/figure-model'
|
|
5
|
-
import type { FigurePart } from '@hatiolab/figure-model'
|
|
6
|
-
|
|
7
|
-
import { parentChoices, patchJoint, setJointType, jointOf } from '../client/modeller/joint-edits.ts'
|
|
8
|
-
import { fromFigureSource, partToFigure, toFigureSource } from '../client/modeller/figure-source.ts'
|
|
9
|
-
import type { FigureDraft, PartModel } from '../client/modeller/figure-source.ts'
|
|
10
|
-
|
|
11
|
-
/*
|
|
12
|
-
* Editing joints (ADR-0066). Every result is checked against the format's own validate, so an edit that
|
|
13
|
-
* produces a source the format refuses fails here instead of on save.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const part = (name: string, zPos: number, depth: number, parent?: string): PartModel => ({
|
|
17
|
-
type: 'figure-part',
|
|
18
|
-
name,
|
|
19
|
-
primitive: 'cube',
|
|
20
|
-
left: 50,
|
|
21
|
-
top: 50,
|
|
22
|
-
width: 100,
|
|
23
|
-
height: 100,
|
|
24
|
-
zPos,
|
|
25
|
-
depth,
|
|
26
|
-
token: 'palette.primary',
|
|
27
|
-
...(parent ? { parent } : {})
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const PARTS: PartModel[] = [part('base', 0, 100), part('arm', 100, 400, 'base'), part('tip', 500, 100, 'arm')]
|
|
31
|
-
const DRAFT: FigureDraft = { version: 2, figureType: 'ARM', width: 200, height: 200, depth: 600, detailLevel: 'M' }
|
|
32
|
-
|
|
33
|
-
const figurePart = (draft: FigureDraft, name: string): FigurePart => partToFigure(PARTS.find(p => p.name === name)!, draft)
|
|
34
|
-
const errorsOf = (draft: FigureDraft) => validate(toFigureSource(draft, PARTS)).errors.map(e => `${e.code}@${e.path}`)
|
|
35
|
-
|
|
36
|
-
describe('joint edits', () => {
|
|
37
|
-
it('offers as parents every part except itself and what hangs below it', () => {
|
|
38
|
-
assert.deepEqual(parentChoices(PARTS, 'arm'), ['base'])
|
|
39
|
-
// arm and tip hang below base, so base has nothing to attach to
|
|
40
|
-
assert.deepEqual(parentChoices(PARTS, 'base'), [])
|
|
41
|
-
assert.deepEqual(parentChoices([...PARTS, part('lamp', 0, 10)], 'arm'), ['base', 'lamp'])
|
|
42
|
-
assert.deepEqual(parentChoices(PARTS, 'tip'), ['base', 'arm'])
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('adds a joint where the part meets its parent, with a parameter that drives it', () => {
|
|
46
|
-
const draft = setJointType(DRAFT, figurePart(DRAFT, 'arm'), 'revolute', figurePart(DRAFT, 'base'))
|
|
47
|
-
const joint = jointOf(draft, 'arm')!
|
|
48
|
-
assert.deepEqual(joint, {
|
|
49
|
-
name: 'arm-joint',
|
|
50
|
-
child: 'arm',
|
|
51
|
-
type: 'revolute',
|
|
52
|
-
origin: { x: 0, y: 100, z: 0 },
|
|
53
|
-
axis: { x: 0, y: 1, z: 0 },
|
|
54
|
-
limits: { min: -90, max: 90 }
|
|
55
|
-
})
|
|
56
|
-
assert.deepEqual(draft.parameters, [
|
|
57
|
-
{
|
|
58
|
-
name: 'arm-joint',
|
|
59
|
-
range: { unit: 'deg', min: -90, max: 90 },
|
|
60
|
-
default: 0,
|
|
61
|
-
clip: { channels: [{ target: 'arm-joint', keys: [{ at: 0, value: -90 }, { at: 1, value: 90 }] }] }
|
|
62
|
-
}
|
|
63
|
-
])
|
|
64
|
-
assert.deepEqual(errorsOf(draft), [])
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('moves a plain driving parameter to millimetres when the joint starts sliding', () => {
|
|
68
|
-
const turning = setJointType(DRAFT, figurePart(DRAFT, 'arm'), 'revolute')
|
|
69
|
-
const sliding = setJointType(turning, figurePart(turning, 'arm'), 'prismatic')
|
|
70
|
-
assert.deepEqual(jointOf(sliding, 'arm')!.limits, { min: 0, max: 500 })
|
|
71
|
-
assert.deepEqual(sliding.parameters![0]!.range, { unit: 'mm', min: 0, max: 500 })
|
|
72
|
-
assert.deepEqual(errorsOf(sliding), [])
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('drops limits for a continuous joint and still validates', () => {
|
|
76
|
-
const turning = setJointType(DRAFT, figurePart(DRAFT, 'arm'), 'revolute')
|
|
77
|
-
const endless = setJointType(turning, figurePart(turning, 'arm'), 'continuous')
|
|
78
|
-
assert.equal('limits' in jointOf(endless, 'arm')!, false)
|
|
79
|
-
assert.deepEqual(endless.parameters![0]!.range, { unit: 'deg', min: -360, max: 360 })
|
|
80
|
-
assert.deepEqual(errorsOf(endless), [])
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('moves a plain driving parameter with new limits, and leaves one the author reshaped alone', () => {
|
|
84
|
-
const turning = setJointType(DRAFT, figurePart(DRAFT, 'arm'), 'revolute')
|
|
85
|
-
const wider = patchJoint(turning, 'arm-joint', { limits: { min: -120, max: 150 } })
|
|
86
|
-
assert.deepEqual(wider.parameters![0]!.range, { unit: 'deg', min: -120, max: 150 })
|
|
87
|
-
assert.deepEqual(errorsOf(wider), [])
|
|
88
|
-
|
|
89
|
-
const reshaped: FigureDraft = {
|
|
90
|
-
...turning,
|
|
91
|
-
parameters: [
|
|
92
|
-
{
|
|
93
|
-
...turning.parameters![0]!,
|
|
94
|
-
clip: { channels: [{ target: 'arm-joint', keys: [{ at: 0, value: 0 }, { at: 0.5, value: 45 }, { at: 1, value: 90 }] }] }
|
|
95
|
-
}
|
|
96
|
-
]
|
|
97
|
-
}
|
|
98
|
-
const kept = patchJoint(reshaped, 'arm-joint', { limits: { min: -120, max: 150 } })
|
|
99
|
-
assert.deepEqual(kept.parameters, reshaped.parameters)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('clearing the joint removes it and every channel that drove it, and nothing else', () => {
|
|
103
|
-
const turning = setJointType(DRAFT, figurePart(DRAFT, 'arm'), 'revolute')
|
|
104
|
-
const withClip: FigureDraft = {
|
|
105
|
-
...turning,
|
|
106
|
-
animations: [
|
|
107
|
-
{
|
|
108
|
-
name: 'wiggle',
|
|
109
|
-
channels: [
|
|
110
|
-
{ target: 'arm-joint', keys: [{ at: 0, value: 0 }, { at: 1, value: 30 }] },
|
|
111
|
-
{ target: 'tip', path: 'rotation', keys: [{ at: 0, value: { x: 0, y: 0, z: 0 } }, { at: 1, value: { x: 0, y: 90, z: 0 } }] }
|
|
112
|
-
]
|
|
113
|
-
}
|
|
114
|
-
]
|
|
115
|
-
}
|
|
116
|
-
const cleared = setJointType(withClip, figurePart(withClip, 'arm'), undefined)
|
|
117
|
-
assert.equal(cleared.joints, undefined)
|
|
118
|
-
assert.deepEqual(cleared.parameters, [])
|
|
119
|
-
assert.deepEqual(cleared.animations![0]!.channels.map(c => c.target), ['tip'])
|
|
120
|
-
assert.deepEqual(errorsOf(cleared), [])
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
/*
|
|
124
|
-
* 관절은 두 부품이 **맞닿는 자리**에 선다. 위로 쌓은 부품에서는 아랫면이지만, 옆에 달린
|
|
125
|
-
* 부품에서는 옆면이다 — 그 구분이 없으면 캐비닛 문의 축이 문 바닥에 생긴다.
|
|
126
|
-
*/
|
|
127
|
-
describe('새 관절이 서는 자리', () => {
|
|
128
|
-
const at = (over: Partial<PartModel>): PartModel => ({ ...part('door', 0, 100), ...over })
|
|
129
|
-
|
|
130
|
-
it('위에 쌓인 부품은 아랫면에서 만난다', () => {
|
|
131
|
-
/* arm 은 base(0..100) 위 100..500 이다. 맞닿는 면이 100 이고, 가로로는 둘이 겹치는 가운데다. */
|
|
132
|
-
const joint = jointOf(setJointType(DRAFT, figurePart(DRAFT, 'arm'), 'revolute', figurePart(DRAFT, 'base')), 'arm')!
|
|
133
|
-
assert.deepEqual(joint.origin, { x: 0, y: 100, z: 0 })
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('옆에 달린 부품은 옆면에서 만난다 — 아랫면이 아니다', () => {
|
|
137
|
-
/* 문은 몸통(가운데 0, 폭 100)의 오른쪽에 붙어 선다. 축은 문의 왼쪽 면, 높이는 겹치는 가운데. */
|
|
138
|
-
const body = at({ name: 'body', left: 0, top: 0, width: 100, height: 100, zPos: 0, depth: 400 })
|
|
139
|
-
const door = at({ name: 'door', left: 100, top: 0, width: 20, height: 100, zPos: 40, depth: 320, parent: 'body' })
|
|
140
|
-
const parts = [body, door]
|
|
141
|
-
const draft: FigureDraft = { ...DRAFT, width: 400, height: 400, depth: 400 }
|
|
142
|
-
const shown = (one: PartModel) => partToFigure(one, draft)
|
|
143
|
-
|
|
144
|
-
const joint = jointOf(setJointType(draft, shown(door), 'revolute', shown(body)), 'door')!
|
|
145
|
-
const half = draft.width / 2
|
|
146
|
-
|
|
147
|
-
assert.equal(joint.origin.x, 100 - half, '문의 왼쪽 면에 선다')
|
|
148
|
-
assert.equal(joint.origin.y, 200, '겹치는 높이(40..360 과 0..400)의 한가운데다')
|
|
149
|
-
assert.deepEqual(parts.map(one => one.name), ['body', 'door'])
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
it('붙은 부품이 없으면 아랫면이다 — 그때 맞닿는 것은 바닥이다', () => {
|
|
153
|
-
const joint = jointOf(setJointType(DRAFT, figurePart(DRAFT, 'base'), 'revolute'), 'base')!
|
|
154
|
-
assert.equal(joint.origin.y, 0)
|
|
155
|
-
})
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
it('a joint survives opening and saving', () => {
|
|
159
|
-
const turning = setJointType(DRAFT, figurePart(DRAFT, 'arm'), 'revolute')
|
|
160
|
-
const saved = toFigureSource(turning, PARTS)
|
|
161
|
-
const { draft, parts } = fromFigureSource(saved)
|
|
162
|
-
assert.deepEqual(toFigureSource(draft, parts), saved)
|
|
163
|
-
assert.equal(saved.parts.find(p => p.name === 'tip')!.parent, 'arm')
|
|
164
|
-
})
|
|
165
|
-
})
|
package/test/joint-gizmo.test.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { describe, it } from 'node:test'
|
|
3
|
-
|
|
4
|
-
import type { FigureJoint } from '@hatiolab/figure-model'
|
|
5
|
-
|
|
6
|
-
import { axisName, gizmoPart, JOINT_GIZMO } from '../client/modeller/joint-gizmo.ts'
|
|
7
|
-
import type { FigureDraft, PartModel } from '../client/modeller/figure-source.ts'
|
|
8
|
-
|
|
9
|
-
/*
|
|
10
|
-
* 관절이 도는 자리를 저작면에 그리는 부채꼴.
|
|
11
|
-
*
|
|
12
|
-
* 저작자가 축을 고를 때 보는 것이라, **잘못 그리면 틀린 쪽을 가리킨다** — 아무것도 안 그리는
|
|
13
|
-
* 것보다 나쁘다. 그래서 그릴 수 없는 축은 그리지 않는다.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const DRAFT: FigureDraft = { version: 2, figureType: 'ARM', width: 400, height: 400, depth: 1200, detailLevel: 'M' }
|
|
17
|
-
|
|
18
|
-
const ARM: PartModel = {
|
|
19
|
-
type: 'figure-part',
|
|
20
|
-
name: 'arm',
|
|
21
|
-
primitive: 'cube',
|
|
22
|
-
left: 150,
|
|
23
|
-
top: 150,
|
|
24
|
-
width: 100,
|
|
25
|
-
height: 100,
|
|
26
|
-
zPos: 200,
|
|
27
|
-
depth: 500,
|
|
28
|
-
token: 'palette.primary'
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const joint = (over: Partial<FigureJoint> = {}): FigureJoint => ({
|
|
32
|
-
name: 'arm-joint',
|
|
33
|
-
child: 'arm',
|
|
34
|
-
type: 'revolute',
|
|
35
|
-
origin: { x: 0, y: 200, z: 0 },
|
|
36
|
-
axis: { x: 0, y: 1, z: 0 },
|
|
37
|
-
limits: { min: -90, max: 90 },
|
|
38
|
-
...over
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
/** 부채꼴 점들이 도는 각. 0 도에서 시작해 어디까지 벌어졌나. */
|
|
42
|
-
function angles(path: { x: number; y: number }[]) {
|
|
43
|
-
const arc = path.filter(point => Math.hypot(point.x, point.y) > 1)
|
|
44
|
-
return arc.map(point => Math.round((Math.atan2(point.y, point.x) * 180) / Math.PI))
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
describe('관절 부채꼴', () => {
|
|
48
|
-
it('축 벡터를 이름으로 옮긴다', () => {
|
|
49
|
-
assert.equal(axisName({ x: 0, y: 1, z: 0 }), '+y')
|
|
50
|
-
assert.equal(axisName({ x: -1, y: 0, z: 0 }), '-x')
|
|
51
|
-
assert.equal(axisName({ x: 0, y: 0, z: 1 }), '+z')
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('비스듬한 축은 이름이 없고, 그리지도 않는다 — 아무 쪽이나 고르면 틀린 쪽을 가리킨다', () => {
|
|
55
|
-
const slanted = { x: 0.7, y: 0.7, z: 0 }
|
|
56
|
-
assert.equal(axisName(slanted), undefined)
|
|
57
|
-
assert.equal(gizmoPart(joint({ axis: slanted }), ARM, DRAFT), undefined)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('가동 범위만큼 벌어지고, 가운데 점이 하나 있다', () => {
|
|
61
|
-
const part = gizmoPart(joint(), ARM, DRAFT)!
|
|
62
|
-
const path = part.path!
|
|
63
|
-
|
|
64
|
-
assert.equal(part.name, JOINT_GIZMO)
|
|
65
|
-
assert.deepEqual(path[0], { x: 0, y: 0 }, '부채꼴은 가운데에서 시작한다')
|
|
66
|
-
|
|
67
|
-
const turned = angles(path)
|
|
68
|
-
const span = Math.abs(turned[turned.length - 1]! - turned[0]!)
|
|
69
|
-
assert.equal(span, 180, '−90 에서 90 까지면 180 도 벌어진다')
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('무한 회전은 온전한 원이라 가운데 점이 없다', () => {
|
|
73
|
-
const part = gizmoPart(joint({ type: 'continuous', limits: undefined }), ARM, DRAFT)!
|
|
74
|
-
const path = part.path!
|
|
75
|
-
|
|
76
|
-
assert.ok(path.every(point => Math.hypot(point.x, point.y) > 1), '가운데 점이 없다')
|
|
77
|
-
assert.ok(path.length >= 60, '한 바퀴를 6도씩 그린다')
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('반지름은 도는 부품의 크기를 따라간다 — 부품보다 작으면 무엇이 도는지 안 읽힌다', () => {
|
|
81
|
-
const big = gizmoPart(joint(), ARM, DRAFT)!
|
|
82
|
-
const small = gizmoPart(joint(), { ...ARM, width: 40, height: 40, depth: 40 }, DRAFT)!
|
|
83
|
-
|
|
84
|
-
assert.equal(big.width, 2 * Math.round(500 * 0.6))
|
|
85
|
-
assert.equal(small.width, 2 * 60, '작은 부품에도 눈에 보이는 최소 크기가 있다')
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('관절의 기준점에 선다', () => {
|
|
89
|
-
const part = gizmoPart(joint({ origin: { x: 50, y: 300, z: -20 } }), ARM, DRAFT)!
|
|
90
|
-
/* 저작면은 왼쪽 위 기준이고 z 는 밑면이다. 기준점이 가운데로 가야 맞다. */
|
|
91
|
-
assert.equal(part.left + part.width / 2, DRAFT.width / 2 + 50)
|
|
92
|
-
assert.equal(part.top + part.height / 2, DRAFT.height / 2 - 20)
|
|
93
|
-
assert.equal(part.zPos + part.depth / 2, 300)
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('저작면에만 있고, 씬이 집지 않는다', () => {
|
|
97
|
-
const part = gizmoPart(joint(), ARM, DRAFT)!
|
|
98
|
-
assert.equal(part.locked, true)
|
|
99
|
-
assert.equal(part.transparent, true)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
/*
|
|
103
|
-
* 가리키는 쪽이 맞나 — **이것이 이 부채꼴의 전부다.**
|
|
104
|
-
*
|
|
105
|
-
* 반대로 벌어진 부채꼴은 없느니만 못하다. 저작자가 그것을 보고 축을 고르기 때문이다. 축마다
|
|
106
|
-
* 씬의 회전과 단면의 각이 도는 쪽이 갈려서, 여섯 축을 하나씩 런타임이 도는 식(로드리게스)과
|
|
107
|
-
* 맞대 본다.
|
|
108
|
-
*/
|
|
109
|
-
describe('벌어진 쪽이 실제로 도는 쪽과 같다', () => {
|
|
110
|
-
const AXES: [string, { x: number; y: number; z: number }][] = [
|
|
111
|
-
['+x', { x: 1, y: 0, z: 0 }],
|
|
112
|
-
['-x', { x: -1, y: 0, z: 0 }],
|
|
113
|
-
['+y', { x: 0, y: 1, z: 0 }],
|
|
114
|
-
['-y', { x: 0, y: -1, z: 0 }],
|
|
115
|
-
['+z', { x: 0, y: 0, z: 1 }],
|
|
116
|
-
['-z', { x: 0, y: 0, z: -1 }]
|
|
117
|
-
]
|
|
118
|
-
|
|
119
|
-
/** 로드리게스 — 런타임이 관절을 돌리는 식(`setFromAxisAngle`)과 같은 회전이다. */
|
|
120
|
-
function turned(v: { x: number; y: number; z: number }, axis: { x: number; y: number; z: number }, degrees: number) {
|
|
121
|
-
const t = (degrees * Math.PI) / 180
|
|
122
|
-
const c = Math.cos(t)
|
|
123
|
-
const s = Math.sin(t)
|
|
124
|
-
const dot = axis.x * v.x + axis.y * v.y + axis.z * v.z
|
|
125
|
-
const cross = {
|
|
126
|
-
x: axis.y * v.z - axis.z * v.y,
|
|
127
|
-
y: axis.z * v.x - axis.x * v.z,
|
|
128
|
-
z: axis.x * v.y - axis.y * v.x
|
|
129
|
-
}
|
|
130
|
-
return {
|
|
131
|
-
x: v.x * c + cross.x * s + axis.x * dot * (1 - c),
|
|
132
|
-
y: v.y * c + cross.y * s + axis.y * dot * (1 - c),
|
|
133
|
-
z: v.z * c + cross.z * s + axis.z * dot * (1 - c)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** 단면의 점을 부품 회전까지 태워 figure 좌표로. 단면은 부품의 x–z 평면에 눕는다. */
|
|
138
|
-
function inFigure(part: PartModel, point: { x: number; y: number }) {
|
|
139
|
-
const v = { x: point.x, y: 0, z: point.y }
|
|
140
|
-
const rx = part.rotationX ?? 0
|
|
141
|
-
const rz = part.rotationY ?? 0
|
|
142
|
-
const afterZ = { x: v.x * Math.cos(rz) - v.y * Math.sin(rz), y: v.x * Math.sin(rz) + v.y * Math.cos(rz), z: v.z }
|
|
143
|
-
return { x: afterZ.x, y: afterZ.y * Math.cos(rx) - afterZ.z * Math.sin(rx), z: afterZ.y * Math.sin(rx) + afterZ.z * Math.cos(rx) }
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
for (const [name, axis] of AXES) {
|
|
147
|
-
it(`${name} 축`, () => {
|
|
148
|
-
const limits = { min: -90, max: 90 }
|
|
149
|
-
const part = gizmoPart(joint({ axis, limits }), ARM, DRAFT)!
|
|
150
|
-
/* 가운데 점 하나에 호가 이어진다. 한 바퀴가 아니면 늘 그렇다. */
|
|
151
|
-
const arc = part.path!.slice(1)
|
|
152
|
-
const at = (degrees: number) =>
|
|
153
|
-
inFigure(part, arc[Math.round(((degrees - limits.min) / (limits.max - limits.min)) * (arc.length - 1))]!)
|
|
154
|
-
|
|
155
|
-
/* 조각이 6 도씩이라 48 도가 점 하나에 정확히 떨어진다 — 사이 값을 쓰면 반올림이 섞인다. */
|
|
156
|
-
const zero = at(0)
|
|
157
|
-
const swung = at(48)
|
|
158
|
-
const runtime = turned(zero, axis, 48)
|
|
159
|
-
const flipped = turned(zero, axis, -48)
|
|
160
|
-
|
|
161
|
-
const off = Math.hypot(swung.x - runtime.x, swung.y - runtime.y, swung.z - runtime.z)
|
|
162
|
-
const offFlipped = Math.hypot(swung.x - flipped.x, swung.y - flipped.y, swung.z - flipped.z)
|
|
163
|
-
|
|
164
|
-
assert.ok(off < 0.5, `${name}: 부채꼴이 ${off.toFixed(1)}mm 어긋난다 — 반대로 재면 ${offFlipped.toFixed(1)}mm`)
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
it('수평축 관절은 눕는다 — 도는 평면이 세로다', () => {
|
|
170
|
-
const part = gizmoPart(joint({ axis: { x: 1, y: 0, z: 0 } }), ARM, DRAFT)!
|
|
171
|
-
/* 씬은 라디안을 쓴다. z 90 도가 `rotationY` 로 간다(`figure-source.ts`). */
|
|
172
|
-
assert.equal(Math.round(((part.rotationY ?? 0) * 180) / Math.PI), 90)
|
|
173
|
-
})
|
|
174
|
-
})
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { describe, it } from 'node:test'
|
|
3
|
-
|
|
4
|
-
import { readFileSync } from 'node:fs'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
|
-
|
|
7
|
-
import { compile, validate } from '@hatiolab/figure-model'
|
|
8
|
-
import type { FigureSource } from '@hatiolab/figure-model'
|
|
9
|
-
|
|
10
|
-
import { jointNeedles, NEEDLE } from '../client/modeller/joint-gizmo.ts'
|
|
11
|
-
|
|
12
|
-
/*
|
|
13
|
-
* 미리보기에서 관절이 돌았다는 것을 보이게 하는 바늘.
|
|
14
|
-
*
|
|
15
|
-
* 이 시험이 지키는 것은 하나다 — **축에서 비켜나 있을 것.** 축 위에 붙으면 대칭인 부품과 같이
|
|
16
|
-
* 돌면서 같은 그림이 되고, 그러면 바늘을 붙인 뜻이 없어진다. 없는 것보다 나쁘다: 화면에 무엇이
|
|
17
|
-
* 하나 더 생겼는데 여전히 아무것도 안 움직인다.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
const source = (over: Partial<FigureSource> = {}): FigureSource => ({
|
|
21
|
-
version: 2,
|
|
22
|
-
type: 'ARM',
|
|
23
|
-
base: { x: 400, y: 1200, z: 400 },
|
|
24
|
-
placement: 'floor',
|
|
25
|
-
detailLevel: 'M',
|
|
26
|
-
parts: [
|
|
27
|
-
{
|
|
28
|
-
name: 'post',
|
|
29
|
-
primitive: 'cylinder',
|
|
30
|
-
transform: { position: { x: 0, y: 100, z: 0 }, size: { x: 200, y: 200, z: 200 } },
|
|
31
|
-
material: { token: 'palette.dark' }
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
name: 'arm',
|
|
35
|
-
primitive: 'cylinder',
|
|
36
|
-
transform: { position: { x: 0, y: 400, z: 0 }, size: { x: 120, y: 400, z: 120 } },
|
|
37
|
-
material: { token: 'palette.primary' },
|
|
38
|
-
parent: 'post'
|
|
39
|
-
}
|
|
40
|
-
],
|
|
41
|
-
joints: [
|
|
42
|
-
{
|
|
43
|
-
name: 'turn',
|
|
44
|
-
child: 'arm',
|
|
45
|
-
type: 'revolute',
|
|
46
|
-
origin: { x: 0, y: 200, z: 0 },
|
|
47
|
-
axis: { x: 0, y: 1, z: 0 },
|
|
48
|
-
limits: { min: -90, max: 90 }
|
|
49
|
-
}
|
|
50
|
-
],
|
|
51
|
-
...over
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
describe('관절 바늘', () => {
|
|
55
|
-
it('관절이 모는 부품에 자식으로 붙는다 — 그래야 관절이 도는 만큼 같이 돈다', () => {
|
|
56
|
-
const [needle] = jointNeedles(source())
|
|
57
|
-
|
|
58
|
-
assert.equal(needle!.parent, 'arm')
|
|
59
|
-
assert.equal(needle!.name, `${NEEDLE}turn`)
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('축에서 비켜나 선다 — 축 위에 있으면 대칭인 부품과 같은 그림이다', () => {
|
|
63
|
-
const [needle] = jointNeedles(source())
|
|
64
|
-
const { position, size } = needle!.transform
|
|
65
|
-
|
|
66
|
-
/* y 축으로 도는 관절: 바늘은 x 로 비켜나고 x 로 길다. */
|
|
67
|
-
assert.ok(position.x > 0, '축에서 비켜났다')
|
|
68
|
-
assert.equal(position.y, 200, '기준점 높이 그대로')
|
|
69
|
-
assert.ok(size.x > size.y && size.x > size.z, 'x 로 길다')
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('가로축으로 도는 관절은 z 로 비켜난다', () => {
|
|
73
|
-
const [needle] = jointNeedles(source({ joints: [{ ...source().joints![0]!, axis: { x: 1, y: 0, z: 0 } }] }))
|
|
74
|
-
const { position, size } = needle!.transform
|
|
75
|
-
|
|
76
|
-
assert.ok(position.z > 0)
|
|
77
|
-
assert.ok(size.z > size.x && size.z > size.y)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('세로축(z)으로 도는 관절은 x 로 비켜난다', () => {
|
|
81
|
-
const [needle] = jointNeedles(source({ joints: [{ ...source().joints![0]!, axis: { x: 0, y: 0, z: 1 } }] }))
|
|
82
|
-
assert.ok(needle!.transform.position.x > 0)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('비스듬한 축은 그리지 않는다 — 비켜날 쪽을 못 고른다', () => {
|
|
86
|
-
const oblique = source({ joints: [{ ...source().joints![0]!, axis: { x: 1, y: 1, z: 0 } }] })
|
|
87
|
-
assert.deepEqual(jointNeedles(oblique), [])
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('길이는 도는 부품에서 온다. 작은 부품에서도 보일 만큼은 된다', () => {
|
|
91
|
-
const [big] = jointNeedles(source())
|
|
92
|
-
assert.equal(big!.transform.size.x, Math.round(400 * 0.6))
|
|
93
|
-
|
|
94
|
-
const small = source({
|
|
95
|
-
parts: source().parts.map(part =>
|
|
96
|
-
part.name === 'arm'
|
|
97
|
-
? { ...part, transform: { ...part.transform, size: { x: 10, y: 10, z: 10 } } }
|
|
98
|
-
: part
|
|
99
|
-
)
|
|
100
|
-
})
|
|
101
|
-
assert.equal(jointNeedles(small)[0]!.transform.size.x, 60)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('굵기는 길이에 견준다 — 8mm 로 고정했더니 미리보기에서 한 픽셀이 안 됐다', () => {
|
|
105
|
-
const [needle] = jointNeedles(source())
|
|
106
|
-
const { size } = needle!.transform
|
|
107
|
-
|
|
108
|
-
/* 길이 240 짜리 막대는 48 로 두껍다. 부품이 작아도 6 밑으로는 안 내려간다. */
|
|
109
|
-
assert.equal(size.y, Math.round(size.x / 5))
|
|
110
|
-
assert.equal(size.z, size.y)
|
|
111
|
-
|
|
112
|
-
const small = source({
|
|
113
|
-
parts: source().parts.map(part =>
|
|
114
|
-
part.name === 'arm' ? { ...part, transform: { ...part.transform, size: { x: 10, y: 10, z: 10 } } } : part
|
|
115
|
-
)
|
|
116
|
-
})
|
|
117
|
-
assert.equal(jointNeedles(small)[0]!.transform.size.y, Math.max(6, Math.round(60 / 5)))
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('관절이 없으면 바늘도 없다', () => {
|
|
121
|
-
assert.deepEqual(jointNeedles(source({ joints: [] })), [])
|
|
122
|
-
assert.deepEqual(jointNeedles(source({ joints: undefined })), [])
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('바늘을 붙인 형식이 그대로 선다 — 미리보기가 이것을 compile 한다', () => {
|
|
126
|
-
const shown: FigureSource = { ...source(), parts: [...source().parts, ...jointNeedles(source())] }
|
|
127
|
-
|
|
128
|
-
assert.deepEqual(validate(shown).errors, [])
|
|
129
|
-
assert.doesNotThrow(() => compile(shown))
|
|
130
|
-
})
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
/*
|
|
134
|
-
* 바늘은 **미리보기에만** 있다. 저장 형식으로 새어 나가면 발행된 도형에 없던 막대가 생기고,
|
|
135
|
-
* 계약 판정에서는 부품이 하나 늘어난 것으로 읽힌다.
|
|
136
|
-
*/
|
|
137
|
-
describe('바늘이 미리보기 밖으로 안 나간다', () => {
|
|
138
|
-
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
139
|
-
|
|
140
|
-
it('미리보기가 붙여서 세운다', () => {
|
|
141
|
-
const preview = read('../client/modeller/figure-preview.ts')
|
|
142
|
-
|
|
143
|
-
assert.ok(/jointNeedles\(source\)/.test(preview), '붙인다')
|
|
144
|
-
assert.ok(/registerFigure\(compile\(shown\)/.test(preview), '붙인 것을 세운다')
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('저작면과 저장은 원본 그대로다', () => {
|
|
148
|
-
const page = read('../client/pages/figure-modeller-page.ts')
|
|
149
|
-
const canvas = read('../client/modeller/figure-canvas.ts')
|
|
150
|
-
|
|
151
|
-
assert.ok(!/jointNeedles/.test(page), '페이지는 바늘을 모른다')
|
|
152
|
-
assert.ok(!/jointNeedles/.test(canvas), '저작 캔버스는 바늘을 모른다')
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
it('원본 목록을 건드리지 않는다 — 같은 배열에 밀어 넣으면 저장에 들어간다', () => {
|
|
156
|
-
const original = source()
|
|
157
|
-
const before = original.parts.length
|
|
158
|
-
jointNeedles(original)
|
|
159
|
-
|
|
160
|
-
assert.equal(original.parts.length, before)
|
|
161
|
-
})
|
|
162
|
-
})
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { readFileSync } from 'node:fs'
|
|
3
|
-
import { test } from 'node:test'
|
|
4
|
-
import { fileURLToPath } from 'node:url'
|
|
5
|
-
|
|
6
|
-
test('상단 별도 헤더를 없애고 모드 툴바(div[modes])에 저장 및 실행 취소/다시 실행을 배치한다', () => {
|
|
7
|
-
const path = fileURLToPath(new URL('../client/pages/figure-modeller-page.ts', import.meta.url))
|
|
8
|
-
const source = readFileSync(path, 'utf8')
|
|
9
|
-
|
|
10
|
-
// 상단 <div header> 제거 검증
|
|
11
|
-
assert.equal(source.includes('<div header>'), false)
|
|
12
|
-
assert.equal(source.includes('button ai-support'), false)
|
|
13
|
-
|
|
14
|
-
// 모드 툴바(div[modes])에 실행 취소(undo), 다시 실행(redo) 버튼 배치 및 편집 모드 조건 검증
|
|
15
|
-
assert.match(source, /this\.mode === 'edit' && !this\.proposalSession/)
|
|
16
|
-
assert.match(source, /button\s+history[\s\S]*figure\.button\.undo[\s\S]*this\.undo\(\)/)
|
|
17
|
-
assert.match(source, /button\s+history[\s\S]*figure\.button\.redo[\s\S]*this\.redo\(\)/)
|
|
18
|
-
|
|
19
|
-
// 저장 버튼 및 미저장 표시(dirty)가 모드 툴바에 위치하는지 검증
|
|
20
|
-
assert.match(source, /<div modes>[\s\S]*button save[\s\S]*this\.save\(\)[\s\S]*<\/div>/)
|
|
21
|
-
assert.match(source, /<div modes>[\s\S]*span dirty[\s\S]*figure\.text\.unsaved-changes[\s\S]*<\/div>/)
|
|
22
|
-
|
|
23
|
-
// 실행 취소/다시 실행 메서드 및 상태 검증
|
|
24
|
-
assert.match(source, /private undoStack:/)
|
|
25
|
-
assert.match(source, /private redoStack:/)
|
|
26
|
-
assert.match(source, /private undo\(\)/)
|
|
27
|
-
assert.match(source, /private redo\(\)/)
|
|
28
|
-
assert.match(source, /private recordHistory\(/)
|
|
29
|
-
|
|
30
|
-
// 키보드 단축키(Cmd/Ctrl+Z, Cmd+Shift+Z / Ctrl+Y) 핸들러 검증
|
|
31
|
-
assert.match(source, /handleKeyDown = \(e: KeyboardEvent\)/)
|
|
32
|
-
assert.match(source, /e\.key === 'z' && !e\.shiftKey[\s\S]*this\.undo\(\)/)
|
|
33
|
-
})
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { test } from 'node:test'
|
|
3
|
-
import { shouldStartNewFigure } from '../client/modeller/new-figure-entry.ts'
|
|
4
|
-
|
|
5
|
-
test('entering new Figure replaces a previous unsaved draft', () => {
|
|
6
|
-
assert.equal(shouldStartNewFigure({ active: true }, true, ''), true)
|
|
7
|
-
})
|
|
8
|
-
test('removing resource ID replaces a saved model', () => {
|
|
9
|
-
assert.equal(shouldStartNewFigure({ resourceId: undefined }, true, 'saved'), true)
|
|
10
|
-
})
|
|
11
|
-
test('first entry starts empty but ordinary updates retain current work', () => {
|
|
12
|
-
assert.equal(shouldStartNewFigure({}, false), true)
|
|
13
|
-
assert.equal(shouldStartNewFigure({}, true, ''), false)
|
|
14
|
-
assert.equal(shouldStartNewFigure({ query: {} }, true, ''), false)
|
|
15
|
-
})
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { describe, it } from 'node:test'
|
|
3
|
-
|
|
4
|
-
import { poseAsDefaults, poseChanges } from '../client/modeller/pose-defaults.ts'
|
|
5
|
-
import type { FigureDraft } from '../client/modeller/figure-source.ts'
|
|
6
|
-
|
|
7
|
-
/*
|
|
8
|
-
* 미리보기에서 세운 자세를 기본값으로 앉힌다.
|
|
9
|
-
*
|
|
10
|
-
* 관절 도형의 휴지 자세(값 0)는 곧게 선 기둥이라, 기본값이 없으면 목록에서도 도면에서도 그
|
|
11
|
-
* 모양으로 선다. 옮겨 적는 일을 손으로 하면 여섯 축에 여섯 번이고, 옮기는 사이 화면의 자세와
|
|
12
|
-
* 적은 수가 갈린다.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const DRAFT: FigureDraft = {
|
|
16
|
-
version: 2,
|
|
17
|
-
figureType: 'ARM',
|
|
18
|
-
width: 900,
|
|
19
|
-
height: 900,
|
|
20
|
-
depth: 1900,
|
|
21
|
-
detailLevel: 'L',
|
|
22
|
-
parameters: [
|
|
23
|
-
{ name: 'shoulder', range: { unit: 'deg', min: -120, max: 120 }, default: 0, clip: { channels: [] } },
|
|
24
|
-
{ name: 'elbow', range: { unit: 'deg', min: -150, max: 150 }, default: 0, clip: { channels: [] } },
|
|
25
|
-
/* 기본값을 안 적은 파라미터. 형식은 그때 범위의 처음에 선다. */
|
|
26
|
-
{ name: 'hoist', range: { unit: 'mm', min: 0, max: 1800 }, clip: { channels: [] } }
|
|
27
|
-
]
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
describe('이 자세를 기본으로', () => {
|
|
31
|
-
it('민 것만 앉히고 나머지는 건드리지 않는다', () => {
|
|
32
|
-
const next = poseAsDefaults(DRAFT, { shoulder: -35 })
|
|
33
|
-
|
|
34
|
-
assert.equal(next.parameters![0]!.default, -35)
|
|
35
|
-
assert.equal(next.parameters![1], DRAFT.parameters![1], '안 민 파라미터는 같은 객체 그대로다')
|
|
36
|
-
assert.equal(next.parameters![2], DRAFT.parameters![2])
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('이미 그 값이면 초안을 그대로 돌려준다 — 고친 것이 없는데 「저장 안 됨」이 붙지 않게', () => {
|
|
40
|
-
assert.equal(poseAsDefaults(DRAFT, { shoulder: 0 }), DRAFT)
|
|
41
|
-
assert.equal(poseAsDefaults(DRAFT, {}), DRAFT)
|
|
42
|
-
/* 기본값을 안 적은 파라미터는 범위의 처음에 서 있다. 그 값을 다시 적지 않는다. */
|
|
43
|
-
assert.equal(poseAsDefaults(DRAFT, { hoist: 0 }), DRAFT)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('기본값을 안 적었던 파라미터에도 앉는다', () => {
|
|
47
|
-
const next = poseAsDefaults(DRAFT, { hoist: 1200 })
|
|
48
|
-
assert.equal(next.parameters![2]!.default, 1200)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('범위 밖은 끝에서 문다 — 다른 길로 들어온 값도 형식을 지나야 한다', () => {
|
|
52
|
-
const next = poseAsDefaults(DRAFT, { elbow: 400 })
|
|
53
|
-
assert.equal(next.parameters![1]!.default, 150)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('수가 아닌 값은 안 말한 것으로 친다', () => {
|
|
57
|
-
for (const bad of [NaN, Infinity, undefined as unknown as number]) {
|
|
58
|
-
assert.equal(poseAsDefaults(DRAFT, { shoulder: bad }), DRAFT, String(bad))
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('무엇이 달라지는지 먼저 말할 수 있다 — 단추를 띄울지 이것으로 정한다', () => {
|
|
63
|
-
assert.deepEqual(poseChanges(DRAFT, { shoulder: -35, elbow: 0 }), [{ name: 'shoulder', from: 0, to: -35 }])
|
|
64
|
-
assert.deepEqual(poseChanges(DRAFT, { shoulder: 0 }), [])
|
|
65
|
-
assert.deepEqual(poseChanges(undefined, { shoulder: -35 }), [])
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('없는 파라미터 이름은 무시한다 — 후보를 보다 남은 값이 섞여도 초안을 늘리지 않는다', () => {
|
|
69
|
-
assert.equal(poseAsDefaults(DRAFT, { 'no-such-thing': 42 }), DRAFT)
|
|
70
|
-
})
|
|
71
|
-
})
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { describe, it } from 'node:test'
|
|
3
|
-
|
|
4
|
-
import { previewBoardDepth } from '../client/modeller/figure-view.ts'
|
|
5
|
-
|
|
6
|
-
describe('preview board depth', () => {
|
|
7
|
-
it('a floor figure gets room for its tallest instance, not the ceiling', () => {
|
|
8
|
-
// AGV: base 460 tall, shown up to twice. The board was 4400 and the camera aimed at 2200.
|
|
9
|
-
assert.equal(previewBoardDepth({ placement: 'floor', base: { y: 460 } }, 4000, 2), 1012)
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('a figure with no placement stands on the floor', () => {
|
|
13
|
-
assert.equal(previewBoardDepth({ base: { y: 460 } }, 4000, 2), 1012)
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('a center figure has no default height, so it stands on the floor too', () => {
|
|
17
|
-
assert.equal(previewBoardDepth({ placement: 'center', base: { y: 460 } }, 4000, 2), 1012)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('a ceiling figure gets room up to the ceiling it hangs from', () => {
|
|
21
|
-
assert.equal(previewBoardDepth({ placement: 'ceiling', base: { y: 460 } }, 4000, 2), 4400)
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('a ceiling figure taller than the ceiling still fits', () => {
|
|
25
|
-
assert.equal(previewBoardDepth({ placement: 'ceiling', base: { y: 2500 } }, 4000, 2), 5500)
|
|
26
|
-
})
|
|
27
|
-
})
|