printbench 0.8.6 → 0.8.9

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/README.md CHANGED
@@ -101,7 +101,7 @@ function onExported(payload: PrintBenchExportedPayload) {
101
101
  | `gridSizeMm` | `{ x, y, z }` | — | 宿主默认固定网格尺寸(mm,边长取床面两轴 max,20 格)。挂载时写入一次;若未同时传 `gridAutoFit`,会自动设为固定尺寸(`gridAutoFit: false`) |
102
102
  | `diagnosisZIndex` | `number` | `100` | 诊断浮层 `z-index`。也可在外层设 CSS 变量 `--printbench-diagnosis-z` |
103
103
  | `annotations` | `Annotation[]` \| `AnnotationDocument` \| `string` | — | 模型批注(世界坐标 mm)。需同时打开 `features.annotations` 才能编辑;详见下方「批注」 |
104
- | `features` | `{ numeric?, primitives?, split?, autoSplit?, drafts?, extras?, restore?, layout?, importUnit?, hideEmpty?, hideTour?, hideStatusBar?, hidePanelModeToggle?, hidePreviewLighting?, annotations?, … }` | 能力项默认 `false` | 进阶能力。缺省精简:左栏仅导入 / 零件 / 导出 / 快照。未开 `drafts` 时不读不写自动草稿。导入单位优先用文件声明,否则按包围盒尺寸推测 mm/cm/m 再换算到内部毫米;开 `importUnit` 时可再确认。`split` 为切面拆件;`autoSplit` 为「自动拆件」按钮(默认隐藏,需显式打开)。`restore` 为「复原到导入状态」按钮(默认隐藏,宿主需显式打开;完整工作台预设已开启)。`layout` 打开贴地 / 镜像 / 目标尺寸缩放 / 选面朝下与打印床体积。`annotations` 打开模型批注(默认关闭,`PRINTBENCH_FULL_FEATURES` 也不含该项)。`hideEmpty` / `hideTour` / `hideStatusBar` / `hidePanelModeToggle` / `hidePreviewLighting` 为 `true` 时分别隐藏空画布提示、首次引导 Tour(可与 `extras` 同开)、底栏状态条、零件栏悬浮/通栏切换按钮、预览模式左上角灯光模式控件(默认显示)。 |
104
+ | `features` | `{ numeric?, primitives?, split?, autoSplit?, drafts?, extras?, restore?, layout?, importUnit?, hideEmpty?, hideTour?, hideStatusBar?, hidePanelModeToggle?, hidePreviewLighting?, annotations?, annotationPanel?, … }` | 能力项默认 `false` | 进阶能力。缺省精简:左栏仅导入 / 零件 / 导出 / 快照。未开 `drafts` 时不读不写自动草稿。导入单位优先用文件声明,否则按包围盒尺寸推测 mm/cm/m 再换算到内部毫米;开 `importUnit` 时可再确认。`split` 为切面拆件;`autoSplit` 为「自动拆件」按钮(默认隐藏,需显式打开)。`restore` 为「复原到导入状态」按钮(默认隐藏,宿主需显式打开;完整工作台预设已开启)。`layout` 打开贴地 / 镜像 / 目标尺寸缩放 / 选面朝下与打印床体积。`annotations` 打开批注能力(画布标记、放置、prop/事件与 ref API;默认关闭,`PRINTBENCH_FULL_FEATURES` 也不含)。`annotationPanel` 控制右栏自带批注 Tab(依赖 `annotations`;省略时随 `annotations` 开启,设 `false` 可自建宿主 UI)。`hideEmpty` / `hideTour` / `hideStatusBar` / `hidePanelModeToggle` / `hidePreviewLighting` 为 `true` 时分别隐藏空画布提示、首次引导 Tour(可与 `extras` 同开)、底栏状态条、零件栏悬浮/通栏切换按钮、预览模式左上角灯光模式控件(默认显示)。 |
105
105
 
106
106
 
107
107
  嵌入预览示例(宿主列表 + 色板 + 自定义导出 + 按轴/最长边缩放):
@@ -198,6 +198,10 @@ async function upload() {
198
198
  | `select-part` | `{ id, index, additive }` | 画布或零件树点选 |
199
199
  | `part-colors-change` | `Record<string, string>` | 工作台内改色时抛出(嵌入预览以 `partColors` 为准) |
200
200
  | `annotations-change` | `{ schema, version, unit, coordinateSpace, fileName?, generatedAt, annotations, reason }` | 批注新增 / 修改 / 删除时抛出(`reason`: `add` \| `update` \| `remove`)。仅在 `features.annotations` 开启时触发 |
201
+ | `annotation-select` | `string \| null` | 当前选中批注变化(列表、画布标记或 ref API);关闭批注能力时不触发 |
202
+ | `annotation-mode-change` | `'off' \| 'place'` | 放置模式进出(Esc 或再次切换按钮 / `setAnnotationMode('off')`);新建成功不退出;仅在 `features.annotations` 开启时触发 |
203
+ | `annotation-click` | `string` | 点击画布标注点(随后仍会选中并飞到锚点) |
204
+ | `annotation-hover` | `string \| null` | 鼠标经过画布标注点;离开全部标记时为 `null` |
201
205
 
202
206
 
203
207
  `exported` 的 `format` 为 `'stl' | '3mf' | 'glb'`;多零件 STL 时 `fileName` 为 zip,`blob` 为打包后的文件。GLB 为单文件,零件色写入材质。
@@ -218,24 +222,171 @@ async function upload() {
218
222
  | `exportAnnotations()` | 上面文档的格式化 JSON 字符串。 |
219
223
  | `setAnnotations(input)` | 用数组 / 文档 / JSON 字符串替换批注列表。 |
220
224
  | `addAnnotation(input)` | 以编程方式添加一条批注,返回新 `Annotation`;功能未开启时返回 `null`。 |
225
+ | `setAnnotationMode(mode)` | `'off'` \| `'place'`:进入 / 退出画布放置模式。`place` 下可连续点模型钉批注(点到已有标记则选中且不新建);新建成功不退出,需 Esc 或再次设为 `'off'`。 |
226
+ | `getAnnotationMode()` | 当前放置模式。 |
227
+ | `selectAnnotation(id)` | 选中批注,或传 `null` 清空;不飞相机。 |
228
+ | `getSelectedAnnotationId()` | 当前选中批注 id。 |
229
+ | `updateAnnotation(id, patch)` | 更新标题 / 说明 / 级别 / 状态等;未开启或找不到时返回 `null`。 |
230
+ | `removeAnnotation(id)` | 删除批注。 |
231
+ | `flyToAnnotation(id)` | 选中并飞到该批注锚点。 |
221
232
 
222
233
  ### 批注(默认关闭)
223
234
 
224
- 批注是**独立旁路数据**:只做锚点渲染与 JSON 序列化,不进入网格、不进入 STL / 3MF / GLB 导出,也不影响诊断结果。默认隐藏,需显式开启:
235
+ 批注是**独立旁路数据**:只做锚点渲染与 JSON 序列化,不进入网格、不进入 STL / 3MF / GLB 导出,也不影响诊断结果。默认隐藏,需显式开启。
236
+
237
+ 能力与自带 UI 分开配置:
238
+
239
+ | 开关 | 作用 |
240
+ | ---- | ---- |
241
+ | `features.annotations` | 画布标记、放置模式、`annotations` prop / 事件、ref API |
242
+ | `features.annotationPanel` | 右栏「检查」里的自带批注 Tab(依赖上一行)。**省略时随 `annotations` 开启**;设 `false` 可只用宿主自定义 UI |
243
+
244
+ #### 示例 1:自带面板(默认行为)
245
+
246
+ 开启批注能力后,右栏出现「检查」页签(诊断 / 批注)。省略 `annotationPanel` 时默认同开。
225
247
 
226
248
  ```vue
227
- <PrintBench
228
- :features="{ annotations: true }"
229
- :annotations="notes"
230
- @annotations-change="notes = $event"
231
- />
249
+ <script setup lang="ts">
250
+ import { ref } from 'vue'
251
+ import { PrintBench } from 'printbench'
252
+ import type { PrintBenchAnnotationsPayload } from 'printbench'
253
+
254
+ /** 受控回写:原样赋回不会回环 */
255
+ const notes = ref<PrintBenchAnnotationsPayload | undefined>()
256
+ </script>
257
+
258
+ <template>
259
+ <PrintBench
260
+ :features="{ annotations: true }"
261
+ :annotations="notes"
262
+ @annotations-change="notes = $event"
263
+ />
264
+ </template>
232
265
  ```
233
266
 
234
- 开启后右栏出现「检查」页签(诊断 / 批注),可点「放置批注」后在模型表面点上标注,支持级别、标题、说明与已解决标记。
267
+ 用户可在面板内点「放置批注」进入连续放置(再点按钮或 Esc 退出);点模型表面可连续钉批注,点到已有标记会选中而非新建。支持级别、标题、说明与已解决标记。也可随时用 `ref` 读写:
235
268
 
236
- 锚点使用**内部毫米世界坐标**(与当前 `upAxis` 一致,默认 Z 轴向上)加 `partId`、命中面法线;零件被移动 / 旋转 / 镜像后仍可按 `partId` 重新挂接,`partId` 消失(拆件 / 布尔 / 合并)时标记为 `orphaned` 并保留原位置显示。
269
+ ```ts
270
+ import type { PrintBenchExpose } from 'printbench'
271
+
272
+ const bench = ref<PrintBenchExpose | null>(null) // 绑到 <PrintBench ref="bench" />
273
+
274
+ bench.value?.exportAnnotations() // JSON 字符串
275
+ bench.value?.setAnnotations(savedDoc) // 整表替换(数组 / 文档 / JSON 字符串均可)
276
+ bench.value?.addAnnotation({
277
+ position: [12.5, 8, -3.25],
278
+ normal: [0, 0, 1],
279
+ partId: 'mesh_xxx',
280
+ title: '壁厚不足',
281
+ body: '建议加厚至 1.2mm',
282
+ severity: 'yellow',
283
+ })
284
+ ```
237
285
 
238
- JSON 结构:
286
+ #### 示例 2:自定义宿主 UI
287
+
288
+ 关掉自带 Tab,保留画布标记与 API,由宿主渲染列表 / 编辑器。
289
+
290
+ ```vue
291
+ <script setup lang="ts">
292
+ import { computed, ref } from 'vue'
293
+ import { PrintBench } from 'printbench'
294
+ import type {
295
+ PrintBenchAnnotationMode,
296
+ PrintBenchAnnotationsPayload,
297
+ PrintBenchExpose,
298
+ } from 'printbench'
299
+
300
+ const bench = ref<PrintBenchExpose | null>(null)
301
+ const notes = ref<PrintBenchAnnotationsPayload | undefined>()
302
+ const selectedId = ref<string | null>(null)
303
+ const hoveredId = ref<string | null>(null)
304
+ const mode = ref<PrintBenchAnnotationMode>('off')
305
+
306
+ const list = computed(() => notes.value?.annotations ?? [])
307
+ const selected = computed(() => list.value.find((a) => a.id === selectedId.value) ?? null)
308
+ const placing = computed(() => mode.value === 'place')
309
+
310
+ function togglePlace() {
311
+ bench.value?.setAnnotationMode(placing.value ? 'off' : 'place')
312
+ }
313
+
314
+ function openNote(id: string) {
315
+ bench.value?.flyToAnnotation(id) // 选中并飞到锚点
316
+ }
317
+
318
+ function saveTitle(title: string) {
319
+ if (!selectedId.value) return
320
+ bench.value?.updateAnnotation(selectedId.value, { title })
321
+ }
322
+
323
+ function markResolved() {
324
+ if (!selectedId.value || !selected.value) return
325
+ const next = selected.value.status === 'resolved' ? 'open' : 'resolved'
326
+ bench.value?.updateAnnotation(selectedId.value, { status: next })
327
+ }
328
+
329
+ function removeSelected() {
330
+ if (!selectedId.value) return
331
+ bench.value?.removeAnnotation(selectedId.value)
332
+ }
333
+ </script>
334
+
335
+ <template>
336
+ <div class="host-review">
337
+ <PrintBench
338
+ ref="bench"
339
+ :features="{ annotations: true, annotationPanel: false }"
340
+ :annotations="notes"
341
+ @annotations-change="notes = $event"
342
+ @annotation-select="selectedId = $event"
343
+ @annotation-mode-change="mode = $event"
344
+ @annotation-click="openNote"
345
+ @annotation-hover="hoveredId = $event"
346
+ />
347
+
348
+ <aside>
349
+ <button type="button" @click="togglePlace">
350
+ {{ placing ? '放置中…(再点退出 / Esc)' : '放置批注' }}
351
+ </button>
352
+ <p v-if="hoveredId">悬停:{{ hoveredId }}</p>
353
+
354
+ <button
355
+ v-for="a in list"
356
+ :key="a.id"
357
+ type="button"
358
+ :class="{ active: a.id === selectedId }"
359
+ @click="openNote(a.id)"
360
+ >
361
+ {{ a.title }}
362
+ <span v-if="a.status === 'resolved'">已解决</span>
363
+ <span v-if="a.orphaned">零件已失效</span>
364
+ </button>
365
+
366
+ <div v-if="selected">
367
+ <input
368
+ :value="selected.title"
369
+ @change="saveTitle(($event.target as HTMLInputElement).value)"
370
+ />
371
+ <button type="button" @click="markResolved">
372
+ {{ selected.status === 'resolved' ? '重新打开' : '标记已解决' }}
373
+ </button>
374
+ <button type="button" @click="removeSelected">删除</button>
375
+ </div>
376
+ </aside>
377
+ </div>
378
+ </template>
379
+ ```
380
+
381
+ 要点:
382
+ - `@annotation-mode-change` 同步放置态;**新建成功不退出** `place`,仅 Esc / 再点按钮 / `setAnnotationMode('off')` 退出
383
+ - `@annotation-click` / `@annotation-hover` 响应画布标记手势;`@annotation-select` 覆盖列表与 API 选中
384
+ - 放置模式下点到已有标记:选中并飞行,**不新建、不退出放置**;点空白模型表面才钉新批注
385
+ - `annotationPanel: false` 时放置成功**不会**弹出右栏诊断浮层
386
+
387
+ #### 数据格式
388
+
389
+ 锚点使用**内部毫米世界坐标**(与当前 `upAxis` 一致,默认 Z 轴向上)加 `partId`、命中面法线;零件被移动 / 旋转 / 镜像后仍可按 `partId` 重新挂接,`partId` 消失(拆件 / 布尔 / 合并)时标记为 `orphaned` 并保留原位置显示。
239
390
 
240
391
  ```json
241
392
  {
@@ -290,7 +441,7 @@ JSON 结构:
290
441
  | --- | ------------------------------------------------------ |
291
442
  | 左栏 | 导入、导出、快照、零件树、上色、移动/旋转/缩放;`features` 打开后才有数值变换、布局(贴地/镜像/缩放/选面朝下)、基本体、拆件、草稿与观察工具 |
292
443
  | 画布 | 轨道旋转缩放;双击或 **F** 自适应;**复位视角** 回默认相机;开启 `layout` 后可在布局区/设置中显示打印床线框(默认隐藏) |
293
- | 右栏 | 诊断与测量浮层(右上,默认 `z-index: 100`),默认收起;开启 `features.annotations` 后标题变为「检查」,多出「批注」页签 |
444
+ | 右栏 | 诊断与测量浮层(右上,默认 `z-index: 100`),默认收起;开启 `features.annotationPanel`(默认随 `annotations`)后标题变为「检查」,多出「批注」页签 |
294
445
  | 底栏 | 包围盒、内部单位 mm、体积、文件名;超床时提示超出轴 |
295
446
 
296
447
 
@@ -313,7 +464,7 @@ JSON 结构:
313
464
  | Ctrl+S | 保存自动草稿(需打开 `features.drafts`) |
314
465
  | 1 / 2 / 3 | 移动 / 旋转 / 缩放 |
315
466
  | P | 切换工作台观察预览 |
316
- | Esc | 取消手柄、预览、测量、选面朝下 |
467
+ | Esc | 取消批注放置、手柄、预览、测量、选面朝下 |
317
468
  | F | 按模型自适应视角 |
318
469
  | Shift | 变换吸附 |
319
470
  | Ctrl | 多选、框选 |
@@ -1,5 +1,5 @@
1
1
  import j from "jszip";
2
- import { h as w, r as R } from "./index-DV7N04zK.js";
2
+ import { h as w, r as R } from "./index-87IMinge.js";
3
3
  function p(s, l) {
4
4
  return s.match(new RegExp(`\\b${l}\\s*=\\s*"([^"]*)"`, "i"))?.[1];
5
5
  }
@@ -1,6 +1,6 @@
1
1
  import { Group as w, Mesh as l, MeshStandardMaterial as u, DoubleSide as h, Color as c } from "three";
2
2
  import { GLTFExporter as p } from "three/examples/jsm/exporters/GLTFExporter.js";
3
- import { b as d } from "./index-DV7N04zK.js";
3
+ import { b as d } from "./index-87IMinge.js";
4
4
  async function G(r, e, n) {
5
5
  const t = new w();
6
6
  t.name = "PrintBench", r.forEach((f, o) => {