@things-factory/figure-service 10.1.72 → 10.1.74
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/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/recipes/roller-row/build.mts +1 -6
- package/recipes/roller-row/facts.md +1 -1
- package/recipes/roller-row/guide.en.md +0 -1
- package/recipes/roller-row/guide.ko.md +0 -1
- package/recipes/roller-row/models/default.json +1 -1
- package/recipes/roller-row/recipe.json +0 -49
- package/recipes/roller-row/thumbnail.png +0 -0
- package/scripts/templates.test.mts +25 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/figure-service",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.74",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"@things-factory:registry": "https://registry.npmjs.org"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"test:templates": "node --test scripts/templates.test.mts"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@hatiolab/figure-model": "^0.1.
|
|
28
|
+
"@hatiolab/figure-model": "^0.1.90",
|
|
29
29
|
"@operato/typeorm-history": "^10.0.0",
|
|
30
30
|
"@things-factory/ai-client-base": "^10.1.53",
|
|
31
31
|
"@things-factory/auth-base": "^10.1.47",
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"graphql-upload": "^15.0.2",
|
|
36
36
|
"koa-router": "^7.4.0"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "59d5114dc91f6dd746f114bc047f07d70313c776"
|
|
39
39
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* 롤러 줄 — the model: rollers across the host's depth on its top, as many as fit along its length at the pitch, all
|
|
1
|
+
/* 롤러 줄 — the model: rollers across the host's depth on its top, as many as fit along its length at the pitch, all turning on one control. */
|
|
2
2
|
import { host, Model } from '../_lib/model.mts'
|
|
3
3
|
|
|
4
4
|
export default function build() {
|
|
@@ -8,11 +8,6 @@ export default function build() {
|
|
|
8
8
|
/* By its centre, a radius above the top: it spins in place (ADR-0093, 2026-09-26). */
|
|
9
9
|
.do({ kind: 'attach', part: 'roller', face: 'centre', to: { part: 'host', face: 'top' }, gap: 30 } as never)
|
|
10
10
|
.moves('roller', { type: 'revolute', axis: 'Z', lower: -360, upper: 360, state: 'spin' })
|
|
11
|
-
/* A stripe along each roller, so its turning shows: a plain cylinder looks the same at any angle. */
|
|
12
|
-
.box('stripe', 12, 20, 720)
|
|
13
|
-
.follow('stripe', 'z', 'roller', 'y')
|
|
14
|
-
.on('stripe', 'right', 'roller', 'left')
|
|
15
|
-
.colour('stripe', 'stripe', '#2f3437')
|
|
16
11
|
.do({ kind: 'make-repeat', part: 'roller', axis: 'x', pitch: 200, narrowRange: true })
|
|
17
12
|
return m.asset
|
|
18
13
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
## 붙는 면 · 따라가는 크기
|
|
14
14
|
- 고른 부품의 윗면(top)에, 부품의 X 방향으로 한 줄.
|
|
15
15
|
- 롤러 길이 = 고른 부품 깊이(Z) × 0.9. 롤러 수 = 부품 길이에 간격마다 들어가는 만큼(최대 100개).
|
|
16
|
-
- 롤러는 가운데(centre)로 붙고 틈 = 반지름이라 윗면에 얹힌다. 회전 Z −360~360, 조작값 spin 하나로 모든 복제가 돈다.
|
|
16
|
+
- 롤러는 가운데(centre)로 붙고 틈 = 반지름이라 윗면에 얹힌다. 회전 Z −360~360, 조작값 spin 하나로 모든 복제가 돈다.
|
|
17
17
|
|
|
18
18
|
## 알려진 한계
|
|
19
19
|
- 롤러는 X 방향으로만 줄을 선다.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"figure-v3-asset-1","name":"TPL_ROLLER_ROW","document":{"version":"figure-v3-prototype-1","model":{"version":"r3-spatial-1","inputs":[{"id":"host.size.x","unit":"mm","min":5e-324,"max":1.7976931348623157e+308,"role":"design"},{"id":"host.size.y","unit":"mm","min":5e-324,"max":1.7976931348623157e+308,"role":"design"},{"id":"host.size.z","unit":"mm","min":5e-324,"max":1.7976931348623157e+308,"role":"design"},{"id":"roller.dimension.radius","unit":"mm","min":0,"max":1.7976931348623157e+308,"role":"design"},{"id":"
|
|
1
|
+
{"version":"figure-v3-asset-1","name":"TPL_ROLLER_ROW","document":{"version":"figure-v3-prototype-1","model":{"version":"r3-spatial-1","inputs":[{"id":"host.size.x","unit":"mm","min":5e-324,"max":1.7976931348623157e+308,"role":"design"},{"id":"host.size.y","unit":"mm","min":5e-324,"max":1.7976931348623157e+308,"role":"design"},{"id":"host.size.z","unit":"mm","min":5e-324,"max":1.7976931348623157e+308,"role":"design"},{"id":"roller.dimension.radius","unit":"mm","min":0,"max":1.7976931348623157e+308,"role":"design"},{"id":"spin","unit":"deg","min":-360,"max":360,"role":"state"}],"constants":[{"id":"d.mm.0","unit":"mm","value":0},{"id":"a.deg.0","unit":"deg","value":0},{"id":"k.ratio.0_5","unit":"ratio","value":0.5},{"id":"k.ratio.1","unit":"ratio","value":1},{"id":"k.ratio.0_9","unit":"ratio","value":0.9},{"id":"a.deg.90","unit":"deg","value":90},{"id":"d.mm.30","unit":"mm","value":30},{"id":"axis.ratio.0","unit":"ratio","value":0},{"id":"k.ratio._0_5","unit":"ratio","value":-0.5},{"id":"limit.count.100","unit":"count","value":100},{"id":"d.mm.200","unit":"mm","value":200}],"nodes":[{"id":"host.shape","op":"box-shape@1","args":["host.size.x","host.size.y","host.dimension.z.value"],"outputs":{"shape":"host.shapeValue"},"params":{"frame":"host.local"}},{"id":"host.pose","op":"rigid@1","args":["d.mm.0","host.on.plane.stand.y.1.sum.value","d.mm.0","a.deg.0","a.deg.0","a.deg.0"],"outputs":{"pose":"host.poseValue"},"params":{"from":"host.local","to":"host.seat"}},{"id":"host","op":"place@1","args":["host.shapeValue","host.chain.value"],"outputs":{"placed":"host.placed"}},{"id":"roller.shape","op":"cylinder-shape@1","args":["roller.dimension.radius","roller.dimension.y.value"],"outputs":{"shape":"roller.shapeValue"},"params":{"frame":"roller.local"}},{"id":"roller.pose","op":"rigid@1","args":["d.mm.0","d.mm.0","d.mm.0","a.deg.90","a.deg.0","a.deg.0"],"outputs":{"pose":"roller.poseValue"},"params":{"from":"roller.local","to":"roller.seat"}},{"id":"roller","op":"member@1","args":["roller.shapeValue","roller.turned.value"],"outputs":{"placed":"roller.placed"}},{"id":"roller.dimension.y","op":"mul@1","args":["roller.y.follows.times.value","k.ratio.1"],"outputs":{"value":"roller.dimension.y.value"}},{"id":"host.dimension.z","op":"mul@1","args":["host.size.z","k.ratio.1"],"outputs":{"value":"host.dimension.z.value"}},{"id":"roller.y.follows.times","op":"mul@1","args":["host.dimension.z.value","k.ratio.0_9"],"outputs":{"value":"roller.y.follows.times.value"}},{"id":"host.on.plane.stand.y.0.times","op":"mul@1","args":["host.size.y","k.ratio.0_5"],"outputs":{"value":"host.on.plane.stand.y.0.times.value"}},{"id":"host.on.plane.stand.y.1.sum","op":"add@1","args":["host.on.plane.stand.y.0.times.value","d.mm.0"],"outputs":{"value":"host.on.plane.stand.y.1.sum.value"}},{"id":"host.seat.rigid","op":"rigid@1","args":["d.mm.0","d.mm.0","d.mm.0","a.deg.0","a.deg.0","a.deg.0"],"outputs":{"pose":"host.seat.value"},"params":{"from":"host.seat","to":"asset"}},{"id":"host.chain","op":"compose@1","args":["host.seat.value","host.poseValue"],"outputs":{"pose":"host.chain.value"}},{"id":"roller.on.host.seat.y.0.times","op":"mul@1","args":["host.size.y","k.ratio.0_5"],"outputs":{"value":"roller.on.host.seat.y.0.times.value"}},{"id":"roller.on.host.seat.y.1.sum","op":"add@1","args":["roller.on.host.seat.y.0.times.value","d.mm.30"],"outputs":{"value":"roller.on.host.seat.y.1.sum.value"}},{"id":"roller.seat.rigid","op":"rigid@1","args":["roller.row.start.0.times.value","roller.on.host.seat.y.1.sum.value","d.mm.0","a.deg.0","a.deg.0","a.deg.0"],"outputs":{"pose":"roller.seat.value"},"params":{"from":"roller.joint","to":"host.local"}},{"id":"roller.motion","op":"axis-turn@1","args":["axis.ratio.0","axis.ratio.0","k.ratio.1","spin"],"outputs":{"pose":"roller.motion.value"},"params":{"from":"roller.seat","to":"roller.joint"}},{"id":"roller.turned","op":"compose@1","args":["roller.motion.value","roller.poseValue"],"outputs":{"pose":"roller.turned.value"}},{"id":"roller.row.start.0.times","op":"mul@1","args":["host.size.x","k.ratio._0_5"],"outputs":{"value":"roller.row.start.0.times.value"}},{"id":"roller.row.length.0.times","op":"mul@1","args":["host.size.x","k.ratio.0_5"],"outputs":{"value":"roller.row.length.0.times.value"}},{"id":"roller.row.length.1.times","op":"mul@1","args":["host.size.x","k.ratio.0_5"],"outputs":{"value":"roller.row.length.1.times.value"}},{"id":"roller.row.length.1.sum","op":"add@1","args":["roller.row.length.0.times.value","roller.row.length.1.times.value"],"outputs":{"value":"roller.row.length.1.sum.value"}},{"id":"roller.row.extent.1.sum","op":"add@1","args":["roller.dimension.radius","roller.dimension.radius"],"outputs":{"value":"roller.row.extent.1.sum.value"}},{"id":"roller.layout","op":"fit-pitch@1","args":["roller.row.length.1.sum.value","d.mm.0","d.mm.0","roller.row.extent.1.sum.value","d.mm.200","k.ratio.0_5","limit.count.100"],"outputs":{"layout":"roller.layout.value"}},{"id":"roller.assembly","op":"assembly@1","args":["roller.placed"],"outputs":{"assembly":"roller.assembly.value"}},{"id":"roller.row","op":"compose@1","args":["host.chain.value","roller.seat.value"],"outputs":{"pose":"roller.row.value"}},{"id":"roller.repeat","op":"repeat@1","args":["roller.layout.value","roller.assembly.value","roller.row.value"],"outputs":{"collection":"roller.repeat.value"},"params":{"axis":"x"}}]},"capabilities":{"version":"r3-capability-3","assetFrame":"asset","bindings":[]}},"designInputs":{"host.size.x":3000,"host.size.y":700,"host.size.z":800,"roller.dimension.radius":30},"stateDefaults":{"spin":0},"palette":{"palette.primary":"#477b9e","palette.host":"#d9dde2"},"appearance":[{"target":"host","material":{"token":"palette.host"}},{"target":"roller","material":{"token":"palette.primary"},"segments":12}],"joints":[{"id":"host","type":"fixed","body0":{"plane":"mounting-plane"},"body1":{"part":"host","face":"bottom"},"origin":{}},{"id":"roller","type":"revolute","body0":{"part":"host","face":"top"},"body1":{"part":"roller","face":"centre"},"origin":{"gap":30,"turn":{"x":90,"y":0,"z":0}},"axis":"Z","lowerLimit":-360,"upperLimit":360,"state":{"id":"spin"},"repeat":{"axis":"X","pitch":200}}]}
|
|
@@ -61,18 +61,6 @@
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
|
-
{
|
|
65
|
-
"kind": "add-part",
|
|
66
|
-
"part": {
|
|
67
|
-
"id": "{id}-stripe",
|
|
68
|
-
"kind": "box",
|
|
69
|
-
"dimensions": {
|
|
70
|
-
"width": 12,
|
|
71
|
-
"height": 20,
|
|
72
|
-
"depth": 720
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
64
|
{
|
|
77
65
|
"kind": "link-dimension",
|
|
78
66
|
"part": "{id}-roller",
|
|
@@ -85,17 +73,6 @@
|
|
|
85
73
|
"times": 0.9,
|
|
86
74
|
"narrowRange": true
|
|
87
75
|
},
|
|
88
|
-
{
|
|
89
|
-
"kind": "link-dimension",
|
|
90
|
-
"part": "{id}-stripe",
|
|
91
|
-
"axis": "z",
|
|
92
|
-
"source": {
|
|
93
|
-
"of": "part",
|
|
94
|
-
"part": "{id}-roller",
|
|
95
|
-
"axis": "y"
|
|
96
|
-
},
|
|
97
|
-
"narrowRange": true
|
|
98
|
-
},
|
|
99
76
|
{
|
|
100
77
|
"kind": "set-joint",
|
|
101
78
|
"joint": {
|
|
@@ -134,22 +111,6 @@
|
|
|
134
111
|
},
|
|
135
112
|
"narrowRange": true
|
|
136
113
|
},
|
|
137
|
-
{
|
|
138
|
-
"kind": "set-joint",
|
|
139
|
-
"joint": {
|
|
140
|
-
"id": "{id}-stripe",
|
|
141
|
-
"type": "fixed",
|
|
142
|
-
"body0": {
|
|
143
|
-
"part": "{id}-roller",
|
|
144
|
-
"face": "left"
|
|
145
|
-
},
|
|
146
|
-
"body1": {
|
|
147
|
-
"part": "{id}-stripe",
|
|
148
|
-
"face": "right"
|
|
149
|
-
},
|
|
150
|
-
"origin": {}
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
114
|
{
|
|
154
115
|
"kind": "set-material",
|
|
155
116
|
"target": "{id}-roller",
|
|
@@ -164,16 +125,6 @@
|
|
|
164
125
|
"kind": "set-segments",
|
|
165
126
|
"target": "{id}-roller",
|
|
166
127
|
"segments": 12
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
"kind": "set-material",
|
|
170
|
-
"target": "{id}-stripe",
|
|
171
|
-
"material": {
|
|
172
|
-
"token": "palette.tpl-2f3437"
|
|
173
|
-
},
|
|
174
|
-
"palette": {
|
|
175
|
-
"palette.tpl-2f3437": "#2f3437"
|
|
176
|
-
}
|
|
177
128
|
}
|
|
178
129
|
]
|
|
179
130
|
}
|
|
Binary file
|
|
@@ -12,7 +12,7 @@ import assert from 'node:assert/strict'
|
|
|
12
12
|
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
13
13
|
import { join } from 'node:path'
|
|
14
14
|
|
|
15
|
-
import { applyV3Proposal, applyV3Recipe, checkV3Recipe, compileV3Asset, inspectV3Asset, serializeV3Asset } from '@hatiolab/figure-model'
|
|
15
|
+
import { applyV3Proposal, applyV3Recipe, checkV3Recipe, compileV3Asset, compileV3Document, inspectV3Asset, serializeV3Asset } from '@hatiolab/figure-model'
|
|
16
16
|
import type { V3Asset } from '@hatiolab/figure-model'
|
|
17
17
|
|
|
18
18
|
import { buildTemplate, modelFileOf, RECIPES } from './build-templates.mts'
|
|
@@ -69,6 +69,30 @@ for (const name of names) {
|
|
|
69
69
|
}
|
|
70
70
|
})
|
|
71
71
|
|
|
72
|
+
test(`${name}: an instance's evaluator gives what a whole evaluation gives, through its controls and its host's sizes`, async () => {
|
|
73
|
+
/* The per-instance evaluator runs again only what changed (2026-09-26); it must answer as a whole evaluation does. */
|
|
74
|
+
const { models } = await buildTemplate(name)
|
|
75
|
+
for (const { values, asset } of models) {
|
|
76
|
+
const plan = compileV3Document(asset.document)
|
|
77
|
+
const own = plan.evaluator()
|
|
78
|
+
const base = { ...asset.designInputs, ...asset.stateDefaults }
|
|
79
|
+
const states = asset.document.model.inputs.filter(i => i.role === 'state')
|
|
80
|
+
const steps: Array<Record<string, number>> = [base]
|
|
81
|
+
for (const k of [0.5, 2, 1]) steps.push({ ...withHost(asset, k).designInputs, ...asset.stateDefaults })
|
|
82
|
+
for (const input of states) for (const v of [input.min!, input.max!, (input.min! + input.max!) / 2]) steps.push({ ...base, [input.id]: v })
|
|
83
|
+
for (const inputs of steps) {
|
|
84
|
+
let whole: unknown, refused = false
|
|
85
|
+
try {
|
|
86
|
+
whole = plan.evaluate(inputs)
|
|
87
|
+
} catch {
|
|
88
|
+
refused = true
|
|
89
|
+
}
|
|
90
|
+
if (refused) assert.throws(() => own.evaluate(inputs), `${JSON.stringify(values)}: refused as the whole is`)
|
|
91
|
+
else assert.equal(JSON.stringify(own.evaluate(inputs)), JSON.stringify(whole), `${JSON.stringify(values)}: ${JSON.stringify(inputs).slice(0, 120)}`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
72
96
|
test(`${name}: the words and the picture are there`, () => {
|
|
73
97
|
for (const file of ['guide.ko.md', 'guide.en.md', 'facts.md', 'thumbnail.png']) assert.ok(existsSync(join(RECIPES, name, file)), `${file}`)
|
|
74
98
|
})
|