@pioneer_zmc/dsh-workbench 0.2.1
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/LICENSE +21 -0
- package/README.md +98 -0
- package/README.zh.md +100 -0
- package/client.js +122 -0
- package/cordis.patch.yml +8 -0
- package/dist/check-building.js +1669 -0
- package/dist/check-room.js +980 -0
- package/dist/index.js +2873 -0
- package/package.json +60 -0
- package/skills/specdev-building/SKILL.md +85 -0
- package/skills/specdev-building/assets/layouts/court.json +53 -0
- package/skills/specdev-building/assets/layouts/row.json +48 -0
- package/skills/specdev-building/assets/layouts/single.json +27 -0
- package/skills/specdev-building/assets/layouts/tower-9.json +71 -0
- package/skills/specdev-building/assets/layouts/tower.json +59 -0
- package/skills/specdev-building/references/blueprint-contract.md +126 -0
- package/skills/specdev-building/references/diagnostics.md +101 -0
- package/skills/specdev-building/references/layouts.md +112 -0
- package/skills/specdev-business/SKILL.md +91 -0
- package/skills/specdev-business/assets/plan-template.md +66 -0
- package/skills/specdev-business/assets/spec-template.md +111 -0
- package/skills/specdev-business/assets/tasks-template.md +36 -0
- package/skills/specdev-business/references/change.md +117 -0
- package/skills/specdev-business/references/evidence-review.md +125 -0
- package/skills/specdev-business/references/implementation.md +67 -0
- package/skills/specdev-business/references/project-contract.md +211 -0
- package/skills/specdev-business/references/source-map.md +110 -0
- package/skills/specdev-business/references/specification.md +93 -0
- package/skills/specdev-business/references/workflow.md +126 -0
- package/skills/specdev-room/SKILL.md +77 -0
- package/skills/specdev-room/assets/intro-room.json +14 -0
- package/skills/specdev-room/references/diagnostics.md +21 -0
- package/skills/specdev-room/references/room-contract.md +59 -0
- package/vendor/archify-renderer/VERSION.md +52 -0
- package/vendor/archify-renderer/archify/LICENSE +22 -0
- package/vendor/archify-renderer/archify/THIRD_PARTY_NOTICES.md +56 -0
- package/vendor/archify-renderer/archify/assets/template.html +14787 -0
- package/vendor/archify-renderer/archify/bin/archify.mjs +1990 -0
- package/vendor/archify-renderer/archify/renderers/shared/brand-marks.mjs +563 -0
- package/vendor/archify-renderer/archify/renderers/shared/cli.mjs +218 -0
- package/vendor/archify-renderer/archify/renderers/shared/desktop-readability.mjs +26 -0
- package/vendor/archify-renderer/archify/renderers/shared/diagnostics.mjs +127 -0
- package/vendor/archify-renderer/archify/renderers/shared/engineering-profiles.mjs +157 -0
- package/vendor/archify-renderer/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
- package/vendor/archify-renderer/archify/renderers/shared/generated-validators.mjs +13 -0
- package/vendor/archify-renderer/archify/renderers/shared/geometry.mjs +1423 -0
- package/vendor/archify-renderer/archify/renderers/shared/i18n.mjs +594 -0
- package/vendor/archify-renderer/archify/renderers/shared/legend.mjs +217 -0
- package/vendor/archify-renderer/archify/renderers/shared/output-path.mjs +321 -0
- package/vendor/archify-renderer/archify/renderers/shared/repository-evidence.mjs +235 -0
- package/vendor/archify-renderer/archify/renderers/shared/text-fit.mjs +49 -0
- package/vendor/archify-renderer/archify/renderers/shared/utils.mjs +232 -0
- package/vendor/archify-renderer/archify/renderers/shared/validator.mjs +86 -0
- package/vendor/archify-renderer/archify/renderers/workflow/render-workflow.mjs +35 -0
- package/vendor/archify-renderer/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- package/vendor/archify-renderer/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
- package/vendor/archify-renderer/archify/schemas/common.schema.json +115 -0
- package/vendor/archify-renderer/archify/schemas/workflow.schema.json +428 -0
- package/vendor/archify-renderer/archify/scripts/check-render-output.mjs +835 -0
- package/vendor/archify-renderer/shims/process.mjs +18 -0
- package/vendor/archify-renderer/stubs/child_process.mjs +2 -0
- package/vendor/archify-renderer/stubs/crypto.mjs +3 -0
- package/vendor/archify-renderer/stubs/dns-promises.mjs +2 -0
- package/vendor/archify-renderer/stubs/fs.mjs +3 -0
- package/vendor/archify-renderer/stubs/http.mjs +3 -0
- package/vendor/archify-renderer/stubs/https.mjs +3 -0
- package/vendor/archify-renderer/stubs/net.mjs +3 -0
- package/vendor/archify-renderer/stubs/path.mjs +3 -0
- package/vendor/archify-renderer/stubs/url.mjs +3 -0
- package/web/assets/building-kit.js +1157 -0
- package/web/assets/building-look.js +185 -0
- package/web/assets/building-plan.js +495 -0
- package/web/assets/building-scene.js +529 -0
- package/web/assets/building-walk.js +248 -0
- package/web/assets/building.js +859 -0
- package/web/assets/business.js +124 -0
- package/web/assets/chart-link.js +165 -0
- package/web/assets/common.js +302 -0
- package/web/assets/details.js +197 -0
- package/web/assets/evidence.js +30 -0
- package/web/assets/furniture/SOURCES.md +24 -0
- package/web/assets/furniture/assets/ceramic-display-stand-01/asset.json +25 -0
- package/web/assets/furniture/assets/ceramic-display-stand-01/model.js +116 -0
- package/web/assets/furniture/assets/ceramic-display-stand-01/preview.webp +0 -0
- package/web/assets/furniture/assets/ceramic-floor-lamp-01/asset.json +61 -0
- package/web/assets/furniture/assets/ceramic-floor-lamp-01/model.js +56 -0
- package/web/assets/furniture/assets/ceramic-floor-lamp-01/preview.webp +0 -0
- package/web/assets/furniture/assets/ceramic-low-cabinet-01/asset.json +81 -0
- package/web/assets/furniture/assets/ceramic-low-cabinet-01/model.js +67 -0
- package/web/assets/furniture/assets/ceramic-low-cabinet-01/preview.webp +0 -0
- package/web/assets/furniture/assets/ceramic-vase-01/asset.json +21 -0
- package/web/assets/furniture/assets/ceramic-vase-01/model.js +119 -0
- package/web/assets/furniture/assets/ceramic-vase-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-bed-01/asset.json +61 -0
- package/web/assets/furniture/assets/fairy-bed-01/model.js +63 -0
- package/web/assets/furniture/assets/fairy-bed-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-book-stack-01/asset.json +21 -0
- package/web/assets/furniture/assets/fairy-book-stack-01/model.js +71 -0
- package/web/assets/furniture/assets/fairy-book-stack-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-bookshelf-01/asset.json +32 -0
- package/web/assets/furniture/assets/fairy-bookshelf-01/model.js +168 -0
- package/web/assets/furniture/assets/fairy-bookshelf-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-desk-01/asset.json +25 -0
- package/web/assets/furniture/assets/fairy-desk-01/model.js +132 -0
- package/web/assets/furniture/assets/fairy-desk-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-notice-board-01/asset.json +23 -0
- package/web/assets/furniture/assets/fairy-notice-board-01/model.js +125 -0
- package/web/assets/furniture/assets/fairy-notice-board-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-potted-plant-01/asset.json +21 -0
- package/web/assets/furniture/assets/fairy-potted-plant-01/model.js +128 -0
- package/web/assets/furniture/assets/fairy-potted-plant-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-reading-chair-01/asset.json +61 -0
- package/web/assets/furniture/assets/fairy-reading-chair-01/model.js +66 -0
- package/web/assets/furniture/assets/fairy-reading-chair-01/preview.webp +0 -0
- package/web/assets/furniture/assets/fairy-table-lamp-01/asset.json +21 -0
- package/web/assets/furniture/assets/fairy-table-lamp-01/model.js +182 -0
- package/web/assets/furniture/assets/fairy-table-lamp-01/preview.webp +0 -0
- package/web/assets/furniture/catalog.js +137 -0
- package/web/assets/furniture/index.json +110 -0
- package/web/assets/furniture/registry.js +72 -0
- package/web/assets/furniture-page.css +68 -0
- package/web/assets/furniture-page.js +302 -0
- package/web/assets/home.js +65 -0
- package/web/assets/node-detail.js +148 -0
- package/web/assets/panel-link.js +47 -0
- package/web/assets/read.js +746 -0
- package/web/assets/room-scene.js +386 -0
- package/web/assets/room.css +287 -0
- package/web/assets/room.js +437 -0
- package/web/assets/rooms/bindings.js +449 -0
- package/web/assets/rooms/ceramic-reflection.js +193 -0
- package/web/assets/rooms/ceramic-room.js +302 -0
- package/web/assets/rooms/door.js +35 -0
- package/web/assets/rooms/fairy-room.js +492 -0
- package/web/assets/rooms/furniture-assembly.js +113 -0
- package/web/assets/rooms/navigation.js +262 -0
- package/web/assets/rooms/placeholder-catalog.json +71 -0
- package/web/assets/rooms/placeholder-furniture.js +76 -0
- package/web/assets/rooms/placement.js +588 -0
- package/web/assets/rooms/reader.js +441 -0
- package/web/assets/rooms/reading-gate.js +157 -0
- package/web/assets/rooms/room-lighting.js +50 -0
- package/web/assets/rooms/templates.json +50 -0
- package/web/assets/rooms/walk-frame.js +64 -0
- package/web/assets/rooms/walk-session.js +138 -0
- package/web/assets/save-result.js +89 -0
- package/web/assets/screenshots/business.png +0 -0
- package/web/assets/screenshots/node-detail.png +0 -0
- package/web/assets/screenshots/project-home.png +0 -0
- package/web/assets/screenshots/workflow.png +0 -0
- package/web/assets/showcase.js +232 -0
- package/web/assets/style.css +836 -0
- package/web/assets/template.js +7 -0
- package/web/assets/theme.js +29 -0
- package/web/assets/three/OrbitControls.js +1972 -0
- package/web/assets/three/VERSION.md +12 -0
- package/web/assets/three/three.core.js +60586 -0
- package/web/assets/three/three.module.js +19719 -0
- package/web/assets/workspaces.css +21 -0
- package/web/assets/workspaces.js +81 -0
- package/web/building.html +131 -0
- package/web/business.html +43 -0
- package/web/furniture.html +47 -0
- package/web/index.html +57 -0
- package/web/read.html +107 -0
- package/web/room.html +146 -0
- package/web/showcase.html +50 -0
- package/web/workspaces.html +32 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// 说明文档(details.md)解析:纯函数,不碰 DOM,Node 侧可直接 import 测试。
|
|
2
|
+
// 约定:一级标题 `#` 是图的总说明;`## <节点 id>` 分节,
|
|
3
|
+
// 节内为自由 Markdown。图上不存在但详情写了的 id —— 容忍,不报错(删节点后旧详情不必同步清);
|
|
4
|
+
// 图上存在但详情缺失的节点 —— 只列为"没写说明",不算错误。
|
|
5
|
+
|
|
6
|
+
/** 去掉首尾空行,不去中间(节内空行保留,段落由 parseBody 再拆)。 */
|
|
7
|
+
function trimBlank(lines) {
|
|
8
|
+
let start = 0
|
|
9
|
+
let end = lines.length
|
|
10
|
+
while (start < end && !lines[start].trim()) start += 1
|
|
11
|
+
while (end > start && !lines[end - 1].trim()) end -= 1
|
|
12
|
+
return lines.slice(start, end)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 拆出总说明与各分节(按文件出现顺序)。空文件/非字符串 → 空结果。 */
|
|
16
|
+
export function parseDetails(text) {
|
|
17
|
+
const result = { title: '', sections: [] }
|
|
18
|
+
if (typeof text !== 'string' || !text.trim()) return result
|
|
19
|
+
let current = null
|
|
20
|
+
// 正在其中的代码块围栏(``` 或 ~~~):围栏里的 `#`/`##` 是演示示例不是标题,
|
|
21
|
+
// 当正文原样保留——不然示例会截断上一节,还会冒充别的节点的说明
|
|
22
|
+
let fence = null
|
|
23
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
24
|
+
const line = rawLine.trimEnd()
|
|
25
|
+
const fenceMark = /^ {0,3}(`{3,}|~{3,})/.exec(line)
|
|
26
|
+
if (fence) {
|
|
27
|
+
// 关围栏:同种字符、不短于开的时候、后面没字(后面有字算围栏里的正文)
|
|
28
|
+
if (fenceMark && fenceMark[1][0] === fence[0] && fenceMark[1].length >= fence.length
|
|
29
|
+
&& !line.slice(fenceMark[0].length).trim()) fence = null
|
|
30
|
+
if (current) current.body.push(rawLine)
|
|
31
|
+
continue
|
|
32
|
+
}
|
|
33
|
+
if (fenceMark) {
|
|
34
|
+
fence = fenceMark[1]
|
|
35
|
+
if (current) current.body.push(rawLine)
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
// 一级标题只在首个分节之前算"总说明";分节里的 `#` 属于正文
|
|
39
|
+
const heading1 = /^#[ \t]+(.+?)[ \t]*$/.exec(line)
|
|
40
|
+
if (heading1 && !current) {
|
|
41
|
+
result.title = heading1[1].trim()
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
// `^##[ \t]` 不会匹配 `###`(第三个字符不是空白),三级标题因此留在节内
|
|
45
|
+
const heading2 = /^##[ \t]+(.+?)[ \t]*$/.exec(line)
|
|
46
|
+
if (heading2) {
|
|
47
|
+
// 约定写纯节点 id;多写的字不丢正文,按空白切成 id 与剩余文字
|
|
48
|
+
const [id, ...rest] = heading2[1].trim().split(/\s+/)
|
|
49
|
+
current = { id, note: rest.join(' '), body: [] }
|
|
50
|
+
result.sections.push(current)
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
if (current) current.body.push(rawLine)
|
|
54
|
+
}
|
|
55
|
+
for (const section of result.sections) section.body = trimBlank(section.body).join('\n')
|
|
56
|
+
return result
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 把说明文档与图上节点对上,供页面直接渲染。
|
|
61
|
+
* nodes 为空数组表示"图读不开、判断不了归属"(例如 workflow.json 坏了):
|
|
62
|
+
* 此时不把条目判成图外条目,按原样列出。
|
|
63
|
+
* nodes 里混进非对象或缺 id 的坏条目(如 JSON 里写了 null):跳过它们,不让页面崩——
|
|
64
|
+
* 图本身已由渲染区报错,这里不再因为同一份坏文件二次崩页。
|
|
65
|
+
*/
|
|
66
|
+
export function buildDetails(text, nodes) {
|
|
67
|
+
const parsed = parseDetails(text)
|
|
68
|
+
const chartNodes = (Array.isArray(nodes) ? nodes : []).filter(
|
|
69
|
+
(node) => node && typeof node === 'object' && !Array.isArray(node) && typeof node.id === 'string',
|
|
70
|
+
)
|
|
71
|
+
const known = chartNodes.length > 0
|
|
72
|
+
const byId = new Map(chartNodes.map((node) => [node.id, node]))
|
|
73
|
+
const seen = new Set()
|
|
74
|
+
// 同一编号写多节:不算错,但要让人知道——块上带这个编号在文档里出现的次数,
|
|
75
|
+
// 详情弹层据此提示"显示的是第一份",而不是默默挑一份
|
|
76
|
+
const times = new Map()
|
|
77
|
+
for (const section of parsed.sections) times.set(section.id, (times.get(section.id) || 0) + 1)
|
|
78
|
+
const blocks = []
|
|
79
|
+
const strays = []
|
|
80
|
+
for (const section of parsed.sections) {
|
|
81
|
+
seen.add(section.id)
|
|
82
|
+
const node = byId.get(section.id)
|
|
83
|
+
const entry = {
|
|
84
|
+
id: section.id,
|
|
85
|
+
label: node ? node.label || node.id : section.id,
|
|
86
|
+
sublabel: node && node.sublabel ? node.sublabel : '',
|
|
87
|
+
note: section.note || '',
|
|
88
|
+
body: section.body,
|
|
89
|
+
repeats: times.get(section.id) || 1,
|
|
90
|
+
onChart: known ? Boolean(node) : null,
|
|
91
|
+
}
|
|
92
|
+
if (entry.onChart === false) strays.push(entry)
|
|
93
|
+
else blocks.push(entry)
|
|
94
|
+
}
|
|
95
|
+
const missing = known
|
|
96
|
+
? chartNodes.filter((node) => !seen.has(node.id)).map((node) => ({ id: node.id, label: node.label || node.id }))
|
|
97
|
+
: []
|
|
98
|
+
const hasText = typeof text === 'string' && Boolean(text.trim())
|
|
99
|
+
return {
|
|
100
|
+
empty: !hasText,
|
|
101
|
+
title: parsed.title,
|
|
102
|
+
// 有内容却没有分节:没按约定写(只有总说明也算),页面据此说明为什么只有一段
|
|
103
|
+
malformed: hasText && parsed.sections.length === 0,
|
|
104
|
+
blocks,
|
|
105
|
+
strays,
|
|
106
|
+
missing,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 在说明文档结果里找某个节点的这一节(详情弹层用)。只做判断与查找,不拼文案——措辞由页面
|
|
112
|
+
* 按同一个版本的情况写,弹层与说明面板才说得一样。返回 { kind, ... }:
|
|
113
|
+
* 'unreadable' 文档在但读不开(如超过大小上限):给 unreadable 原因即此态
|
|
114
|
+
* 'empty' 这个版本没有说明文档
|
|
115
|
+
* 'section' 找到这一节:block 就是说明面板里那一块(正文/名字/副标签/编号都在里面);
|
|
116
|
+
* 同一编号在文档里写了多节时 repeats 带次数(取按出现顺序的第一份,
|
|
117
|
+
* 页面要据此提示,不能默默挑一份)
|
|
118
|
+
* 'malformed' 有文档但没按约定分节(每节应以「## 节点编号」开头)
|
|
119
|
+
* 'missing' 文档按约定分了节,但没有这个节点的
|
|
120
|
+
* 五种之外不会有第六种:图上有的节点,落到后四种之一必然说得出原因。
|
|
121
|
+
*/
|
|
122
|
+
export function findSection(doc, id, { unreadable = '' } = {}) {
|
|
123
|
+
if (unreadable) return { kind: 'unreadable', reason: unreadable }
|
|
124
|
+
if (!doc || doc.empty) return { kind: 'empty' }
|
|
125
|
+
// 图外条目(旧编号)也认:配上就显示,不因为"图上没有"就当没写
|
|
126
|
+
const block = doc.blocks.find((item) => item.id === id) || doc.strays.find((item) => item.id === id)
|
|
127
|
+
if (block) return { kind: 'section', block, repeats: block.repeats || 1 }
|
|
128
|
+
if (doc.malformed) return { kind: 'malformed' }
|
|
129
|
+
return { kind: 'missing', id }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 正文分块内核:空行分段、`-`/`*`/`数字.` 列表;headings 打开时把 `#` 标题单独成块
|
|
134
|
+
* (房内资料面板读整篇业务文档要标题;节点详情的节内正文不按标题切)。
|
|
135
|
+
* 只认最常见的写法,不假装是完整 Markdown 解析器。
|
|
136
|
+
*/
|
|
137
|
+
function parseBlocks(text, headings) {
|
|
138
|
+
const items = []
|
|
139
|
+
let paragraph = []
|
|
140
|
+
const flush = () => {
|
|
141
|
+
if (paragraph.length) {
|
|
142
|
+
items.push({ kind: 'paragraph', text: paragraph.join(' ') })
|
|
143
|
+
paragraph = []
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const line of String(text ?? '').split(/\r?\n/)) {
|
|
147
|
+
const trimmed = line.trim()
|
|
148
|
+
if (!trimmed) {
|
|
149
|
+
flush()
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
const heading = headings ? /^(#{1,6})[ \t]+(.+?)[ \t]*$/.exec(trimmed) : null
|
|
153
|
+
if (heading) {
|
|
154
|
+
flush()
|
|
155
|
+
items.push({ kind: 'heading', level: heading[1].length, text: heading[2] })
|
|
156
|
+
continue
|
|
157
|
+
}
|
|
158
|
+
const bullet = /^(?:[-*+]|\d+\.)[ \t]+(.*)$/.exec(trimmed)
|
|
159
|
+
if (bullet) {
|
|
160
|
+
flush()
|
|
161
|
+
items.push({ kind: 'bullet', text: bullet[1] })
|
|
162
|
+
} else {
|
|
163
|
+
paragraph.push(trimmed)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
flush()
|
|
167
|
+
return items
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 一节正文拆成段落与列表项。只认最常见的写法(空行分段、`-`/`*`/`数字.` 列表),
|
|
172
|
+
* 不假装是完整 Markdown 解析器。
|
|
173
|
+
*/
|
|
174
|
+
export const parseBody = (text) => parseBlocks(text, false)
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 整篇文档的正文分块(房内资料面板读业务文档用):与 parseBody 同一口径,多认 `#` 标题行。
|
|
178
|
+
* 标题按层级给 { kind: 'heading', level 1..6, text },页面据此决定用几级标题呈现。
|
|
179
|
+
*/
|
|
180
|
+
export const parseDocument = (text) => parseBlocks(text, true)
|
|
181
|
+
|
|
182
|
+
/** 行内标记:**加粗** 与 `代码`。返回片段数组,页面拼 DOM 用(不经 innerHTML)。 */
|
|
183
|
+
export function splitInline(text) {
|
|
184
|
+
const parts = []
|
|
185
|
+
const pattern = /\*\*([^*]+)\*\*|`([^`]+)`/g
|
|
186
|
+
let last = 0
|
|
187
|
+
let match
|
|
188
|
+
while ((match = pattern.exec(String(text ?? '')))) {
|
|
189
|
+
if (match.index > last) parts.push({ text: String(text).slice(last, match.index) })
|
|
190
|
+
if (match[1] !== undefined) parts.push({ text: match[1], strong: true })
|
|
191
|
+
else parts.push({ text: match[2], code: true })
|
|
192
|
+
last = pattern.lastIndex
|
|
193
|
+
}
|
|
194
|
+
const source = String(text ?? '')
|
|
195
|
+
if (last < source.length) parts.push({ text: source.slice(last) })
|
|
196
|
+
return parts
|
|
197
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// 源码证据展示的纯函数部分(步骤 4/D):不含 DOM,Node 可直接加载验证(.runtime/check-d.mjs)。
|
|
2
|
+
// 输入形状 = 服务端 /api/evidence 的返回(src/core/evidence.ts 的 EvidenceResult)。
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 服务端把报错拼成「refs[0](bad-commit):原因」;页面上编号与 id 已单独展示,
|
|
6
|
+
* 这里剥掉前缀只留人话原因。前缀对不上(形状变了)就原样返回,不吞错误。
|
|
7
|
+
*/
|
|
8
|
+
export function displayError(error) {
|
|
9
|
+
const stripped = error.replace(/^refs\[\d+\]([^:]*):/, '')
|
|
10
|
+
return stripped || error
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 代码片段拼成带行号的整段文本(行号 = 文件里的真实行号,从 fromLine 起)。 */
|
|
14
|
+
export function formatCodeBlock(text, fromLine) {
|
|
15
|
+
const lines = text.split('\n')
|
|
16
|
+
const width = String(fromLine + lines.length - 1).length
|
|
17
|
+
return lines.map((line, i) => `${String(fromLine + i).padStart(width)} │ ${line}`).join('\n')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 汇总一行:几条证据、几条有效、几条有问题。没有引用时返回 null(不显示汇总)。 */
|
|
21
|
+
export function evidenceSummary(refs) {
|
|
22
|
+
if (!refs.length) return null
|
|
23
|
+
const ok = refs.filter((ref) => ref.ok).length
|
|
24
|
+
return { total: refs.length, ok, bad: refs.length - ok }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 有效引用的出处说明(一行):文件 · 行范围 · 固定提交(短号)。 */
|
|
28
|
+
export function describeSource(ref) {
|
|
29
|
+
return `文件 ${ref.path} · 第 ${ref.fromLine}–${ref.toLine} 行 · 固定提交 ${ref.commit.slice(0, 8)}`
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 家具来源与许可
|
|
2
|
+
|
|
3
|
+
十二款登记家具均由本项目程序化自制;模型代码、程序生成纹理与渲染缩略图统一采用同包 [MIT许可证](../../../LICENSE)。没有引入外部模型或网络素材。木纹/织物由固定种子DataTexture生成,其余使用材质着色;缩略图由相应模型渲染生成。
|
|
4
|
+
|
|
5
|
+
asset.json 的 source.license 为 MIT。status=internal 是当前目录格式的登记字段,目录读取器仍要求此值;它不是许可限制,不需要修改为public/published。尺寸、互动、摆放与绑定约束以每款asset.json为准。
|
|
6
|
+
|
|
7
|
+
| 家具 | 元数据 | 模型 | 缩略图 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| 暖釉陶瓷展示台 | [ceramic-display-stand-01](assets/ceramic-display-stand-01/asset.json) | [model.js](assets/ceramic-display-stand-01/model.js) | [preview.webp](assets/ceramic-display-stand-01/preview.webp) |
|
|
10
|
+
| 暖釉落地阅读灯 | [ceramic-floor-lamp-01](assets/ceramic-floor-lamp-01/asset.json) | [model.js](assets/ceramic-floor-lamp-01/model.js) | [preview.webp](assets/ceramic-floor-lamp-01/preview.webp) |
|
|
11
|
+
| 暖白双门矮柜 | [ceramic-low-cabinet-01](assets/ceramic-low-cabinet-01/asset.json) | [model.js](assets/ceramic-low-cabinet-01/model.js) | [preview.webp](assets/ceramic-low-cabinet-01/preview.webp) |
|
|
12
|
+
| 暖釉细颈花瓶 | [ceramic-vase-01](assets/ceramic-vase-01/asset.json) | [model.js](assets/ceramic-vase-01/model.js) | [preview.webp](assets/ceramic-vase-01/preview.webp) |
|
|
13
|
+
| 林间蜂蜜木床 | [fairy-bed-01](assets/fairy-bed-01/asset.json) | [model.js](assets/fairy-bed-01/model.js) | [preview.webp](assets/fairy-bed-01/preview.webp) |
|
|
14
|
+
| 林间双册书摆 | [fairy-book-stack-01](assets/fairy-book-stack-01/asset.json) | [model.js](assets/fairy-book-stack-01/model.js) | [preview.webp](assets/fairy-book-stack-01/preview.webp) |
|
|
15
|
+
| 林间蜂蜜木书架 | [fairy-bookshelf-01](assets/fairy-bookshelf-01/asset.json) | [model.js](assets/fairy-bookshelf-01/model.js) | [preview.webp](assets/fairy-bookshelf-01/preview.webp) |
|
|
16
|
+
| 林间蜂蜜木书桌 | [fairy-desk-01](assets/fairy-desk-01/asset.json) | [model.js](assets/fairy-desk-01/model.js) | [preview.webp](assets/fairy-desk-01/preview.webp) |
|
|
17
|
+
| 林间蜂蜜木告示牌 | [fairy-notice-board-01](assets/fairy-notice-board-01/asset.json) | [model.js](assets/fairy-notice-board-01/model.js) | [preview.webp](assets/fairy-notice-board-01/preview.webp) |
|
|
18
|
+
| 林间小叶盆栽 | [fairy-potted-plant-01](assets/fairy-potted-plant-01/asset.json) | [model.js](assets/fairy-potted-plant-01/model.js) | [preview.webp](assets/fairy-potted-plant-01/preview.webp) |
|
|
19
|
+
| 林间软垫阅读椅 | [fairy-reading-chair-01](assets/fairy-reading-chair-01/asset.json) | [model.js](assets/fairy-reading-chair-01/model.js) | [preview.webp](assets/fairy-reading-chair-01/preview.webp) |
|
|
20
|
+
| 林间暖光台灯 | [fairy-table-lamp-01](assets/fairy-table-lamp-01/asset.json) | [model.js](assets/fairy-table-lamp-01/model.js) | [preview.webp](assets/fairy-table-lamp-01/preview.webp) |
|
|
21
|
+
|
|
22
|
+
## 第三方依赖
|
|
23
|
+
|
|
24
|
+
Three.js用于渲染,随包代码保留上游MIT版权声明,来源与版本见 [Three.js版本说明](../three/VERSION.md)。Archify用于资料和流程图渲染,其 [LICENSE](../../../vendor/archify-renderer/archify/LICENSE) 与 [第三方声明](../../../vendor/archify-renderer/archify/THIRD_PARTY_NOTICES.md) 保留。家具MIT声明不替换任何第三方版权。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"assetVersion": 1,
|
|
4
|
+
"status": "internal",
|
|
5
|
+
"assetId": "ceramic-display-stand-01",
|
|
6
|
+
"name": "暖釉陶瓷展示台",
|
|
7
|
+
"category": "display-stand",
|
|
8
|
+
"description": "暖白象牙釉面陶瓷展示台:厚台板带柔和倒角与台板下一圈浅青灰收边,中央收腰柱身托住台板,向下接较宽的低底座,底面落地。釉面有柔和高光与很弱的粗糙度变化。占地 0.8×0.6 米、高 0.9 米,本地 +Z 为正面。台顶提供水平陈列面,可放置台灯、花瓶、绿植与小摆件;展示台没有资料动作,不是屏幕,不显示文档或流程图。",
|
|
9
|
+
"styleIds": ["ceramic"],
|
|
10
|
+
"modelRef": "ceramic-display-stand-01",
|
|
11
|
+
"previewRef": "assets/ceramic-display-stand-01/preview.webp",
|
|
12
|
+
"placeholder": false,
|
|
13
|
+
"bounds": { "minX": -0.4, "maxX": 0.4, "minY": 0, "maxY": 0.9, "minZ": -0.3, "maxZ": 0.3 },
|
|
14
|
+
"placement": { "allowedKinds": ["floor"], "allowedYawDeg": [0, 90, 180, 270] },
|
|
15
|
+
"clearanceBoxes": [
|
|
16
|
+
{ "name": "展台前陈列操作区", "bounds": { "minX": -0.5, "maxX": 0.5, "minY": 0, "maxY": 1.9, "minZ": 0.3, "maxZ": 1.3 } }
|
|
17
|
+
],
|
|
18
|
+
"approachPoint": { "x": 0, "z": 0.9 },
|
|
19
|
+
"surfaces": [
|
|
20
|
+
{ "surfaceId": "display-top", "name": "展示台顶部陈列区", "rect": { "minX": -0.32, "maxX": 0.32, "minZ": -0.22, "maxZ": 0.22 }, "y": 0.9, "allowedCategories": ["lamp", "vase", "plant", "ornament"] }
|
|
21
|
+
],
|
|
22
|
+
"capabilities": [],
|
|
23
|
+
"recommendations": { "note": "推荐写实项目展示区,也可混搭童话阅读房。台顶陈列区接受台灯、花瓶、绿植与小摆件四类,子物品仍须自身支持 surface 放置并通过投影、重叠与高度等现行校验,不代表对应资产已经做出或任意大小都能放。" },
|
|
24
|
+
"source": { "origin": "自制", "license": "MIT", "note": "当前资产包 model.js 自制 Three.js 程序化模型与固定种子 DataTexture 釉面粗糙度纹理,无外部模型或贴图。" }
|
|
25
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// 暖釉陶瓷展示台:自制程序化候选模型,不依赖 DOM、网络或实验文件。
|
|
2
|
+
// 米制、+Y 向上、+Z 正面;底面中心原点。冻结包络 X±0.4、Y[0,0.9]、Z±0.3。
|
|
3
|
+
// 设计常数(检查按此独立手算):台板圆角矩形主体 X±0.4/Z±0.3(shape 内收倒角 0.03),
|
|
4
|
+
// 挤出总厚 0.07=depth 0.062+两端倒角 0.004,Y[0.83,0.9],顶面平面区 X±0.37/Z±0.27;
|
|
5
|
+
// 台板收边(浅青灰)X±0.36/Z±0.26、Y[0.800,0.838],顶面嵌入台板内部避免共面闪纹;
|
|
6
|
+
// 收腰柱身 Lathe 半径 0.118~0.175、Y[0.050,0.845],两端开口分别埋入底座与收边;
|
|
7
|
+
// 宽底座圆角矩形 X±0.335/Z±0.235(shape 0.33/0.23+倒角 0.005)、Y[0,0.085],底面贴地。
|
|
8
|
+
import * as THREE from 'three';
|
|
9
|
+
|
|
10
|
+
// 釉面粗糙度微变化:缓波纹叠极弱噪点(NoColorSpace,乘在基础 roughness 上),不是砂纸颗粒。
|
|
11
|
+
function glazeRoughness() {
|
|
12
|
+
const size = 256;
|
|
13
|
+
const data = new Uint8Array(size * size * 4);
|
|
14
|
+
let seed = 20260941;
|
|
15
|
+
const random = () => { seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0; return seed / 4294967296; };
|
|
16
|
+
for (let v = 0; v < size; v++) for (let u = 0; u < size; u++) {
|
|
17
|
+
const wave = 232 + 3.5 * Math.sin(u * 0.021 + Math.cos(v * 0.017) * 1.2) + 2 * Math.sin(v * 0.043 + u * 0.011) + (random() - 0.5) * 3;
|
|
18
|
+
const i = (v * size + u) * 4;
|
|
19
|
+
const tone = Math.round(Math.max(0, Math.min(255, wave)));
|
|
20
|
+
data[i] = data[i + 1] = data[i + 2] = tone; data[i + 3] = 255;
|
|
21
|
+
}
|
|
22
|
+
const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat);
|
|
23
|
+
texture.colorSpace = THREE.NoColorSpace;
|
|
24
|
+
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
|
|
25
|
+
texture.magFilter = THREE.LinearFilter;
|
|
26
|
+
texture.minFilter = THREE.LinearMipmapLinearFilter;
|
|
27
|
+
texture.generateMipmaps = true;
|
|
28
|
+
texture.needsUpdate = true;
|
|
29
|
+
return texture;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 圆角矩形轮廓(本地 XY 平面):圆角只落在四角,直边保持给定半宽/半深。
|
|
33
|
+
function roundedRect(halfWidth, halfDepth, radius) {
|
|
34
|
+
const shape = new THREE.Shape();
|
|
35
|
+
shape.moveTo(-halfWidth + radius, -halfDepth);
|
|
36
|
+
shape.lineTo(halfWidth - radius, -halfDepth);
|
|
37
|
+
shape.quadraticCurveTo(halfWidth, -halfDepth, halfWidth, -halfDepth + radius);
|
|
38
|
+
shape.lineTo(halfWidth, halfDepth - radius);
|
|
39
|
+
shape.quadraticCurveTo(halfWidth, halfDepth, halfWidth - radius, halfDepth);
|
|
40
|
+
shape.lineTo(-halfWidth + radius, halfDepth);
|
|
41
|
+
shape.quadraticCurveTo(-halfWidth, halfDepth, -halfWidth, halfDepth - radius);
|
|
42
|
+
shape.lineTo(-halfWidth, -halfDepth + radius);
|
|
43
|
+
shape.quadraticCurveTo(-halfWidth, -halfDepth, -halfWidth + radius, -halfDepth);
|
|
44
|
+
shape.closePath();
|
|
45
|
+
return shape;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 水平板:在本地 XY 画圆角矩形、沿 +Z 挤出,再绕 X 转 -90° 使挤出方向成为 +Y;
|
|
49
|
+
// yBottom 对应挤出几何的 z=0 面(含倒角时是下端倒角外沿)。只创建与放置 mesh,入组由调用方处理。
|
|
50
|
+
function slab(geometry, material, name, yBottom) {
|
|
51
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
52
|
+
mesh.rotation.x = -Math.PI / 2;
|
|
53
|
+
mesh.position.y = yBottom;
|
|
54
|
+
mesh.name = name;
|
|
55
|
+
mesh.castShadow = mesh.receiveShadow = true;
|
|
56
|
+
return mesh;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createCeramicDisplayStand() {
|
|
60
|
+
const group = new THREE.Group();
|
|
61
|
+
group.name = 'ceramic-display-stand-01';
|
|
62
|
+
const owned = new Set();
|
|
63
|
+
const keep = (resource) => { owned.add(resource); return resource; };
|
|
64
|
+
let disposed = false;
|
|
65
|
+
const dispose = () => {
|
|
66
|
+
if (disposed) return;
|
|
67
|
+
disposed = true;
|
|
68
|
+
group.removeFromParent();
|
|
69
|
+
for (const resource of owned) resource.dispose();
|
|
70
|
+
owned.clear(); group.clear();
|
|
71
|
+
};
|
|
72
|
+
try {
|
|
73
|
+
const rough = keep(glazeRoughness());
|
|
74
|
+
// 暖白象牙釉:柔和高光(clearcoat)叠很弱的粗糙度变化;模型自身不创建灯光或环境贴图。
|
|
75
|
+
const glaze = keep(new THREE.MeshPhysicalMaterial({
|
|
76
|
+
color: '#efe7da', roughness: 0.17, roughnessMap: rough, metalness: 0,
|
|
77
|
+
clearcoat: 0.55, clearcoatRoughness: 0.3,
|
|
78
|
+
}));
|
|
79
|
+
const trim = keep(new THREE.MeshPhysicalMaterial({
|
|
80
|
+
color: '#b6c3be', roughness: 0.38, roughnessMap: rough, metalness: 0,
|
|
81
|
+
clearcoat: 0.3, clearcoatRoughness: 0.45,
|
|
82
|
+
}));
|
|
83
|
+
const add = (geometry, material, name, yBottom) => group.add(slab(keep(geometry), material, name, yBottom));
|
|
84
|
+
|
|
85
|
+
// 厚台板:shape 内收 0.03 让倒角后的主体恰为 X±0.4/Z±0.3;顶面(Y=0.9)平面区 X±0.37/Z±0.27,
|
|
86
|
+
// 完整覆盖承载矩形 X±0.32/Z±0.22,九点采样不会命中倒角斜面。
|
|
87
|
+
const TOP = { depth: 0.062, bevel: 0.004, top: 0.9 };
|
|
88
|
+
add(new THREE.ExtrudeGeometry(
|
|
89
|
+
roundedRect(0.37, 0.27, 0.05),
|
|
90
|
+
{ depth: TOP.depth, bevelEnabled: true, bevelThickness: TOP.bevel, bevelSize: 0.03, bevelSegments: 3, curveSegments: 12 },
|
|
91
|
+
), glaze, '厚台板', TOP.top - TOP.depth - TOP.bevel);
|
|
92
|
+
|
|
93
|
+
// 台板收边:浅青灰整板,四壁环绕形成台板下的一圈收边;顶面 Y=0.838 嵌入台板主体(不共面)。
|
|
94
|
+
add(new THREE.ExtrudeGeometry(roundedRect(0.36, 0.26, 0.04), { depth: 0.038, bevelEnabled: false, curveSegments: 12 }), trim, '台板收边', 0.800);
|
|
95
|
+
|
|
96
|
+
// 中央收腰柱身:Lathe 旋转体轮廓,底部展宽、腰部收细、顶部微扩托住收边;
|
|
97
|
+
// 两端开口分别埋入底座(顶 0.085)与收边板(0.800~0.838)内部,外部看不到开口。
|
|
98
|
+
const profile = [
|
|
99
|
+
[0.150, 0.050], [0.150, 0.090], [0.175, 0.160], [0.150, 0.300],
|
|
100
|
+
[0.118, 0.440], [0.118, 0.560], [0.150, 0.700], [0.172, 0.800], [0.172, 0.845],
|
|
101
|
+
].map(([radius, height]) => new THREE.Vector2(radius, height));
|
|
102
|
+
// Lathe 旋转体本身绕 Y 直立,直接放置,不做水平板的转轴处理。
|
|
103
|
+
const column = new THREE.Mesh(keep(new THREE.LatheGeometry(profile, 40)), glaze);
|
|
104
|
+
column.name = '收腰柱身'; column.castShadow = column.receiveShadow = true;
|
|
105
|
+
group.add(column);
|
|
106
|
+
|
|
107
|
+
// 较宽低底座:圆角矩形厚板,底面 Y=0 落地,顶面 0.085 与柱身嵌接。
|
|
108
|
+
const BASE = { depth: 0.075, bevel: 0.005 };
|
|
109
|
+
add(new THREE.ExtrudeGeometry(
|
|
110
|
+
roundedRect(0.33, 0.23, 0.06),
|
|
111
|
+
{ depth: BASE.depth, bevelEnabled: true, bevelThickness: BASE.bevel, bevelSize: 0.005, bevelSegments: 2, curveSegments: 12 },
|
|
112
|
+
), glaze, '宽底座', BASE.bevel);
|
|
113
|
+
|
|
114
|
+
return { group, dispose };
|
|
115
|
+
} catch (error) { dispose(); throw error; }
|
|
116
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"assetVersion": 1,
|
|
4
|
+
"status": "internal",
|
|
5
|
+
"assetId": "ceramic-floor-lamp-01",
|
|
6
|
+
"name": "暖釉落地阅读灯",
|
|
7
|
+
"category": "floor-lamp",
|
|
8
|
+
"description": "低圆暖白釉面底座连接黄铜立杆、支架和灯座,双壁暖白布罩带厚口沿及上下开口,罩内固定暖亮灯泡。宽深不超过0.48、高不超过1.65米;按+Z为统一正面,仅地面放置。静态自发光外观,不实际照亮周围,不支持灯光开关,无承载面。",
|
|
9
|
+
"styleIds": [
|
|
10
|
+
"ceramic"
|
|
11
|
+
],
|
|
12
|
+
"modelRef": "ceramic-floor-lamp-01",
|
|
13
|
+
"previewRef": "assets/ceramic-floor-lamp-01/preview.webp",
|
|
14
|
+
"placeholder": false,
|
|
15
|
+
"bounds": {
|
|
16
|
+
"minX": -0.24,
|
|
17
|
+
"maxX": 0.24,
|
|
18
|
+
"minY": 0,
|
|
19
|
+
"maxY": 1.65,
|
|
20
|
+
"minZ": -0.24,
|
|
21
|
+
"maxZ": 0.24
|
|
22
|
+
},
|
|
23
|
+
"placement": {
|
|
24
|
+
"allowedKinds": [
|
|
25
|
+
"floor"
|
|
26
|
+
],
|
|
27
|
+
"allowedYawDeg": [
|
|
28
|
+
0,
|
|
29
|
+
90,
|
|
30
|
+
180,
|
|
31
|
+
270
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"clearanceBoxes": [
|
|
35
|
+
{
|
|
36
|
+
"name": "正面接近操作区",
|
|
37
|
+
"bounds": {
|
|
38
|
+
"minX": -0.34,
|
|
39
|
+
"maxX": 0.34,
|
|
40
|
+
"minY": 0,
|
|
41
|
+
"maxY": 1.9,
|
|
42
|
+
"minZ": 0.24,
|
|
43
|
+
"maxZ": 1.2
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"approachPoint": {
|
|
48
|
+
"x": 0,
|
|
49
|
+
"z": 0.85
|
|
50
|
+
},
|
|
51
|
+
"surfaces": [],
|
|
52
|
+
"capabilities": [],
|
|
53
|
+
"recommendations": {
|
|
54
|
+
"note": "适合温暖阅读休憩房,与蜂蜜木书桌、书架、奶油布料及暖白陶瓷搭配;风格只是软推荐,混搭允许,摆放以空间和类别校验为准。"
|
|
55
|
+
},
|
|
56
|
+
"source": {
|
|
57
|
+
"origin": "自制",
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"note": "自制Three.js程序化实体几何,无外部模型或贴图;有木面者使用固定种子DataTexture。"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// 自制阅读休憩家具。米制、+Y向上、+Z正面,底面中心为原点。
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
|
|
4
|
+
// 圆角实体沿Z挤出,边缘真实倒角;归一到指定尺寸,顶面与包络不靠声明替身。
|
|
5
|
+
function rounded(w, h, d, radius = 0.025) {
|
|
6
|
+
const r = Math.min(radius, w / 4, h / 4), x = -w / 2, y = -h / 2;
|
|
7
|
+
const shape = new THREE.Shape();
|
|
8
|
+
shape.moveTo(x + r, y); shape.lineTo(x + w - r, y);
|
|
9
|
+
shape.quadraticCurveTo(x + w, y, x + w, y + r);
|
|
10
|
+
shape.lineTo(x + w, y + h - r); shape.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
|
11
|
+
shape.lineTo(x + r, y + h); shape.quadraticCurveTo(x, y + h, x, y + h - r);
|
|
12
|
+
shape.lineTo(x, y + r); shape.quadraticCurveTo(x, y, x + r, y);
|
|
13
|
+
const g = new THREE.ExtrudeGeometry(shape, { depth: d, steps: 1, curveSegments: 4,
|
|
14
|
+
bevelEnabled: true, bevelSegments: 2, bevelSize: Math.min(0.006, d / 5), bevelThickness: Math.min(0.006, d / 5) });
|
|
15
|
+
g.center(); g.computeBoundingBox();
|
|
16
|
+
const size = g.boundingBox.getSize(new THREE.Vector3());
|
|
17
|
+
g.scale(w / size.x, h / size.y, d / size.z); g.computeVertexNormals();
|
|
18
|
+
return g;
|
|
19
|
+
}
|
|
20
|
+
export function createCeramicFloorLamp() {
|
|
21
|
+
const group = new THREE.Group(); group.name = 'ceramic-floor-lamp-01';
|
|
22
|
+
const owned = new Set(), keep = value => { owned.add(value); return value; }; let disposed = false;
|
|
23
|
+
const dispose = () => {
|
|
24
|
+
if (disposed) return; disposed = true; group.removeFromParent();
|
|
25
|
+
for (const resource of owned) resource.dispose(); owned.clear(); group.clear();
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
const material = options => keep(new THREE.MeshStandardMaterial(options));
|
|
29
|
+
const cream = material({ color: '#eee4cf', roughness: 0.92 });
|
|
30
|
+
const glaze = material({ color: '#eee9dd', roughness: 0.43 });
|
|
31
|
+
const brass = material({ color: '#b69557', metalness: 0.72, roughness: 0.38 });
|
|
32
|
+
const add = (g, m, x, y, z, name) => {
|
|
33
|
+
const mesh = new THREE.Mesh(keep(g), m); mesh.name = name; mesh.position.set(x, y, z);
|
|
34
|
+
mesh.castShadow = mesh.receiveShadow = true; group.add(mesh); return mesh;
|
|
35
|
+
};
|
|
36
|
+
const box = (w, h, d, m, x, y, z, name) => add(new THREE.BoxGeometry(w, h, d), m, x, y, z, name);
|
|
37
|
+
const soft = (w, h, d, m, x, y, z, name, radius) => add(rounded(w, h, d, radius), m, x, y, z, name);
|
|
38
|
+
add(new THREE.CylinderGeometry(0.185, 0.215, 0.085, 40), glaze, 0, 0.0425, 0, '釉面低圆底座');
|
|
39
|
+
add(new THREE.CylinderGeometry(0.018, 0.018, 1.355, 16), brass, 0, 0.7575, 0, '黄铜立杆');
|
|
40
|
+
add(new THREE.CylinderGeometry(0.035, 0.032, 0.08, 20), brass, 0, 1.39, 0, '杆端灯座');
|
|
41
|
+
const bulbMat = material({ color: '#fff1cf', emissive: '#ffcf80', emissiveIntensity: 0.45, roughness: 0.5 });
|
|
42
|
+
const bulb = add(new THREE.SphereGeometry(1, 20, 12), bulbMat, 0, 1.465, 0, '固定暖亮灯泡'); bulb.scale.set(0.045, 0.065, 0.045);
|
|
43
|
+
// Lathe环剖面绕一圈:外壁上行、顶口沿、内壁下行、底口沿,中央上下真正开口。
|
|
44
|
+
const profile = [[0.235,1.28],[0.158,1.65],[0.148,1.65],[0.225,1.28],[0.235,1.28]];
|
|
45
|
+
add(new THREE.LatheGeometry(profile.map(([r,y]) => new THREE.Vector2(r,y)), 48), cream, 0, 0, 0, '双壁开口厚布灯罩');
|
|
46
|
+
// 三个真实径向支架位于下口平面,穿入罩的厚口沿与中心杆端。
|
|
47
|
+
for (let i = 0; i < 3; i++) {
|
|
48
|
+
const angle = i * Math.PI * 2 / 3;
|
|
49
|
+
const a = new THREE.Vector3(0, 1.285, 0), b = new THREE.Vector3(Math.cos(angle)*0.232, 1.285, Math.sin(angle)*0.232);
|
|
50
|
+
const delta = b.clone().sub(a);
|
|
51
|
+
const support = add(new THREE.CylinderGeometry(0.004,0.004,delta.length(),10),brass,(a.x+b.x)/2,1.285,(a.z+b.z)/2,'径向灯罩支架');
|
|
52
|
+
support.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0),delta.normalize());
|
|
53
|
+
}
|
|
54
|
+
return { group, dispose };
|
|
55
|
+
} catch (error) { dispose(); throw error; }
|
|
56
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"assetVersion": 1,
|
|
4
|
+
"status": "internal",
|
|
5
|
+
"assetId": "ceramic-low-cabinet-01",
|
|
6
|
+
"name": "暖白双门矮柜",
|
|
7
|
+
"category": "cabinet",
|
|
8
|
+
"description": "暖白半哑光完整柜体、浅木厚顶板、两扇关闭柜门与黄铜小把手。宽1.10、深0.56、高0.85米;+Z为双门正面。柜顶陈列区平整,可承载台灯、花瓶、盆栽、摆件;点击可打开已绑定的资料集合,不是开柜门或抽屉动画。",
|
|
9
|
+
"styleIds": [
|
|
10
|
+
"ceramic"
|
|
11
|
+
],
|
|
12
|
+
"modelRef": "ceramic-low-cabinet-01",
|
|
13
|
+
"previewRef": "assets/ceramic-low-cabinet-01/preview.webp",
|
|
14
|
+
"placeholder": false,
|
|
15
|
+
"bounds": {
|
|
16
|
+
"minX": -0.55,
|
|
17
|
+
"maxX": 0.55,
|
|
18
|
+
"minY": 0,
|
|
19
|
+
"maxY": 0.85,
|
|
20
|
+
"minZ": -0.28,
|
|
21
|
+
"maxZ": 0.28
|
|
22
|
+
},
|
|
23
|
+
"placement": {
|
|
24
|
+
"allowedKinds": [
|
|
25
|
+
"floor"
|
|
26
|
+
],
|
|
27
|
+
"allowedYawDeg": [
|
|
28
|
+
0,
|
|
29
|
+
90,
|
|
30
|
+
180,
|
|
31
|
+
270
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"clearanceBoxes": [
|
|
35
|
+
{
|
|
36
|
+
"name": "正面接近操作区",
|
|
37
|
+
"bounds": {
|
|
38
|
+
"minX": -0.65,
|
|
39
|
+
"maxX": 0.65,
|
|
40
|
+
"minY": 0,
|
|
41
|
+
"maxY": 1.9,
|
|
42
|
+
"minZ": 0.28,
|
|
43
|
+
"maxZ": 1.3
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"approachPoint": {
|
|
48
|
+
"x": 0,
|
|
49
|
+
"z": 0.9
|
|
50
|
+
},
|
|
51
|
+
"surfaces": [
|
|
52
|
+
{
|
|
53
|
+
"surfaceId": "cabinet-top",
|
|
54
|
+
"name": "矮柜顶部陈列区",
|
|
55
|
+
"rect": {
|
|
56
|
+
"minX": -0.48,
|
|
57
|
+
"maxX": 0.48,
|
|
58
|
+
"minZ": -0.21,
|
|
59
|
+
"maxZ": 0.21
|
|
60
|
+
},
|
|
61
|
+
"y": 0.85,
|
|
62
|
+
"allowedCategories": [
|
|
63
|
+
"lamp",
|
|
64
|
+
"vase",
|
|
65
|
+
"plant",
|
|
66
|
+
"ornament"
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"capabilities": [
|
|
71
|
+
"open-document-collection"
|
|
72
|
+
],
|
|
73
|
+
"recommendations": {
|
|
74
|
+
"note": "适合温暖阅读休憩房,与蜂蜜木书桌、书架、奶油布料及暖白陶瓷搭配;风格只是软推荐,混搭允许,摆放以空间和类别校验为准。"
|
|
75
|
+
},
|
|
76
|
+
"source": {
|
|
77
|
+
"origin": "自制",
|
|
78
|
+
"license": "MIT",
|
|
79
|
+
"note": "自制Three.js程序化实体几何,无外部模型或贴图;有木面者使用固定种子DataTexture。"
|
|
80
|
+
}
|
|
81
|
+
}
|