@things-factory/figure-ui 10.1.92 → 10.1.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/modeller/fasten-geometry.ts +152 -0
- package/client/modeller/figure-cavity-drawing.ts +259 -0
- package/client/modeller/figure-fasten-drawing.ts +406 -0
- package/client/modeller/figure-links.ts +71 -35
- package/client/modeller/figure-section-panel.ts +2 -0
- package/client/modeller/section-controls.ts +19 -0
- package/dist-client/modeller/fasten-geometry.d.ts +60 -0
- package/dist-client/modeller/fasten-geometry.js +138 -0
- package/dist-client/modeller/fasten-geometry.js.map +1 -0
- package/dist-client/modeller/figure-cavity-drawing.d.ts +48 -0
- package/dist-client/modeller/figure-cavity-drawing.js +253 -0
- package/dist-client/modeller/figure-cavity-drawing.js.map +1 -0
- package/dist-client/modeller/figure-fasten-drawing.d.ts +74 -0
- package/dist-client/modeller/figure-fasten-drawing.js +452 -0
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -0
- package/dist-client/modeller/figure-links.d.ts +7 -0
- package/dist-client/modeller/figure-links.js +70 -33
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-section-panel.d.ts +1 -0
- package/dist-client/modeller/figure-section-panel.js +2 -0
- package/dist-client/modeller/figure-section-panel.js.map +1 -1
- package/dist-client/modeller/section-controls.d.ts +10 -0
- package/dist-client/modeller/section-controls.js +17 -0
- package/dist-client/modeller/section-controls.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/test/browser/acceptance-page.js +10 -0
- package/test/cavity-drawing.test.ts +69 -0
- package/test/fasten-drawing.test.ts +45 -0
- package/test/fasten-geometry.test.ts +79 -0
- package/test/links.test.ts +3 -1
- package/translations/en.json +11 -1
- package/translations/ja.json +11 -1
- package/translations/ko.json +11 -1
- package/translations/ms.json +11 -1
- package/translations/zh.json +11 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-ui",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.93",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"@things-factory/figure-service": "^10.1.84",
|
|
45
45
|
"three": "^0.185.1"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "cec4765a5a0c3e7540a822c852110bec7f27250c"
|
|
48
48
|
}
|
|
@@ -56,6 +56,16 @@ export const MODELS = {
|
|
|
56
56
|
a = applyFigureAuthoring(a, { kind: 'attach', part: 'cap', face: 'bottom', to: { part: 'wedge', face: 'top' } })
|
|
57
57
|
return serializeFigureAsset(a)
|
|
58
58
|
},
|
|
59
|
+
/* A lid fastened to the top of a bin standing on the floor: the fastening drawn and dragged (user 2026-10-01). */
|
|
60
|
+
'lid-on-bin': () => {
|
|
61
|
+
let a = createFigureAsset('Lid on bin')
|
|
62
|
+
a.palette = { ...a.palette, ...PALETTE }
|
|
63
|
+
a = addFigureBox(a, { id: 'bin', size: { x: 600, y: 300, z: 400 }, position: { x: 0, y: 150, z: 0 } })
|
|
64
|
+
a = applyFigureAuthoring(a, { kind: 'attach', part: 'bin', face: 'bottom', to: { plane: 'mounting-plane' } })
|
|
65
|
+
a = addFigureBox(a, { id: 'lid', size: { x: 300, y: 20, z: 200 }, position: { x: 0, y: 0, z: 0 } })
|
|
66
|
+
a = applyFigureAuthoring(a, { kind: 'attach', part: 'lid', face: 'bottom', to: { part: 'bin', face: 'top' } })
|
|
67
|
+
return serializeFigureAsset(a)
|
|
68
|
+
},
|
|
59
69
|
/* A box and a drum: rounded corners and an inside from the properties tab (ADR-0100 stage 1). */
|
|
60
70
|
shapes: () => {
|
|
61
71
|
let a = createFigureAsset('Box and drum')
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **속 공간 그림** (사용자 2026-10-01: 「속공간 만들기도 시각적으로 편집할 수 있도록」).
|
|
3
|
+
*
|
|
4
|
+
* 벽 손잡이를 끄는 것, 끝을 눌러 여닫는 것이 figure-model 의 set-cavity 한 명령으로 받아들여지는지, 커널이 거절할 모양은
|
|
5
|
+
* 보내지 않고 그 까닭을 말하는지, 끄는 동안은 미리보기만 하고 놓을 때 한 번 보내는지를 잰다.
|
|
6
|
+
*/
|
|
7
|
+
import { test } from 'node:test'
|
|
8
|
+
import assert from 'node:assert/strict'
|
|
9
|
+
import { readFileSync } from 'node:fs'
|
|
10
|
+
import { fileURLToPath } from 'node:url'
|
|
11
|
+
|
|
12
|
+
import { addFigureBox, applyFigureAuthoring, compileFigureAsset, createFigureAsset } from '@hatiolab/figure-model'
|
|
13
|
+
import type { FigureAsset, FigureEvaluatedShape } from '@hatiolab/figure-model'
|
|
14
|
+
|
|
15
|
+
import { cavityRefusalOf, cavityResultOf, cavityWorkingOf, endToggled, wallDragged } from '../client/modeller/section-controls.ts'
|
|
16
|
+
|
|
17
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
18
|
+
const has = (source: string, re: RegExp, message?: string) => assert.ok(re.test(source), message ?? `missing: ${re}`)
|
|
19
|
+
const drawing = read('../client/modeller/figure-cavity-drawing.ts')
|
|
20
|
+
const panel = read('../client/modeller/figure-section-panel.ts')
|
|
21
|
+
|
|
22
|
+
const tote = (): FigureAsset => {
|
|
23
|
+
const box = addFigureBox(createFigureAsset('Tote'), { id: 'bin', size: { x: 600, y: 300, z: 400 }, position: { x: 0, y: 150, z: 0 } })
|
|
24
|
+
return applyFigureAuthoring(box, { kind: 'set-cavity', part: 'bin', cavity: { wall: 10, start: { closed: 15 }, end: 'open' } })
|
|
25
|
+
}
|
|
26
|
+
const shapeOf = (asset: FigureAsset) =>
|
|
27
|
+
(compileFigureAsset(asset).evaluate().geometry.find(g => (g as { id: string }).id === 'bin') as unknown as { shape: FigureEvaluatedShape }).shape
|
|
28
|
+
const send = (asset: FigureAsset, w: NonNullable<ReturnType<typeof cavityWorkingOf>>) =>
|
|
29
|
+
applyFigureAuthoring(asset, { kind: 'set-cavity', part: 'bin', cavity: { wall: w.wall, start: w.start, end: w.end } })
|
|
30
|
+
|
|
31
|
+
test('벽 손잡이: 한 벽을 끌면 네 벽이 같이, 면마다 벽이면 그 벽만 — 한 명령으로 들어간다', () => {
|
|
32
|
+
const a = tote()
|
|
33
|
+
const w = cavityWorkingOf(shapeOf(a))!
|
|
34
|
+
assert.deepEqual(w.wall, { 'x-': 10, 'x+': 10, 'z-': 10, 'z+': 10 })
|
|
35
|
+
const together = send(a, { ...w, wall: wallDragged(w.wall, 'x-', 24.6, false) })
|
|
36
|
+
assert.deepEqual(shapeOf(together).cavity!.wall, { 'x-': 25, 'x+': 25, 'z-': 25, 'z+': 25 })
|
|
37
|
+
const one = send(a, { ...w, wall: wallDragged(w.wall, 'z+', 40, true) })
|
|
38
|
+
assert.deepEqual(shapeOf(one).cavity!.wall, { 'x-': 10, 'x+': 10, 'z-': 10, 'z+': 40 })
|
|
39
|
+
assert.deepEqual(wallDragged(w.wall, 'x-', -5, false)['x-'], 1, 'never below 1 mm')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('끝을 누르면 여닫힌다: 위도 아래도 열면 관, 닫으면 기본 두께로', () => {
|
|
43
|
+
const a = tote()
|
|
44
|
+
const s = shapeOf(a)
|
|
45
|
+
const w = cavityWorkingOf(s)!
|
|
46
|
+
const pipe = send(a, { ...w, start: endToggled(s, w.start) })
|
|
47
|
+
assert.equal(cavityResultOf(shapeOf(pipe).cavity!), 'pipe')
|
|
48
|
+
const sealed = send(a, { ...w, end: endToggled(s, w.end) })
|
|
49
|
+
assert.equal(cavityResultOf(shapeOf(sealed).cavity!), 'sealed')
|
|
50
|
+
assert.deepEqual(shapeOf(sealed).cavity!.end, { closed: 40 }, 'the default wall: a tenth of the narrower side')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('커널이 거절할 모양은 보내지 않고 까닭을 말한다 — 끄는 동안은 미리보기, 놓을 때 한 번', () => {
|
|
54
|
+
const s = shapeOf(tote())
|
|
55
|
+
const w = cavityWorkingOf(s)!
|
|
56
|
+
assert.equal(cavityRefusalOf(s, { ...w, wall: wallDragged(w.wall, 'x-', 250, false) })?.code, 'wall-too-thick')
|
|
57
|
+
assert.equal(cavityRefusalOf(s, { ...w, start: { closed: 200 }, end: { closed: 150 } })?.code, 'ends-too-thick')
|
|
58
|
+
has(drawing, /this\.emit\('command-live', this\.refusal \? null : this\.action\(w\)\)/, 'a refused shape is not previewed')
|
|
59
|
+
has(drawing, /if \(this\.refusal\) \{\s*this\.emit\('command-live', null\)\s*return\s*\}\s*this\.emit\('command', this\.action\(w\)\)/, 'let go: one command, or nothing -- and the reason stays said')
|
|
60
|
+
has(drawing, /this\.refusal = this\.judge\(next\)\s*if \(!this\.refusal\) this\.emit\('command', this\.action\(next\)\)/, 'a press toggles by one command')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('단면 칸에 그림이 들어간다: 속 공간이 있을 때, 면마다 벽 토글을 따른다', () => {
|
|
64
|
+
has(panel, /cavity \? html`<figure-cavity-drawing \.body=\$\{body\} \.perSide=\$\{this\.perSide\} \?locked=\$\{this\.locked\}><\/figure-cavity-drawing>` : nothing/)
|
|
65
|
+
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) {
|
|
66
|
+
const t = JSON.parse(read(`../translations/${lang}.json`)) as Record<string, string>
|
|
67
|
+
for (const key of ['figure.label.section-cavity-top', 'figure.label.section-cavity-side']) assert.ok(t[key], `${lang} ${key}`)
|
|
68
|
+
}
|
|
69
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **붙이기 그림이 폼에 들어간 모습** (사용자 2026-10-01).
|
|
3
|
+
*
|
|
4
|
+
* 부품의 면은 고르지 않는다. 상대(부품 또는 배치 기준의 면)와 그 면, 방식만 그림으로 고른다. 이미 붙은 부품은 끌기를 놓으면
|
|
5
|
+
* 그 자리로 한 번 고쳐지고, 새로 붙이는 부품은 「붙이기」를 기다린다. 화면에 풀이 문구를 두지 않는다.
|
|
6
|
+
*/
|
|
7
|
+
import { test } from 'node:test'
|
|
8
|
+
import assert from 'node:assert/strict'
|
|
9
|
+
import { readFileSync } from 'node:fs'
|
|
10
|
+
import { fileURLToPath } from 'node:url'
|
|
11
|
+
|
|
12
|
+
const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
|
|
13
|
+
const has = (source: string, re: RegExp, message?: string) => assert.ok(re.test(source), message ?? `missing: ${re}`)
|
|
14
|
+
const form = read('../client/modeller/figure-links.ts')
|
|
15
|
+
const drawing = read('../client/modeller/figure-fasten-drawing.ts')
|
|
16
|
+
const ko = JSON.parse(read('../translations/ko.json')) as Record<string, string>
|
|
17
|
+
const say = (key: string, params: Record<string, string>) => Object.entries(params).reduce((s, [k, v]) => s.replace(`{${k}}`, v), ko[key] ?? key)
|
|
18
|
+
|
|
19
|
+
test('부품의 면·맞닿기 고르기 칸이 없고, 그 자리에 그림이 있다', () => {
|
|
20
|
+
assert.ok(!form.includes("i18next.t('figure.label.my-face')"), 'no list of the part\'s own faces')
|
|
21
|
+
assert.ok(!form.includes("i18next.t('figure.label.facing')"), 'no facing list')
|
|
22
|
+
has(form, /<figure-fasten-drawing[\s\S]*?\.modes=\$\{f\.toPlane \? \(\['meet'\] as FastenMode\[\]\)/, 'the plane is set on only')
|
|
23
|
+
has(form, /turnsInPlace\(asset, this\.partId\) \? \(\['centre'\] as FastenMode\[\]\) : \[\]/, 'centre on it only for a part that turns')
|
|
24
|
+
has(form, /const derived = f\.toPlane \|\| f\.target \? myFaceFor\(asset, this\.partId, aim, mode\) : undefined/, 'the part\'s face follows the target and how')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('상대 목록에 배치 기준의 면이 이름으로 있다: 바닥 · 천장 · 벽 · 중심', () => {
|
|
28
|
+
has(form, /const planeWord = i18next\.t\('figure\.label\.resize-place-' \+ \(asset\.placement \?\? 'floor'\)\)/)
|
|
29
|
+
has(form, /<option value=\$\{PLANE\} \?selected=\$\{f\.toPlane\}>\$\{planeWord\}<\/option>/)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('놓으면: 이미 붙은 부품은 한 번 고쳐지고, 새 부품은 붙이기를 기다린다', () => {
|
|
33
|
+
has(form, /private async onFastenRelease\(held: boolean\) \{\s*if \(!held\) return\s*await this\.updateComplete\s*if \(this\.fastenCandidate\) this\.command\(this\.fastenCandidate\.action\)/)
|
|
34
|
+
has(drawing, /private drop\(\) \{[\s\S]*?this\.released\(\)\s*\}/, 'a drag ends in one release')
|
|
35
|
+
has(drawing, /this\.change\(\{ targetFace: f \}\)\s*this\.released\(\)/, 'a face pressed is one change')
|
|
36
|
+
has(drawing, /this\.change\(\{ mode: m \}\)\s*this\.released\(\)/, 'a way pressed is one change')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('화면 문구는 결과와 예외만 — 한 줄 요약, 가장자리 밖, 닿지 않음', () => {
|
|
40
|
+
assert.equal(say('figure.text.fasten-sentence-meet', { part: 'lid', face: '아랫면', to: 'bin의 윗면' }), 'lid의 아랫면을 bin의 윗면에 맞닿게')
|
|
41
|
+
assert.equal(say('figure.text.fasten-sentence-centre', { part: 'roller', face: '', to: 'frame의 앞면' }), 'roller의 가운데를 frame의 앞면 위에')
|
|
42
|
+
for (const key of ['figure.text.fasten-off-face', 'figure.text.fasten-past-edges', 'figure.label.fasten-mode-meet', 'figure.label.fasten-mode-flush', 'figure.label.fasten-mode-centre'])
|
|
43
|
+
for (const lang of ['ko', 'en', 'ja', 'zh', 'ms']) assert.ok((JSON.parse(read(`../translations/${lang}.json`)) as Record<string, string>)[key], `${lang} ${key}`)
|
|
44
|
+
assert.ok(!/끌어 옮깁니다|면 밖으로도/.test(JSON.stringify(ko)), 'no instructions on the screen')
|
|
45
|
+
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **붙이기 그림이 읽는 것** (사용자 2026-10-01: 「면을 선택하는 것이 복잡해보인다」).
|
|
3
|
+
*
|
|
4
|
+
* 붙이기는 상대 면과 방식(올려놓기 · 같은 면 · 가운데를 면에)만 고른다. 부품의 면은 거기서 정해진다 — 돌려 놓인 부품이면
|
|
5
|
+
* 지금 그 방향을 보는 면이다. 면 위에 놓은 자리는 가운데·안쪽 끝·바깥 끝에 붙고, 그 밖은 mm 다. figure-model 이 그 명령을
|
|
6
|
+
* 받아 부품을 정말 그 자리에 두는지까지 잰다.
|
|
7
|
+
*/
|
|
8
|
+
import { test } from 'node:test'
|
|
9
|
+
import assert from 'node:assert/strict'
|
|
10
|
+
|
|
11
|
+
import { addFigureBox, applyFigureAuthoring, compileFigureAsset, createFigureAsset, figurePartFaceOf } from '@hatiolab/figure-model'
|
|
12
|
+
import type { FigureAsset, FigureAuthoringAction } from '@hatiolab/figure-model'
|
|
13
|
+
|
|
14
|
+
import { alignmentAt, faceViewOf, myFaceFor, offsetOf, overhangOf, screenOf } from '../client/modeller/fasten-geometry.ts'
|
|
15
|
+
|
|
16
|
+
const scene = (lidRotation?: { x: number; y: number; z: number }): FigureAsset => {
|
|
17
|
+
let a = addFigureBox(createFigureAsset('Box'), { id: 'bin', size: { x: 600, y: 300, z: 400 }, position: { x: 0, y: 150, z: 0 } })
|
|
18
|
+
a = applyFigureAuthoring(a, { kind: 'attach', part: 'bin', face: 'bottom', to: { plane: 'mounting-plane' } })
|
|
19
|
+
return addFigureBox(a, { id: 'lid', size: { x: 300, y: 20, z: 200 }, position: { x: 0, y: 400, z: 0 }, ...(lidRotation ? { rotation: lidRotation } : {}) })
|
|
20
|
+
}
|
|
21
|
+
const at = (a: FigureAsset, id: string) => (compileFigureAsset(a).evaluate().geometry.find(g => (g as { id: string }).id === id) as unknown as { pose: { t: number[] } }).pose.t
|
|
22
|
+
|
|
23
|
+
test('부품의 면은 상대 면과 방식으로 정해진다: 올려놓기는 마주 보는 면, 같은 면은 같은 면, 가운데는 가운데', () => {
|
|
24
|
+
const a = scene()
|
|
25
|
+
assert.equal(myFaceFor(a, 'lid', { part: 'bin', face: 'top' }, 'meet'), 'bottom')
|
|
26
|
+
assert.equal(myFaceFor(a, 'lid', { part: 'bin', face: 'top' }, 'flush'), 'top')
|
|
27
|
+
assert.equal(myFaceFor(a, 'lid', { part: 'bin', face: 'front' }, 'meet'), 'back')
|
|
28
|
+
assert.equal(myFaceFor(a, 'lid', { part: 'bin', face: 'top' }, 'centre'), 'centre')
|
|
29
|
+
assert.equal(myFaceFor(a, 'lid', 'plane', 'meet'), 'bottom', 'on a floor: the face looking down')
|
|
30
|
+
assert.equal(myFaceFor(a, 'lid', 'plane', 'centre'), undefined, 'the plane takes a face, never a centre')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('돌려 놓인 부품이면 지금 아래를 보는 면이다 — 이름이 아니라 방향으로', () => {
|
|
34
|
+
const a = scene({ x: 90, y: 0, z: 0 })
|
|
35
|
+
const face = myFaceFor(a, 'lid', { part: 'bin', face: 'top' }, 'meet')!
|
|
36
|
+
assert.notEqual(face, 'bottom', 'turned a quarter about X, its bottom no longer looks down')
|
|
37
|
+
const n = figurePartFaceOf(a, 'lid', face)!.normal
|
|
38
|
+
assert.ok(Math.abs(n[1]! + 1) < 1e-6, `${face} looks down: ${n}`)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('면 그림: 상대 면의 크기와 부품 발자국, 가장자리 맞춤이 되는 축', () => {
|
|
42
|
+
const v = faceViewOf(scene(), 'lid', { part: 'bin', face: 'top' })!
|
|
43
|
+
assert.deepEqual(v.axes, ['x', 'z'])
|
|
44
|
+
assert.deepEqual([v.face.x, v.face.z], [600, 400])
|
|
45
|
+
assert.deepEqual([v.part.x, v.part.z], [300, 200])
|
|
46
|
+
assert.deepEqual(v.edges, { x: true, z: true })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('놓은 자리: 가운데, 안쪽 끝(가장자리와 나란히), 바깥 끝(가장자리에 닿게), 그 밖은 mm', () => {
|
|
50
|
+
assert.deepEqual(alignmentAt(4, 600, 300, true, 10), { how: 'centre', caught: 'centre' })
|
|
51
|
+
assert.deepEqual(alignmentAt(146, 600, 300, true, 10), { how: 'max', caught: 'inside' })
|
|
52
|
+
assert.deepEqual(alignmentAt(-150, 600, 300, false, 10), { how: 'mm', mm: -150, caught: 'inside' }, 'no edges to line up: a distance that sits there')
|
|
53
|
+
assert.deepEqual(alignmentAt(452, 600, 300, true, 10), { how: 'mm', mm: 450, caught: 'outside' })
|
|
54
|
+
assert.deepEqual(alignmentAt(212.4, 600, 300, true, 10), { how: 'mm', mm: 212 })
|
|
55
|
+
assert.equal(offsetOf('max', undefined, 600, 300), 150)
|
|
56
|
+
assert.equal(offsetOf('mm', 212, 600, 300), 212)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('면 밖으로 나온 곳과 닿지 않는 것을 말한다', () => {
|
|
60
|
+
const v = faceViewOf(scene(), 'lid', { part: 'bin', face: 'top' })!
|
|
61
|
+
assert.deepEqual(overhangOf(v, { x: 0, y: 0, z: 0 }), { touching: true, sides: [] })
|
|
62
|
+
assert.deepEqual(overhangOf(v, { x: 200, y: 0, z: 0 }), { touching: true, sides: [{ axis: 'x', side: 'high', mm: 50 }] })
|
|
63
|
+
assert.equal(overhangOf(v, { x: 460, y: 0, z: 0 }).touching, false)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('그 자리가 명령이 되면 figure-model 이 부품을 정말 거기 둔다: 오른쪽 끝, 같은 면, 바깥 끝', () => {
|
|
67
|
+
const a = scene()
|
|
68
|
+
const attach = (extra: Partial<Extract<FigureAuthoringAction, { kind: 'attach' }>>) =>
|
|
69
|
+
applyFigureAuthoring(a, { kind: 'attach', part: 'lid', face: 'bottom', to: { part: 'bin', face: 'top' }, ...extra } as FigureAuthoringAction)
|
|
70
|
+
assert.deepEqual(at(attach({ align: { x: 'max' } }), 'lid').map(Math.round), [150, 310, 0])
|
|
71
|
+
assert.deepEqual(at(attach({ face: 'top', facing: 'flush' }), 'lid').map(Math.round), [0, 290, 0])
|
|
72
|
+
assert.deepEqual(at(attach({ align: { x: { mm: 450 } } }), 'lid').map(Math.round), [450, 310, 0], 'past the edge: fastened all the same')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('면은 서 있는 대로 보인다: 앞면은 X 가로·Y 위로, 옆면은 Z 가로·Y 위로, 윗면은 X 가로·Z 아래로', () => {
|
|
76
|
+
assert.deepEqual(screenOf(['x', 'y']), { h: 'x', v: 'y', sign: { x: 1, y: -1, z: 1 } })
|
|
77
|
+
assert.deepEqual(screenOf(['y', 'z']), { h: 'z', v: 'y', sign: { x: 1, y: -1, z: 1 } })
|
|
78
|
+
assert.deepEqual(screenOf(['x', 'z']), { h: 'x', v: 'z', sign: { x: 1, y: 1, z: 1 } })
|
|
79
|
+
})
|
package/test/links.test.ts
CHANGED
|
@@ -331,7 +331,9 @@ test('붙여도 제자리 — 폼은 지금 서 있는 자리를 가운데서
|
|
|
331
331
|
row = applyFigureAuthoring(row, { kind: 'attach', part: 'cap', face: 'bottom', to: { part: 'roller', face: 'top' } })
|
|
332
332
|
assert.deepEqual([hangsFrom(row, 'roller', 'plate'), hangsFrom(row, 'cap', 'plate'), hangsFrom(row, 'plate', 'roller')], [true, true, false])
|
|
333
333
|
const form = read('../client/modeller/figure-links.ts')
|
|
334
|
-
|
|
334
|
+
/* The target (a part or the plane, user 2026-10-01) and its face: the form is filled in whenever either changes. */
|
|
335
|
+
assert.ok(form.includes("this.aimAt(asset, value === PLANE ? { ...f, toPlane: true, target: '' } : { ...f, toPlane: false, target: value })"), '상대를 바꿀 때마다')
|
|
336
|
+
assert.ok(form.includes("this.aimAt(asset, { ...f, targetFace: c.targetFace as FigureFace })"), '상대 면을 바꿀 때마다')
|
|
335
337
|
assert.ok(!form.includes("figure.label.stands-on-plane"), '부품 붙이기 폼에 모델 설치 기준을 섞지 않는다')
|
|
336
338
|
/* Kept in the list, not choosable, with the reason (ADR-0097 §5.5). */
|
|
337
339
|
assert.ok(form.includes('const loops = (one: string) => hangsFrom(asset, one, this.partId)'))
|
package/translations/en.json
CHANGED
|
@@ -949,5 +949,15 @@
|
|
|
949
949
|
"figure.button.section-editor-discard": "discard and close",
|
|
950
950
|
"figure.text.section-points": "{points} points",
|
|
951
951
|
"figure.text.section-points-apex": "{points} points · one apex",
|
|
952
|
-
"figure.label.mirror-copy": "mirror copy"
|
|
952
|
+
"figure.label.mirror-copy": "mirror copy",
|
|
953
|
+
"figure.label.section-cavity-top": "the inside seen along the push",
|
|
954
|
+
"figure.label.section-cavity-side": "cut along the push",
|
|
955
|
+
"figure.label.fasten-mode-meet": "set on it",
|
|
956
|
+
"figure.label.fasten-mode-flush": "flush with it",
|
|
957
|
+
"figure.label.fasten-mode-centre": "centre on it",
|
|
958
|
+
"figure.text.fasten-off-face": "It does not touch {face}.",
|
|
959
|
+
"figure.text.fasten-past-edges": "It comes past the edges: {sides}",
|
|
960
|
+
"figure.text.fasten-sentence-meet": "{part}'s {face} set on {to}",
|
|
961
|
+
"figure.text.fasten-sentence-flush": "{part}'s {face} flush with {to}",
|
|
962
|
+
"figure.text.fasten-sentence-centre": "{part}'s centre on {to}"
|
|
953
963
|
}
|
package/translations/ja.json
CHANGED
|
@@ -949,5 +949,15 @@
|
|
|
949
949
|
"figure.button.section-editor-discard": "破棄して閉じる",
|
|
950
950
|
"figure.text.section-points": "点 {points} 個",
|
|
951
951
|
"figure.text.section-points-apex": "点 {points} 個 · 頂点 1 つ",
|
|
952
|
-
"figure.label.mirror-copy": "対称コピー"
|
|
952
|
+
"figure.label.mirror-copy": "対称コピー",
|
|
953
|
+
"figure.label.section-cavity-top": "押し出し方向から見た内部",
|
|
954
|
+
"figure.label.section-cavity-side": "押し出し方向の断面",
|
|
955
|
+
"figure.label.fasten-mode-meet": "載せる",
|
|
956
|
+
"figure.label.fasten-mode-flush": "同じ面に合わせる",
|
|
957
|
+
"figure.label.fasten-mode-centre": "中心を面に",
|
|
958
|
+
"figure.text.fasten-off-face": "{face}に接していません。",
|
|
959
|
+
"figure.text.fasten-past-edges": "縁からはみ出しています: {sides}",
|
|
960
|
+
"figure.text.fasten-sentence-meet": "{part}の{face}を{to}に接して",
|
|
961
|
+
"figure.text.fasten-sentence-flush": "{part}の{face}を{to}と同じ面に",
|
|
962
|
+
"figure.text.fasten-sentence-centre": "{part}の中心を{to}の上に"
|
|
953
963
|
}
|
package/translations/ko.json
CHANGED
|
@@ -949,5 +949,15 @@
|
|
|
949
949
|
"figure.button.section-editor-discard": "버리고 닫기",
|
|
950
950
|
"figure.text.section-points": "점 {points}개",
|
|
951
951
|
"figure.text.section-points-apex": "점 {points}개 · 꼭짓점 하나",
|
|
952
|
-
"figure.label.mirror-copy": "대칭 복사"
|
|
952
|
+
"figure.label.mirror-copy": "대칭 복사",
|
|
953
|
+
"figure.label.section-cavity-top": "위에서 본 속 공간",
|
|
954
|
+
"figure.label.section-cavity-side": "옆에서 자른 모습",
|
|
955
|
+
"figure.label.fasten-mode-meet": "올려놓기",
|
|
956
|
+
"figure.label.fasten-mode-flush": "같은 면에 맞추기",
|
|
957
|
+
"figure.label.fasten-mode-centre": "가운데를 면에",
|
|
958
|
+
"figure.text.fasten-off-face": "{face}과 닿지 않습니다.",
|
|
959
|
+
"figure.text.fasten-past-edges": "가장자리 밖으로 나와 있습니다: {sides}",
|
|
960
|
+
"figure.text.fasten-sentence-meet": "{part}의 {face}을 {to}에 맞닿게",
|
|
961
|
+
"figure.text.fasten-sentence-flush": "{part}의 {face}을 {to}과 같은 면으로",
|
|
962
|
+
"figure.text.fasten-sentence-centre": "{part}의 가운데를 {to} 위에"
|
|
953
963
|
}
|
package/translations/ms.json
CHANGED
|
@@ -949,5 +949,15 @@
|
|
|
949
949
|
"figure.button.section-editor-discard": "buang dan tutup",
|
|
950
950
|
"figure.text.section-points": "{points} titik",
|
|
951
951
|
"figure.text.section-points-apex": "{points} titik · satu puncak",
|
|
952
|
-
"figure.label.mirror-copy": "salinan cermin"
|
|
952
|
+
"figure.label.mirror-copy": "salinan cermin",
|
|
953
|
+
"figure.label.section-cavity-top": "bahagian dalam dilihat sepanjang tolakan",
|
|
954
|
+
"figure.label.section-cavity-side": "keratan sepanjang tolakan",
|
|
955
|
+
"figure.label.fasten-mode-meet": "letak di atas",
|
|
956
|
+
"figure.label.fasten-mode-flush": "sama rata",
|
|
957
|
+
"figure.label.fasten-mode-centre": "tengah pada muka",
|
|
958
|
+
"figure.text.fasten-off-face": "Ia tidak menyentuh {face}.",
|
|
959
|
+
"figure.text.fasten-past-edges": "Ia melepasi tepi: {sides}",
|
|
960
|
+
"figure.text.fasten-sentence-meet": "{face} {part} diletakkan pada {to}",
|
|
961
|
+
"figure.text.fasten-sentence-flush": "{face} {part} sama rata dengan {to}",
|
|
962
|
+
"figure.text.fasten-sentence-centre": "Tengah {part} pada {to}"
|
|
953
963
|
}
|
package/translations/zh.json
CHANGED
|
@@ -949,5 +949,15 @@
|
|
|
949
949
|
"figure.button.section-editor-discard": "放弃并关闭",
|
|
950
950
|
"figure.text.section-points": "{points} 个点",
|
|
951
951
|
"figure.text.section-points-apex": "{points} 个点 · 一个顶点",
|
|
952
|
-
"figure.label.mirror-copy": "对称复制"
|
|
952
|
+
"figure.label.mirror-copy": "对称复制",
|
|
953
|
+
"figure.label.section-cavity-top": "沿拉伸方向看内部",
|
|
954
|
+
"figure.label.section-cavity-side": "沿拉伸方向剖切",
|
|
955
|
+
"figure.label.fasten-mode-meet": "放在上面",
|
|
956
|
+
"figure.label.fasten-mode-flush": "与面齐平",
|
|
957
|
+
"figure.label.fasten-mode-centre": "中心在面上",
|
|
958
|
+
"figure.text.fasten-off-face": "未接触{face}。",
|
|
959
|
+
"figure.text.fasten-past-edges": "超出边缘: {sides}",
|
|
960
|
+
"figure.text.fasten-sentence-meet": "{part}的{face}贴在{to}上",
|
|
961
|
+
"figure.text.fasten-sentence-flush": "{part}的{face}与{to}齐平",
|
|
962
|
+
"figure.text.fasten-sentence-centre": "{part}的中心在{to}上"
|
|
953
963
|
}
|