Continue FreeCAD Web UI parity repairs
Some checks failed
FreeCAD master CI / Prepare (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
FreeCAD master CI / Pixi (push) Has been cancelled
FreeCAD master CI / Ubuntu (push) Has been cancelled
FreeCAD master CI / Windows (push) Has been cancelled
FreeCAD master CI / Lint (push) Has been cancelled
FreeCAD master CI / WrapUp (push) Has been cancelled
Stale Issues / stale (push) Has been cancelled
CodeQL Advanced (c-cpp) / Analyze (c-cpp) (push) Has been cancelled
Push Crowdin Translations / update-crowdin (push) Has been cancelled
Fetch Crowdin Translations / update-crowdin (push) Has been cancelled
Build Release / upload_src (push) Has been cancelled
Build Release / build (10.13, macos-15-intel, osx-64) (push) Has been cancelled
Build Release / build (11.0, macos-latest, osx-arm64) (push) Has been cancelled
Build Release / build (15.0, macos-latest, osx-arm64) (push) Has been cancelled
Build Release / build (ubuntu-22.04, linux-64) (push) Has been cancelled
Build Release / build (ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Build Release / build (windows-latest, win-64) (push) Has been cancelled
Monthly issue metrics / issue metrics (push) Has been cancelled
Some checks failed
FreeCAD master CI / Prepare (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
FreeCAD master CI / Pixi (push) Has been cancelled
FreeCAD master CI / Ubuntu (push) Has been cancelled
FreeCAD master CI / Windows (push) Has been cancelled
FreeCAD master CI / Lint (push) Has been cancelled
FreeCAD master CI / WrapUp (push) Has been cancelled
Stale Issues / stale (push) Has been cancelled
CodeQL Advanced (c-cpp) / Analyze (c-cpp) (push) Has been cancelled
Push Crowdin Translations / update-crowdin (push) Has been cancelled
Fetch Crowdin Translations / update-crowdin (push) Has been cancelled
Build Release / upload_src (push) Has been cancelled
Build Release / build (10.13, macos-15-intel, osx-64) (push) Has been cancelled
Build Release / build (11.0, macos-latest, osx-arm64) (push) Has been cancelled
Build Release / build (15.0, macos-latest, osx-arm64) (push) Has been cancelled
Build Release / build (ubuntu-22.04, linux-64) (push) Has been cancelled
Build Release / build (ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Build Release / build (windows-latest, win-64) (push) Has been cancelled
Monthly issue metrics / issue metrics (push) Has been cancelled
This commit is contained in:
83
docs/current/CODEX_RESTART_HANDOFF_20260923_1356.md
Normal file
83
docs/current/CODEX_RESTART_HANDOFF_20260923_1356.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Codex 重启接续 — 2026-09-23 13:56 CST
|
||||
|
||||
## 暂停状态
|
||||
|
||||
用户要求“暂停一下 codex,准备接续文件”。已执行:
|
||||
|
||||
```sh
|
||||
systemctl --user stop freecad-web-acceptance.service
|
||||
```
|
||||
|
||||
暂停后确认:`freecad-web-acceptance.service` 为 `inactive/dead`,MainPID=0;没有留下 OpenSCAD ninja、Vite 或 scheduler 子进程。重启 Codex 后不要擅自恢复后台,等待用户要求继续;继续时只恢复这个原单一服务,不启动第二调度器。
|
||||
|
||||
工作区:`/home/mes123456/FreeCADWasmDev`;项目目录:`working/FreeCAD`。
|
||||
|
||||
## 主线铁律
|
||||
|
||||
- 唯一主线:桌面 FreeCAD UI → Web FreeCAD UI 和功能完全对标,覆盖菜单、工具栏、任务/控制面板、属性页、对话框、右键菜单、状态反馈和完整操作者流程。
|
||||
- 按共享工作台、原生文档所有者和构建目标集中处理同类问题,不拆孤立按钮;复用已有 task、facade、owner、原生产物。
|
||||
- 只做源码接线、样式、原生调用和必要增量构建;不运行测试、烟测、浏览器、截图、独立类型检查、验收或证据复核。状态使用 `implemented_unvalidated`;构建成功不是业务完成。
|
||||
- 原生构建固定 `-j8`;前端使用 `python3 tools/build_web_frontend.py`,该 helper 已包含 `web/.freecad-web-frontend-build.lock`,避免调度器与前台同时清理/写入 `web/dist`。
|
||||
- 不把空队列、查缺结果、心跳、烟测 68/68、路由标记或构建成功当全部主线完成。所有共享台账用 `tools/policy_io.py` 原子写入。
|
||||
- 不改动正在执行的包,不重复已完成源码/构建;出现冷却或无任务时沿源码游标轮转,保存下一确切文件和行号。
|
||||
|
||||
## 暂停前实际状态
|
||||
|
||||
最后运行 `./当前执行情况 --details` 的结果:
|
||||
|
||||
- 查询约 2026-09-23 13:56 CST;调度已停止,第 10807 轮。
|
||||
- 停止前第 10807 轮为 Import 源码查缺,不验证;当时没有待完成具体任务,也没有排队任务。
|
||||
- 最后阶段说明:读取 Import 保存的源码索引和接续游标,只写 discovery JSON,不构建、不验证。
|
||||
- 最后命令读取 Import 的 `ImportExportPreferencesTask`、`ReadShapeCompoundMode`、`VisibleImportDialog`、`ExpandCompound`、`UseLinkGroup`、`ImportHidden`、`ReduceObjects`、`ShowProgress`、`KeepPlacement`、`exportHidden` 等入口;该查缺回合在暂停时尚未写出新的结果文件。
|
||||
- 最新已完成查缺文件:`docs/current/operator-gap-discovery/run-000312.json`(OpenSCAD);该文件记录的 OpenSCAD printVerbose residual 已被实现并登记,不能重做。
|
||||
- 状态文件:`docs/current/freecad-web-scheduler-status.json`;最后 live log:`docs/current/continuation-runs/cycle-010807.log`。
|
||||
|
||||
## 最近完成且必须复用
|
||||
|
||||
### OpenSCAD
|
||||
|
||||
第 10806 轮已完成并登记:`operator-gap-openscad-preferences-runtime-bridge`,状态 `implemented_unvalidated`。`importCSG` 在 `open/insert/processcsg` 边界刷新 `printVerbose`,Add/Refresh 路径也显式刷新;原生目标 `FreeCADWebOpenSCADRuntimeProbe` 已用 `ninja ... -j8` 增量构建,前端 helper 已交付。记录保留在 `docs/current/operator-gap-openscad-preferences-runtime-bridge-implementation.json`。不要重建或重复实现。
|
||||
|
||||
### Start
|
||||
|
||||
`docs/current/start-first-start-general-settings-20260923/README.md`:
|
||||
|
||||
- `start-preferences.tsx` 新增 `readStartGeneral`、`saveStartGeneral`、`FirstStartGeneralSettings`、`applyStartTheme`;Language、UserSchema(Int)、NavigationStyle、Theme 复用现有参数 owner。
|
||||
- `start-launch-panel.tsx` 首次启动面板挂载共享 General settings;Done 等待设置读取完成,设置写入时显示 busy/error。
|
||||
- 前端构建退出 0,最后一次约 1.64s;未运行验证。
|
||||
- 仓库暂无独立 Web locale loader,语言参数持久化和刷新事件已接通,但静态 shell 即时翻译仍是明确限制;不要把这项描述成已实现完整本地化。
|
||||
|
||||
### 共享工具栏/Tux
|
||||
|
||||
- `docs/current/desktop-ui-toolbar-preferences-repair-20260923/README.md`:PersistentToolbars root Enabled 与 User FCParam 共同快照、OPFS、广播、失败回滚、重试和焦点恢复。
|
||||
- `docs/current/desktop-ui-toolbar-visibility-20260923/README.md`:工具栏右键/Toolbars 显隐、`BaseApp/MainWindow/Toolbars` Bool 保存、键盘菜单、隐藏宿主、共享锁。
|
||||
- `web/src/toolbar-preferences.ts`、`persistent-toolbar-workspace.tsx`、`toolbar-visibility-menu.tsx` 已完成范围状态为 implemented_unvalidated。
|
||||
|
||||
### Part UI 包
|
||||
|
||||
`operator-gap-ui-part-menu-toolbar-family` 仍为 `open`,但已完成的范围必须复用:
|
||||
|
||||
- 菜单层级、工具栏、桌面 SVG、键盘/Esc/焦点在 `docs/current/desktop-ui-part-entry-repair-20260923/README.md`。
|
||||
- 网格转换、采样、附着、Part DocumentProbe 框选和同文档 New Sketch 已实现;New Sketch 的记录为 `sketch-delivery.md`。
|
||||
- 当前真正剩余:PartDesign/其他 owner 的 `sketcherCommand` 路由、跨 owner 框选、未追溯的快捷键元数据。源码读取阻塞已修复,接续说明在 `source-access-recovery.md`、`owner-routing-blocker.md`。
|
||||
- 不要依据旧 continuation.md 中“尚未挂载 task / New Sketch 未接线”的历史文字重复施工。
|
||||
|
||||
## 工具/执行问题修复
|
||||
|
||||
- `tools/read_implementation_source.py` 允许确认包命名源码的一跳直接前端 import 依赖,仍保留 packet、窗口数、总字节、单窗口和指纹预算;用于解决 Part 跨 owner 读取被错误拒绝的问题。记录:`docs/current/desktop-ui-part-entry-repair-20260923/source-access-recovery.md`。
|
||||
- `tools/prepare_implementation_business_batch.py` 的 sourceAccess 文案已同步该规则;无须重启后台以加载到下一轮,恢复服务后自然读取。
|
||||
- `tools/build_web_frontend.py` 现在用 `web/.freecad-web-frontend-build.lock` 串行化共享 `web/dist` 写入;等待超过 1800 秒返回 124,不反复重启。
|
||||
- 上下文 413 修复仍有效:scheduler session token limit 24000、prompt byte limit 16000,超限保存有界接续,不重复发送超大请求。记录:`docs/current/context-budget-recovery-20260923/README.md`。
|
||||
|
||||
## 恢复后的第一步
|
||||
|
||||
1. 读取本文件、`docs/WEB_BUSINESS_MIGRATION_RULES.md`、`docs/current/execution-policy.json`。
|
||||
2. 确认服务仍为 inactive;用户明确要求继续后,运行 `systemctl --user start freecad-web-acceptance.service`,只恢复原服务。
|
||||
3. 运行 `./当前执行情况 --details`,读取新的 live cycle;不要把旧的 10806 OpenSCAD 或 13:47 快照当当前状态。
|
||||
4. 读取当前 `cycle-*.log` 和新的 Import discovery JSON。若 Import 产生具体 gap,先登记/执行整组包;若为空,沿其 `nextCursor` 轮转,不宣称完成。
|
||||
5. 对任何现有 open key,优先使用其 `continuationNotes`、`lastExecutionCheckpoint` 和记录路径;不重复已完成构建。原生修改才构建受影响目标 `-j8`;前端相关组只调用一次锁定 helper。
|
||||
6. 每轮继续保持不验证。汇报格式:处理模块 | 对标项 | 源码落点 | 路由入口 | OCCT 边界 | 状态 | 结论。
|
||||
|
||||
## 暂停前台账结论
|
||||
|
||||
当前没有已登记的具体 open 任务,但这只是调度队列快照;桌面 UI → Web UI 和功能完全对标**尚未完成**。不要用空队列或 68/68 烟测记录改变完成定义。
|
||||
44
docs/current/desktop-ui-part-entry-repair-20260923/README.md
Normal file
44
docs/current/desktop-ui-part-entry-repair-20260923/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Part 桌面菜单/工具栏入口集中补漏 — 2026-09-23
|
||||
|
||||
状态:入口布局与键盘修改已实现,未验证;原 Part 包的主工作区任务接线仍待完成。
|
||||
|
||||
## 本轮已修改
|
||||
|
||||
`web/src/part-workbench-ui.tsx`:
|
||||
|
||||
- 依据 `freecad-source/src/Mod/Part/Gui/Workbench.cpp::setupMenuBar/setupToolBars`,将 Box Selection 放回首项;Shape From Mesh、Points From Shape、Attachment、材料检查放回顶层,移除额外 Feature/Inspection 子菜单及重复检查命令。
|
||||
- New Sketch 放回 Part 菜单和 Part Tools 工具栏,继续使用既有 activatePartAction。
|
||||
- 从桌面命令定义采用 Primitive、Persistent Section Cut、Points From Shape、New Sketch、Attachment 标签。
|
||||
- 修正 Shape From Mesh 和 Attachment 图标名;复制五个原生 SVG 到 `web/public/icons/part/`:Part_BoxSelection、Part_Shape_from_Mesh、Part_PointsFromMesh、Part_Attachment、Sketcher_NewSketch。材料检查命令在桌面无图标,移除不存在的 SVG 引用并保留菜单图标占位空间。
|
||||
- 整组菜单共用键盘行为:上下键在当前层移动、右键/Enter 进入子菜单、左键/Esc 返回当前父层、Home/End 跳转、Tab 收起并返回入口焦点;打开同级菜单时收起其他菜单,执行命令前恢复入口焦点。
|
||||
|
||||
沿用 `partUiActions → PartWorkbenchMenu/PartWorkbenchToolbars → project-shell.tsx::activatePartAction`。本轮未改几何算法、原生所有者或 Three.js 模型逻辑。
|
||||
|
||||
## 有源码依据的原包残余(不能用已有菜单标记宣称完成)
|
||||
|
||||
在 `web/src/project-shell.tsx::activatePartAction`,shape-from-mesh、points-from-mesh、edit-attachment、box-selection 仅设置 workspaceTask 并请求 read;相应任务组件没有在主工作区挂载。Sketcher_NewSketch 只切换工作台并设置 sketcher-new-sketch,未挂载对应创建流程。
|
||||
|
||||
1. Mesh 转换和几何采样:复用 `part-mesh-conversion-task.tsx`、`part-sample-points-task.tsx` 及 workbench-shell 中现有挂载。增加嵌入方式,使用当前文档快照/选择、同一 nativeRequest 和 applyNativeDocument;保留 tolerance/sew/sampleDistance、真实 Accept/Cancel/错误反馈,不新建 fixture 或第二文档。适配器已有 createShapeFromMesh/createPointSamples 原生请求。
|
||||
2. Attachment:复用 `part-attachment-task.tsx` 和 executePartAttachmentDesktop 的 begin/apply/accept/cancelAttachment 事务,挂载主工作区并保留引用、MapMode、offset/rotation、取消回滚及卸载处理。单纯 read/snapshot 不会启动编辑。
|
||||
3. BoxSelection:复用 WebViewTask/当前视口的区域框选与 selection callback。executePartBoxSelectionDesktop 发出 part.viewCommand 或 part.documentViewCommand,并非 PART_DOCUMENT_COMMAND。当前 runRetainedPartCommand 里的假 router 忽略 command 且一律把 payload.action 送 nativeRequest,不适合此路由;必须按 owner/command 接到真实视图协议。
|
||||
4. New Sketch:接通既有 Sketcher 创建任务和当前文档所有者,明确平面/支持选择、确认、取消与模型树更新;切换工作台和写任务字符串不是创建流程。
|
||||
|
||||
以上属于已有 `operator-gap-ui-part-menu-toolbar-family` 未交付的原范围,回写同一个 key,不新建孤立按钮任务。原有 Tube/Primitives/Tolerance、快捷基本体与布尔路由、已完成菜单布局直接复用。
|
||||
|
||||
## 构建与接续
|
||||
|
||||
- 命令(项目目录):`python3 tools/build_web_frontend.py`。
|
||||
- 日志:`docs/current/desktop-ui-part-entry-repair-20260923/build.log`。
|
||||
- 退出码 0;Vite 日志 built in 1.65s,保留已有大 chunk 提示。
|
||||
- 无原生源码修改,未运行原生构建;无未结束的本轮构建进程。
|
||||
- 未运行测试、浏览器、截图、独立类型检查或验收。构建成功不代表完整功能通过。
|
||||
- 第 10780 轮原调度器正在执行 Material 两个包,本轮只改 Part 独立源码与资源,未启动第二调度器;共享策略通过 tools/policy_io.py 原子更新。
|
||||
|
||||
处理模块 | 对标项 | 源码落点 | 路由入口 | OCCT 边界 | 状态 | 结论
|
||||
--- | --- | --- | --- | --- | --- | ---
|
||||
Part | 菜单层级/工具栏/图标/键盘 | part-workbench-ui.tsx;web/public/icons/part | PartWorkbenchMenu/PartWorkbenchToolbars | 沿用既有 native owner;无算法变更 | 已补漏(未验证) | 保留
|
||||
Part | 主工作区任务/视图协议/草图创建 | project-shell.tsx;既有 Part task 组件和适配器 | activatePartAction/runRetainedPartCommand | 需正确接到现有 Part/Sketcher 文档与视图所有者 | 已定位待接 | 继续
|
||||
|
||||
## 主任务续接更新
|
||||
|
||||
参见 [continuation.md](continuation.md):网格转换、几何采样、附着、框选已接主工作区并完成一次配置前端交付;New Sketch 仍未完成,整包保持 open。
|
||||
@@ -0,0 +1,36 @@
|
||||
# Part 包跨所有者接线:源码读取阻塞补漏
|
||||
|
||||
2026-09-23 本轮依据 `owner-routing-blocker.md` 接续。该记录明确指出两种不同阻塞:
|
||||
|
||||
1. 已有依赖 `web/src/partdesign-document-facade.ts`、`web/src/web-view-task.tsx` 被读取器当作不在模块范围拒绝。
|
||||
2. 已耗尽 16 个源码窗口/48000 字节后继续请求被拒绝。
|
||||
|
||||
第一项是任务包依赖/工具策略缺口;第二项应保存精确接续、进入新有界回合,不能取消预算或循环同一请求。
|
||||
|
||||
## 本轮源码修改
|
||||
|
||||
- `tools/read_implementation_source.py`:已确认功能包可读取其命名源码直接相对 import/from/dynamic import 引用的前端依赖。仅追踪一跳,`.js` 按本仓库惯例映射 `.ts/.tsx`;候选限于 `web/src`,锚点最大 512 KiB,不递归扫描。原窗口数、总字节、每窗上限、同窗指纹去重和调度器预算标记保留。
|
||||
- `tools/prepare_implementation_business_batch.py`:复用包说明同步直接 import 能力,预算用尽后保存下一文件/行,不重读已完成范围。
|
||||
- 原 Part key 的 `sourceAnchors` 补入以下确切依赖,使下一包既可直接读取又保留跨所有者接续边界。没有修改当前正在执行的 ReverseEngineering 查缺包,也没有另启调度器或手工重建当前 packet。
|
||||
|
||||
## 已定位调用链:下一轮直接按这些落点实现
|
||||
|
||||
1. `project-shell.tsx:82` 已 import PartDesign facade,`:61` 已 import WebViewTask。这两个被拒绝的文件都是正式主工作区直接依赖。
|
||||
2. `project-shell.tsx:205` remoteRequest 发送 `freecad-project-native-request`;`workbench-shell.tsx:617` forward 验证来源并按保留 owner 构造 `partdesign.document` / `part.document` payload;不是把对象迁入另一个文档。
|
||||
3. `partdesign-document-facade.ts:1` 是 DesignAction 与请求/结果协议;`partdesign.worker.ts:6` 分派它,加载 `FreeCADWebPartDesignRuntimeProbe`。独立 `partdesign-save.worker.ts` 不是普通文档命令所有者,不必继续读取它。
|
||||
4. `PartDesignRuntimeProbe.cpp:1345` 将 `partdesign.document` 发往 `PartDesignDocumentWorkflow.cpp:661::dispatchPartDesignDocument`。sketcherCommand 必须逐层接通动作合同和真实原生消费者,不能只给 action map 添字符串。
|
||||
5. `module-router.ts:284` 对登记为 Assembly modeling 的文档改派 `assembly.worker.ts`,同名 PartDesign 命令不代表相同 WASM 所有者。沿实际消费者判断所需构建目标,避免以字段名代替支持声明。
|
||||
6. 框选:`web-view-task.tsx:23` 接收 runtimeOwner;`part-view-facade.ts` 的公开 ViewOwner 当前只有 PartBox/DocumentProbe,协议检查也仅接受这两个。扩展所有者必须追到 native endpoint;不能将 PartDesign 文档标成 DocumentProbe 读取另一份同名文档。
|
||||
|
||||
## 复用与真实剩余
|
||||
|
||||
New Sketch 的 Part DocumentProbe 原生路由、已完成链接、创建/关闭控件,以及 Appearance per Face 的 View 入口已由此前记录交付。复用 `sketch-delivery.md`、`owner-routing-blocker.md` 和已有日志,不重复构建/实现。
|
||||
|
||||
原 Part 包继续 open,实际剩余是其他文档所有者 sketch 分派、跨所有者框选和未追溯的快捷键元数据。本轮解决读取阻塞并提供调用链,不宣称这些业务接线已经完成。
|
||||
|
||||
无需编译:本轮仅改 Python 执行工具与接续元数据;脚本由后续调用加载,无需重启后台。未执行测试、语法检查、业务验证、浏览器、截图或构建,无本轮未结束进程。原服务在第 10791 轮继续 ReverseEngineering 源码查缺。
|
||||
|
||||
处理模块 | 对标项 | 源码落点 | 路由入口 | OCCT 边界 | 状态 | 结论
|
||||
--- | --- | --- | --- | --- | --- | ---
|
||||
Part 接续工具 | 跨工作台精确依赖读取 | read_implementation_source.py;prepare_implementation_business_batch.py | 原单一调度器读取工具 | 不改模型算法 | 已补漏(未验证) | 保留
|
||||
Part 跨所有者 UI | 草图/框选的真实文档调用链 | project-shell → facade/worker → native owner | 已保存精确落点 | 继续复用实际 WASM 消费者 | 已定位待接 | 继续
|
||||
@@ -0,0 +1,46 @@
|
||||
# 共享工具栏偏好持久化与失败恢复补漏
|
||||
|
||||
状态:本次具体缺口已实现,未验证。属于原任务 `operator-gap-tux-persistent-toolbars-enabled-startup`,不新增孤立任务。
|
||||
|
||||
## 源码差异与修改
|
||||
|
||||
桌面依据:`freecad-source/src/Mod/Tux/InitGui.py` 以 `Tux/PersistentToolbars/Enabled` 原生 Bool 决定是否加载持久工具栏,缺省为 true。
|
||||
|
||||
原 Web 实现虽然在 restoreToolbars/saveToolbars 中读取 Enabled,但 `toolbarParameters` 只导出/恢复 User 子组。此前任务记录声称快照已携带根 Bool,与本轮读取到的代码不一致。只有偏好开关读写路由,无法保证关闭后重载仍关闭。
|
||||
|
||||
本次在 `web/src/toolbar-preferences.ts` 集中修复:
|
||||
|
||||
- 共同保存根 Enabled 和原生 User FCParam 内容。内部快照带 schema,保存为 `Std_DlgParameter-PersistentToolbars.json`,继续使用现有 OPFS saved/persist 和广播通道,不向用户暴露 JSON 操作。
|
||||
- 首次恢复优先读新快照,兼容已有 `Std_DlgParameter-PersistentToolbars.FCParam`;旧 User-only 快照不覆盖当前原生 Enabled。开关缺省、显式 false/0 均按原生语义处理。
|
||||
- 导入、保存失败时恢复 User 和 Enabled。快照不导出 System/addTop/addLeft/addRight/addBottom,因此这些原生会话预设保留。
|
||||
- 仅快照真正变化时落盘/广播。关闭持久化后临时调整布局不会写入保存布局,也不再触发一次无意义的广播刷新将临时排列立刻恢复。
|
||||
- 原生请求仍使用 requestOwnerParameters;保存、参数面板和恢复共享同一串行参数入口。
|
||||
|
||||
在 `web/src/persistent-toolbar-workspace.tsx` 补齐同一工作流:
|
||||
|
||||
- 偏好恢复失败后提供 Retry toolbar preferences,避免按钮一直 disabled 且只能刷新页面。
|
||||
- 每次恢复清除旧错误,沿用 revision 防止旧异步回调覆盖新文档/工作台。
|
||||
- 排列面板打开时聚焦首个控件;Cancel/Escape 返回 Arrange toolbars 按钮。
|
||||
- 旧工作台保存失败不覆盖当前工作台反馈。
|
||||
|
||||
## 复用与边界
|
||||
|
||||
复用包:
|
||||
1. 既有原生 Enabled 默认值和布局排序/Break 逻辑,记录 `docs/current/operator-gap-tux-persistent-toolbars-enabled-startup-20260921/README.md`。
|
||||
2. 既有 `view-navigation-preferences.ts::requestOwnerParameters`、OPFS saved/persist 及工具栏广播通道。
|
||||
3. 既有 `persistent-toolbar-workspace.tsx` 的宿主、revision 和布局预览;不改变建模文档、任务草稿或历史。
|
||||
|
||||
路由:Std_DlgParameter → requestToolbarParameters → toolbarParameters → requestOwnerParameters → 已有 DocumentProbe/PartBox 原生参数接口。这里只处理 UI 偏好,无几何算法/OCCT/Three.js 计算变更。
|
||||
|
||||
## 本轮构建和接续
|
||||
|
||||
- 修改位置:上述两个 Web 源文件。无原生源码修改,无需原生构建。
|
||||
- 必要前端构建:项目目录执行 `python3 tools/build_web_frontend.py`;使用现有 web 配置、依赖与快速链接产物。
|
||||
- 日志:`docs/current/desktop-ui-toolbar-preferences-repair-20260923/build.log`;退出码 0;built in 1.63s,保留已有 chunk 大小提示。
|
||||
- 未执行测试、类型检查、烟测、浏览器、截图或验收;不以构建成功宣称全部 UI 对标完成。
|
||||
- 无本轮遗留构建进程。原单一调度器第 10783 轮继续处理 Part 当前文档 New Sketch,未启动第二调度器,未修改其正在开发的 project-shell 或原生源码。
|
||||
- 本范围后续直接复用;剩余整体验收等待用户明确授权,不能自动启动。后续功能按原调度器队列继续。
|
||||
|
||||
处理模块 | 对标项 | 源码落点 | 路由入口 | OCCT 边界 | 状态 | 结论
|
||||
--- | --- | --- | --- | --- | --- | ---
|
||||
共享工具栏/Tux | Enabled 与布局保存、失败回滚、重试与焦点 | toolbar-preferences.ts;persistent-toolbar-workspace.tsx | Std_DlgParameter 与既有 toolbarParameters | 仅原生参数;无几何变更 | 已补漏(未验证) | 保留
|
||||
@@ -0,0 +1,362 @@
|
||||
vite v8.2.1 building client environment for production...
|
||||
Public assets synchronized incrementally in 0.056s
|
||||
[2K
|
||||
transforming...✓ 2448 modules transformed.
|
||||
rendering chunks...
|
||||
computing gzip size...
|
||||
dist/assets/dynamic-worker-bootstrap-CX1JKGrb.js 0.73 kB
|
||||
dist/part-inspect.html 1.04 kB │ gzip: 0.48 kB
|
||||
dist/partdesign-save.html 1.04 kB │ gzip: 0.44 kB
|
||||
dist/sketcher-inspect.html 1.05 kB │ gzip: 0.49 kB
|
||||
dist/part.html 1.10 kB │ gzip: 0.49 kB
|
||||
dist/partdesign-inspect.html 1.11 kB │ gzip: 0.50 kB
|
||||
dist/part-io.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/points.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/sandbox.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/surface.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/start.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/sketcher.html 1.12 kB │ gzip: 0.49 kB
|
||||
dist/robot-save.html 1.12 kB │ gzip: 0.50 kB
|
||||
dist/meshpart.html 1.12 kB │ gzip: 0.50 kB
|
||||
dist/robot-create.html 1.13 kB │ gzip: 0.49 kB
|
||||
dist/robot-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/show-inspect.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/spreadsheet.html 1.13 kB │ gzip: 0.49 kB
|
||||
dist/part-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/show-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/sandbox-save.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/robot-inspect.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/templatepymod.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/start-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/sandbox-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/surface-save.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/points-save.html 1.14 kB │ gzip: 0.51 kB
|
||||
dist/sketcher-python.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/surface-python.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/sandbox-inspect.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/surface-inspect.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/start-inspect.html 1.14 kB │ gzip: 0.51 kB
|
||||
dist/partdesign-python.html 1.15 kB │ gzip: 0.51 kB
|
||||
dist/spreadsheet-save.html 1.15 kB │ gzip: 0.50 kB
|
||||
dist/spreadsheet-python.html 1.15 kB │ gzip: 0.50 kB
|
||||
dist/sketcher-persistence.html 1.15 kB │ gzip: 0.51 kB
|
||||
dist/templatepymod-python.html 1.16 kB │ gzip: 0.51 kB
|
||||
dist/spreadsheet-inspect.html 1.16 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-save.html 1.16 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-create.html 1.17 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-python.html 1.17 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-inspect.html 1.17 kB │ gzip: 0.51 kB
|
||||
dist/mesh.html 1.20 kB │ gzip: 0.51 kB
|
||||
dist/part-save.html 1.21 kB │ gzip: 0.51 kB
|
||||
dist/measure.html 1.22 kB │ gzip: 0.51 kB
|
||||
dist/material.html 1.23 kB │ gzip: 0.52 kB
|
||||
dist/jtreader.html 1.23 kB │ gzip: 0.52 kB
|
||||
dist/fem.html 1.26 kB │ gzip: 0.53 kB
|
||||
dist/core.html 1.26 kB │ gzip: 0.52 kB
|
||||
dist/document.html 1.26 kB │ gzip: 0.52 kB
|
||||
dist/techdraw-inspect.html 1.26 kB │ gzip: 0.53 kB
|
||||
dist/techdraw.html 1.27 kB │ gzip: 0.52 kB
|
||||
dist/openscad.html 1.31 kB │ gzip: 0.54 kB
|
||||
dist/tux.html 1.32 kB │ gzip: 0.53 kB
|
||||
dist/test.html 1.33 kB │ gzip: 0.53 kB
|
||||
dist/web.html 1.33 kB │ gzip: 0.53 kB
|
||||
dist/techdraw-native.html 1.35 kB │ gzip: 0.54 kB
|
||||
dist/techdraw-save.html 1.35 kB │ gzip: 0.54 kB
|
||||
dist/techdraw-python.html 1.35 kB │ gzip: 0.54 kB
|
||||
dist/web-persistence.html 1.36 kB │ gzip: 0.55 kB
|
||||
dist/points-python.html 1.37 kB │ gzip: 0.57 kB
|
||||
dist/points-inspect.html 1.38 kB │ gzip: 0.58 kB
|
||||
dist/help.html 1.39 kB │ gzip: 0.57 kB
|
||||
dist/python.html 1.44 kB │ gzip: 0.57 kB
|
||||
dist/bim.html 1.45 kB │ gzip: 0.57 kB
|
||||
dist/import.html 1.46 kB │ gzip: 0.57 kB
|
||||
dist/draft.html 1.46 kB │ gzip: 0.58 kB
|
||||
dist/inspection.html 1.48 kB │ gzip: 0.57 kB
|
||||
dist/cloud.html 1.49 kB │ gzip: 0.56 kB
|
||||
dist/plot-python.html 1.59 kB │ gzip: 0.60 kB
|
||||
dist/cam.html 1.61 kB │ gzip: 0.59 kB
|
||||
dist/assembly.html 1.64 kB │ gzip: 0.60 kB
|
||||
dist/partdesign.html 2.27 kB │ gzip: 0.73 kB
|
||||
dist/project.html 3.59 kB │ gzip: 0.99 kB
|
||||
dist/index.html 3.74 kB │ gzip: 1.00 kB
|
||||
dist/assets/robot-inspect.worker-CVODVnCd.js 4.53 kB
|
||||
dist/assets/web-persistence.worker-2Zxcj3QT.js 4.55 kB
|
||||
dist/assets/start-python.worker-B5vaxGty.js 4.56 kB
|
||||
dist/assets/tux.worker-BOg1krec.js 4.58 kB
|
||||
dist/assets/sketcher-inspect.worker-C9AvvwhY.js 4.59 kB
|
||||
dist/assets/sketcher-persistence.worker-CfeXMZbg.js 4.62 kB
|
||||
dist/assets/robot-create.worker-DPChGyOR.js 4.66 kB
|
||||
dist/assets/templatepymod.worker-hg1ejMJB.js 4.67 kB
|
||||
dist/assets/surface-inspect.worker-F-GdNdw_.js 4.72 kB
|
||||
dist/assets/surface-save.worker-CUtK-04F.js 4.74 kB
|
||||
dist/assets/python-runtime.worker-B-aJYkJT.js 4.76 kB
|
||||
dist/assets/techdraw-inspect.worker-xPT-ga1B.js 4.77 kB
|
||||
dist/assets/web-dock-overlay.worker-BCsmLfKX.js 4.79 kB
|
||||
dist/assets/sandbox-save.worker-DCebp-Q8.js 4.83 kB
|
||||
dist/assets/robot-save.worker-B9iH5YAv.js 4.89 kB
|
||||
dist/assets/show-inspect.worker-B2QD7gst.js 4.90 kB
|
||||
dist/assets/techdraw-save.worker-sojHDFzQ.js 4.91 kB
|
||||
dist/assets/start-inventory.worker-CFKlvZmC.js 4.96 kB
|
||||
dist/assets/sandbox-inspect.worker-C5YazrBn.js 5.03 kB
|
||||
dist/assets/spreadsheet-save.worker-BiNG1wGg.js 5.04 kB
|
||||
dist/assets/partdesign-inspect.worker-DeojPCWC.js 5.07 kB
|
||||
dist/assets/reverseengineering-save.worker-CgUN2OLj.js 5.07 kB
|
||||
dist/assets/partdesign-save.worker-gVh5xTPd.js 5.10 kB
|
||||
dist/assets/reverseengineering-inspect.worker-CpKjB0Ej.js 5.12 kB
|
||||
dist/assets/reverseengineering-create.worker-BnYvMNcQ.js 5.14 kB
|
||||
dist/assets/spreadsheet-inspect.worker-Qa8cffR8.js 5.31 kB
|
||||
dist/assets/techdraw-native.worker-DzdTH1-p.js 5.43 kB
|
||||
dist/assets/points-persistence.worker-Q2RjUTfj.js 5.66 kB
|
||||
dist/full.html 5.66 kB │ gzip: 1.26 kB
|
||||
dist/assets/techdraw.worker-mfYkJLI7.js 5.74 kB
|
||||
dist/assets/surface.worker-CxzaO0n3.js 5.75 kB
|
||||
dist/main-chain.html 5.90 kB │ gzip: 1.32 kB
|
||||
dist/assets/web.worker-C7hjw4lI.js 6.19 kB
|
||||
dist/assets/help.worker-_0RHyS-0.js 6.23 kB
|
||||
dist/assets/sandbox.worker-Q8ky_HwA.js 6.70 kB
|
||||
dist/assets/spreadsheet.worker-Bbehg6Py.js 6.74 kB
|
||||
dist/assets/workbench-command.worker-BQ7_z6oJ.js 6.95 kB
|
||||
dist/assets/start.worker-CIavZLau.js 6.98 kB
|
||||
dist/assets/plot-python.worker-DtIDsfZF.js 6.98 kB
|
||||
dist/assets/sketcher.worker-DF02tOmg.js 7.12 kB
|
||||
dist/assets/reverseengineering-python.worker-DKZTinnr.js 7.36 kB
|
||||
dist/assets/spreadsheet-python.worker-DMdejRPT.js 7.63 kB
|
||||
dist/assets/templatepymod-python.worker-PsnxyRD5.js 7.81 kB
|
||||
dist/assets/robot-python.worker-CtPujTTt.js 8.96 kB
|
||||
dist/assets/meshpart.worker-DZfzSxZ7.js 9.09 kB
|
||||
dist/workbench.html 9.98 kB │ gzip: 1.93 kB
|
||||
dist/assets/surface-python.worker-DSo_Egxm.js 10.79 kB
|
||||
dist/assets/show-python.worker-DPiddEMC.js 10.89 kB
|
||||
dist/assets/import.worker-C-AQ4J_x.js 12.43 kB
|
||||
dist/assets/partdesign-python.worker-ydIZ9rk7.js 12.48 kB
|
||||
dist/assets/fem.worker-DGQs784X.js 12.82 kB
|
||||
dist/assets/sandbox-python.worker-DQsLVBHZ.js 12.84 kB
|
||||
dist/assets/cloud.worker-DNuING5d.js 13.68 kB
|
||||
dist/assets/inspection.worker-Bk6WfE00.js 13.99 kB
|
||||
dist/assets/cam.worker-we3i0GjL.js 14.42 kB
|
||||
dist/assets/sketcher-python.worker-DCmXcCgC.js 14.45 kB
|
||||
dist/assets/material-python.worker-BcGZQmQt.js 14.67 kB
|
||||
dist/assets/material.worker-BcGZQmQt.js 14.67 kB
|
||||
dist/assets/draft.worker-CV1zzRV7.js 15.11 kB
|
||||
dist/assets/test.worker-CE1QL_n0.js 15.75 kB
|
||||
dist/assets/measure.worker-xIEFKJEl.js 15.96 kB
|
||||
dist/assets/openscad.worker-DrNMAW2-.js 19.09 kB
|
||||
dist/assets/points.worker-9rmxQC5I.js 19.81 kB
|
||||
dist/assets/mesh.worker-DtnBKzpD.js 20.93 kB
|
||||
dist/assets/techdraw-python.worker-DpV5IJ2G.js 21.30 kB
|
||||
dist/assets/bim.worker-B0fOxOsJ.js 22.85 kB
|
||||
dist/assets/partdesign.worker-kFU8GaIi.js 32.30 kB
|
||||
dist/assets/assembly.worker-Dw6ddz-J.js 34.39 kB
|
||||
dist/assets/core.worker-u_jdRzAP.js 35.22 kB
|
||||
dist/assets/PartDesignExample-BpwNhVch.FCStd 49.34 kB
|
||||
dist/assets/part-save.worker-C05hXSGZ.js 62.17 kB
|
||||
dist/assets/part.worker-D61crV2p.js 80.29 kB
|
||||
dist/assets/EngineBlock-CVb0N6vw.FCStd 92.45 kB
|
||||
dist/assets/jtreader.worker-C8dK-7Jp.js 95.78 kB
|
||||
dist/assets/draft_test_objects-Ce4A2NfX.FCStd 113.95 kB
|
||||
dist/assets/AssemblyExample-Hf3Yo9S1.FCStd 173.57 kB
|
||||
dist/assets/FEMExample-HgXO02sk.FCStd 528.07 kB
|
||||
dist/assets/Schenkel-CxpzxKld.stp 590.05 kB
|
||||
dist/assets/ArchDetail-jj9o7oVJ.FCStd 1,876.08 kB
|
||||
dist/assets/BIMExample-47hOGPj8.FCStd 2,072.78 kB
|
||||
dist/assets/workbench-DtksdrdB.css 2.11 kB │ gzip: 0.74 kB
|
||||
dist/assets/partdesign-workspace-component-B0OVOYiL.css 2.38 kB │ gzip: 0.76 kB
|
||||
dist/assets/partdesign-c4C7lohl.css 4.62 kB │ gzip: 1.55 kB
|
||||
dist/assets/play-D-_ZNeFC.js 0.12 kB │ gzip: 0.12 kB
|
||||
dist/assets/show-python-family-facade-DMqlrdIP.js 0.19 kB │ gzip: 0.14 kB
|
||||
dist/assets/show-python-facade-ButUz4Qy.js 0.24 kB │ gzip: 0.20 kB
|
||||
dist/assets/file-input-D8HGScs0.js 0.24 kB │ gzip: 0.16 kB
|
||||
dist/assets/core-runtime-NDNIHpBU.js 0.24 kB │ gzip: 0.17 kB
|
||||
dist/assets/file-search-DnMZlG9r.js 0.26 kB │ gzip: 0.18 kB
|
||||
dist/assets/mesh-persistence-facade-DS9ndFlX.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/robot-create-facade-DsFLU8rr.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/surface-save-facade-DqYkLu2D.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/cloud-persistence-facade-slrLU7xE.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/points-persistence-facade-DVLoCS9l.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/robot-save-facade-46s9sMyB.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/part-python-facade-DuAK0FpU.js 0.27 kB │ gzip: 0.21 kB
|
||||
dist/assets/start-python-facade-B1CMhCGP.js 0.27 kB │ gzip: 0.21 kB
|
||||
dist/assets/route-BjiIcSqv.js 0.27 kB │ gzip: 0.17 kB
|
||||
dist/assets/measure-persistence-facade-Bcss4aJL.js 0.27 kB │ gzip: 0.22 kB
|
||||
dist/assets/robot-python-facade-q2JVYdPx.js 0.27 kB │ gzip: 0.22 kB
|
||||
dist/assets/web-facade-Ck29HNBt.js 0.27 kB │ gzip: 0.22 kB
|
||||
dist/assets/partdesign-save-facade-B34V-F79.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/points-python-facade-5Lmbu8kQ.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/jtreader-persistence-facade-CuW_tMqV.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/sandbox-python-facade-D7HVYX_W.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/surface-python-facade-8DAhyuZd.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/spreadsheet-save-facade--Pk6YEHd.js 0.28 kB │ gzip: 0.21 kB
|
||||
dist/assets/material-persistence-facade-MroMCUFU.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/part-facade-Dqdz2CMv.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/measure-python-facade-DXH12EoV.js 0.29 kB │ gzip: 0.21 kB
|
||||
dist/assets/sketcher-python-facade-s_09asE0.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/openscad-python-facade-CeB9lZUw.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/techdraw-python-facade-BtOLupRJ.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/tux-facade-D2bVxp8w.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/inspection-persistence-facade-nvVV4ARG.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/sketcher-persistence-facade-BDuI9FpW.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/points-facade-BXGQ-Ppz.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/techdraw-save-facade-BvoReCX8.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/part-save-facade-QekuosWe.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/web-persistence-facade-BoD2jJgN.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/part-io-facade-CbFxtVBz.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/show-inspect-facade-B7fdNKtX.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/partdesign-python-facade-CO-vQEgQ.js 0.31 kB │ gzip: 0.22 kB
|
||||
dist/assets/points-inspect-facade-BEeDt8S-.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/sandbox-facade-D7dzZFLP.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/sandbox-save-facade-Ct4d_E5A.js 0.31 kB │ gzip: 0.24 kB
|
||||
dist/assets/sketcher-facade-NnwQipJc.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/techdraw-facade-aagF3oSr.js 0.31 kB │ gzip: 0.25 kB
|
||||
dist/assets/templatepymod-python-facade-COub4Pml.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/robot-inspect-facade-DtM6g5eT.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/start-inventory-facade-B8L6WXtA.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/reverseengineering-save-facade-jaDJAoek.js 0.32 kB │ gzip: 0.22 kB
|
||||
dist/assets/partdesign-DkRY68_C.js 0.32 kB │ gzip: 0.25 kB
|
||||
dist/assets/surface-facade-BkxAqFEl.js 0.32 kB │ gzip: 0.23 kB
|
||||
dist/assets/spreadsheet-python-facade-DNQ06kqq.js 0.32 kB │ gzip: 0.23 kB
|
||||
dist/assets/surface-inspect-facade--FM1PBKH.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/reverseengineering-create-facade-_gePfDI9.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/fem-facade-CTWbg8W4.js 0.33 kB │ gzip: 0.26 kB
|
||||
dist/assets/sandbox-inspect-facade-ChelQkD0.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/draft-facade-DuQJqsW2.js 0.33 kB │ gzip: 0.26 kB
|
||||
dist/assets/templatepymod-facade-BIEatdOS.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/techdraw-native-facade-DFqX--Jm.js 0.34 kB │ gzip: 0.26 kB
|
||||
dist/assets/openscad-inspect-facade-CD1x9jF-.js 0.35 kB │ gzip: 0.25 kB
|
||||
dist/assets/reverseengineering-python-facade-D0ETOYnk.js 0.35 kB │ gzip: 0.23 kB
|
||||
dist/assets/spreadsheet-inspect-facade-Be9KXsQS.js 0.37 kB │ gzip: 0.25 kB
|
||||
dist/assets/reverseengineering-inspect-facade-D4Ki188U.js 0.40 kB │ gzip: 0.24 kB
|
||||
dist/assets/start-facade-CUkdj_rn.js 0.43 kB │ gzip: 0.26 kB
|
||||
dist/assets/inspection-facade-CBfFciGw.js 0.43 kB │ gzip: 0.28 kB
|
||||
dist/assets/save-DdXNGgDu.js 0.44 kB │ gzip: 0.21 kB
|
||||
dist/assets/cam-persistence-facade-BubrF1Zi.js 0.47 kB │ gzip: 0.31 kB
|
||||
dist/assets/spreadsheet-facade-LrDmQfdq.js 0.49 kB │ gzip: 0.31 kB
|
||||
dist/assets/help-python-facade-DtavuPjQ.js 0.51 kB │ gzip: 0.31 kB
|
||||
dist/assets/rotate-ccw-DUGzGpVv.js 0.54 kB │ gzip: 0.38 kB
|
||||
dist/assets/bim-python-facade-b1qXNzBY.js 0.54 kB │ gzip: 0.31 kB
|
||||
dist/assets/facade-envelope-D0Aixh7X.js 0.61 kB │ gzip: 0.30 kB
|
||||
dist/assets/test-facade-8wF3KR_U.js 0.62 kB │ gzip: 0.35 kB
|
||||
dist/assets/boxes-v6_sbvwg.js 0.63 kB │ gzip: 0.26 kB
|
||||
dist/assets/plot-python-Dnb4yZAX.js 0.65 kB │ gzip: 0.43 kB
|
||||
dist/assets/jsx-runtime-CriDp1zE.js 0.66 kB │ gzip: 0.43 kB
|
||||
dist/assets/modulepreload-polyfill-P2Xu9kJm.js 0.67 kB │ gzip: 0.38 kB
|
||||
dist/assets/jtreader-inspect-facade-MuD5LO2G.js 0.67 kB │ gzip: 0.38 kB
|
||||
dist/assets/mesh-python-facade-BqFrGnKZ.js 0.77 kB │ gzip: 0.40 kB
|
||||
dist/assets/assembly-python-facade-lnqmT0xQ.js 0.78 kB │ gzip: 0.38 kB
|
||||
dist/assets/meshpart-python-facade-C0BClkPM.js 0.83 kB │ gzip: 0.39 kB
|
||||
dist/assets/cam-python-facade-B7bklSZs.js 0.86 kB │ gzip: 0.46 kB
|
||||
dist/assets/material-python-facade-D9fGPPR7.js 0.90 kB │ gzip: 0.44 kB
|
||||
dist/assets/cloud-python-facade-BnucAy4n.js 0.96 kB │ gzip: 0.52 kB
|
||||
dist/assets/export-service-3DLpdk4G.js 0.99 kB │ gzip: 0.57 kB
|
||||
dist/assets/import-python-facade-BSX0J_vZ.js 1.16 kB │ gzip: 0.53 kB
|
||||
dist/assets/preload-helper-Czpn1I53.js 1.19 kB │ gzip: 0.67 kB
|
||||
dist/assets/assembly-persistence-facade-Dn6bIi3m.js 1.30 kB │ gzip: 0.70 kB
|
||||
dist/assets/points-python-BMRX_Ssp.js 1.43 kB │ gzip: 0.65 kB
|
||||
dist/assets/points-inspect-Db6j5N-G.js 1.45 kB │ gzip: 0.66 kB
|
||||
dist/assets/draft-stretch-controls-l_shOKCY.js 1.48 kB │ gzip: 0.74 kB
|
||||
dist/assets/recovery-controller-O2kjgylK.js 1.54 kB │ gzip: 0.71 kB
|
||||
dist/assets/partdesign-save-CbeoS6zN.js 1.77 kB │ gzip: 0.73 kB
|
||||
dist/assets/openscad-format-facade-CDmXl5v1.js 1.99 kB │ gzip: 0.87 kB
|
||||
dist/assets/bim-panel-ifc-task-33ENMpVH.js 2.02 kB │ gzip: 0.95 kB
|
||||
dist/assets/lucide-Bj4YvMR1.js 2.10 kB │ gzip: 1.14 kB
|
||||
dist/assets/app-services-Cdf3CnXp.js 2.21 kB │ gzip: 1.26 kB
|
||||
dist/assets/spreadsheet-save-DpOiejKi.js 2.38 kB │ gzip: 1.03 kB
|
||||
dist/assets/openscad-desktop-adapter-CHdUZLWA.js 2.91 kB │ gzip: 1.24 kB
|
||||
dist/assets/test-FWLm90kJ.js 3.34 kB │ gzip: 1.47 kB
|
||||
dist/assets/web-Bm2cXYR8.js 3.35 kB │ gzip: 1.48 kB
|
||||
dist/assets/bim-panel-structure-task-BGdyN2BA.js 3.38 kB │ gzip: 1.24 kB
|
||||
dist/assets/tux-D80RpCwQ.js 3.39 kB │ gzip: 1.49 kB
|
||||
dist/assets/part-save--7-bwKEF.js 3.43 kB │ gzip: 1.52 kB
|
||||
dist/assets/part-python-CgyPLzBK.js 3.44 kB │ gzip: 1.53 kB
|
||||
dist/assets/part-io-BtZUXrip.js 3.46 kB │ gzip: 1.54 kB
|
||||
dist/assets/web-action-03-Dr7GSCtb.js 3.53 kB │ gzip: 1.53 kB
|
||||
dist/assets/part-BkXJyIbn.js 3.54 kB │ gzip: 1.58 kB
|
||||
dist/assets/techdraw-save-C_vsQmHN.js 3.54 kB │ gzip: 1.55 kB
|
||||
dist/assets/robot-python-CYNqMOnV.js 3.54 kB │ gzip: 1.62 kB
|
||||
dist/assets/techdraw-python-DvetJb7U.js 3.57 kB │ gzip: 1.53 kB
|
||||
dist/assets/points-DN0w01T1.js 3.58 kB │ gzip: 1.59 kB
|
||||
dist/assets/ui-accessibility-ZOglN_8v.js 3.60 kB │ gzip: 1.67 kB
|
||||
dist/assets/techdraw-native-_QNwBGlH.js 3.62 kB │ gzip: 1.56 kB
|
||||
dist/assets/robot-inspect-CYa2be4E.js 3.67 kB │ gzip: 1.65 kB
|
||||
dist/assets/sandbox-python-BCGBxJqf.js 3.68 kB │ gzip: 1.62 kB
|
||||
dist/assets/sketcher-python-BL2_OJ2j.js 3.69 kB │ gzip: 1.62 kB
|
||||
dist/assets/part-inspect-0SExly6v.js 3.72 kB │ gzip: 1.63 kB
|
||||
dist/assets/sketcher-D2K5ZZoZ.js 3.72 kB │ gzip: 1.61 kB
|
||||
dist/assets/templatepymod-D8K3ra-l.js 3.76 kB │ gzip: 1.68 kB
|
||||
dist/assets/sandbox-C4VUMo8D.js 3.77 kB │ gzip: 1.62 kB
|
||||
dist/assets/show-python-DacsS6fQ.js 3.77 kB │ gzip: 1.67 kB
|
||||
dist/assets/templatepymod-python-CWQMDm0W.js 3.78 kB │ gzip: 1.68 kB
|
||||
dist/assets/spreadsheet-5hm81bzD.js 3.81 kB │ gzip: 1.67 kB
|
||||
dist/assets/surface-python-BZKBmjcs.js 3.82 kB │ gzip: 1.71 kB
|
||||
dist/assets/robot-create-CWrpDvJA.js 3.82 kB │ gzip: 1.69 kB
|
||||
dist/assets/sandbox-save-Bk1WFx3P.js 3.83 kB │ gzip: 1.66 kB
|
||||
dist/assets/storage-BcICQHZX.js 3.85 kB │ gzip: 1.30 kB
|
||||
dist/assets/robot-save-_Y-zT22T.js 3.85 kB │ gzip: 1.72 kB
|
||||
dist/assets/techdraw-inspect-BSrApN_Q.js 3.86 kB │ gzip: 1.63 kB
|
||||
dist/assets/partdesign-python-Bf3-NqsC.js 3.87 kB │ gzip: 1.66 kB
|
||||
dist/assets/show-inspect-CwJha_c4.js 3.88 kB │ gzip: 1.73 kB
|
||||
dist/assets/points-save-CGe_EUOx.js 3.91 kB │ gzip: 1.67 kB
|
||||
dist/assets/sandbox-inspect-Dja0gG9n.js 3.91 kB │ gzip: 1.69 kB
|
||||
dist/assets/surface-save-CBdaDQb7.js 3.92 kB │ gzip: 1.72 kB
|
||||
dist/assets/sketcher-persistence-0PpuZ_0a.js 3.93 kB │ gzip: 1.68 kB
|
||||
dist/assets/start-WtivU9aJ.js 3.94 kB │ gzip: 1.74 kB
|
||||
dist/assets/start-python-D3PwrGwY.js 3.95 kB │ gzip: 1.73 kB
|
||||
dist/assets/spreadsheet-python-Cd-0coHp.js 3.95 kB │ gzip: 1.74 kB
|
||||
dist/assets/surface-inspect-Bh4WXF9o.js 3.96 kB │ gzip: 1.74 kB
|
||||
dist/assets/start-inspect-CsarTd3K.js 3.98 kB │ gzip: 1.75 kB
|
||||
dist/assets/surface-BMuAvYRi.js 3.98 kB │ gzip: 1.73 kB
|
||||
dist/assets/reverseengineering-python-BrLJGi9x.js 4.00 kB │ gzip: 1.78 kB
|
||||
dist/assets/reverseengineering-create-HZcw3dYz.js 4.08 kB │ gzip: 1.79 kB
|
||||
dist/assets/reverseengineering-save-FzGCucym.js 4.09 kB │ gzip: 1.81 kB
|
||||
dist/assets/spreadsheet-inspect-dFA6Vq6g.js 4.11 kB │ gzip: 1.77 kB
|
||||
dist/assets/sketcher-inspect-Wm52KgOO.js 4.11 kB │ gzip: 1.77 kB
|
||||
dist/assets/reverseengineering-inspect-8nav7qXB.js 4.20 kB │ gzip: 1.82 kB
|
||||
dist/assets/partdesign-inspect-SAd-UGRC.js 4.33 kB │ gzip: 1.81 kB
|
||||
dist/assets/python-BHYtdqGM.js 4.59 kB │ gzip: 2.08 kB
|
||||
dist/assets/bim-axis-material-task-D0PFD79p.js 4.80 kB │ gzip: 1.46 kB
|
||||
dist/assets/part-document-facade-rI9zA1CA.js 4.88 kB │ gzip: 1.90 kB
|
||||
dist/assets/meshpart-Cq3YMnKd.js 5.32 kB │ gzip: 2.19 kB
|
||||
dist/assets/jtreader-D4wdvXBd.js 5.43 kB │ gzip: 2.22 kB
|
||||
dist/assets/material-BtydbLAq.js 5.76 kB │ gzip: 2.33 kB
|
||||
dist/assets/full-DaFiYDN6.js 5.89 kB │ gzip: 2.60 kB
|
||||
dist/assets/techdraw-BJsb309W.js 5.95 kB │ gzip: 2.66 kB
|
||||
dist/assets/fem-BB7u_DYL.js 6.35 kB │ gzip: 2.48 kB
|
||||
dist/assets/openscad-CFW1QTCA.js 6.35 kB │ gzip: 2.43 kB
|
||||
dist/assets/test-desktop-output-B7b_AS0y.js 6.38 kB │ gzip: 2.43 kB
|
||||
dist/assets/session-persistence-Dn8_y31c.js 6.80 kB │ gzip: 2.31 kB
|
||||
dist/assets/mesh-CeS4WbVB.js 6.86 kB │ gzip: 2.71 kB
|
||||
dist/assets/help-BDS1O4w7.js 7.00 kB │ gzip: 2.60 kB
|
||||
dist/assets/draft-OYHAw_73.js 7.02 kB │ gzip: 2.67 kB
|
||||
dist/assets/react-CYoUnH_4.js 7.16 kB │ gzip: 2.83 kB
|
||||
dist/assets/measure-CBI0LP-w.js 7.66 kB │ gzip: 2.91 kB
|
||||
dist/assets/test-desktop-adapter-3EjrcfzZ.js 8.08 kB │ gzip: 2.43 kB
|
||||
dist/assets/import-CMv4bYIE.js 8.87 kB │ gzip: 3.42 kB
|
||||
dist/assets/inspection-DiCbiJhh.js 8.92 kB │ gzip: 3.18 kB
|
||||
dist/assets/bim-xGmbnKPO.js 8.92 kB │ gzip: 2.91 kB
|
||||
dist/assets/assembly-DlAANWeX.js 9.46 kB │ gzip: 3.09 kB
|
||||
dist/assets/document-C6cPH-tZ.js 10.00 kB │ gzip: 3.50 kB
|
||||
dist/assets/core-F-zBHDcX.js 10.00 kB │ gzip: 3.50 kB
|
||||
dist/assets/cam-CA5KV68x.js 12.62 kB │ gzip: 3.86 kB
|
||||
dist/assets/main-chain-CRseZQJq.js 12.80 kB │ gzip: 4.47 kB
|
||||
dist/assets/stateful-family-dispatcher-Cw_QUwP7.js 13.54 kB │ gzip: 3.86 kB
|
||||
dist/assets/cloud-Csavllry.js 14.41 kB │ gzip: 4.26 kB
|
||||
dist/assets/python-family-dispatcher-C3iPbGJV.js 17.54 kB │ gzip: 3.49 kB
|
||||
dist/assets/plot-axes-task-DrizjbDn.js 18.79 kB │ gzip: 6.13 kB
|
||||
dist/assets/draft-desktop-adapter-BZX2hBxE.js 19.62 kB │ gzip: 4.59 kB
|
||||
dist/assets/module-router-Cdhzfrw5.js 24.94 kB │ gzip: 6.97 kB
|
||||
dist/assets/bim-desktop-adapter-5rkAISAK.js 24.94 kB │ gzip: 6.48 kB
|
||||
dist/assets/sketcher-desktop-adapter-Cx7vYbY-.js 25.64 kB │ gzip: 6.00 kB
|
||||
dist/assets/module-registry.generated-ZA3zU2Fi.js 29.46 kB │ gzip: 4.94 kB
|
||||
dist/assets/techdraw-desktop-adapter-BYxSjq3R.js 43.67 kB │ gzip: 7.59 kB
|
||||
dist/assets/client-Bz7rxqNA.js 132.71 kB │ gzip: 43.06 kB
|
||||
dist/assets/project-shell-Fj8cttEd.js 181.70 kB │ gzip: 49.48 kB
|
||||
dist/assets/partdesign-workspace-component-CQMImWVP.js 184.71 kB │ gzip: 47.78 kB
|
||||
dist/assets/web-view-task-B0ApBVvM.js 208.33 kB │ gzip: 49.02 kB
|
||||
dist/assets/import-export-preferences-BELvTpiu.js 319.10 kB │ gzip: 66.74 kB
|
||||
dist/assets/three.module-CmYU8HHc.js 565.41 kB │ gzip: 142.71 kB
|
||||
dist/assets/workbench-BHcelkbO.js 1,133.51 kB │ gzip: 281.09 kB
|
||||
|
||||
✓ built in 1.63s
|
||||
[plugin builtin:vite-reporter]
|
||||
(!) Some chunks are larger than 500 kB after minification. Consider:
|
||||
- Using dynamic import() to code-split the application
|
||||
- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"status": "implemented_unvalidated",
|
||||
"record": "docs/current/desktop-ui-toolbar-preferences-repair-20260923/README.md",
|
||||
"result": "Repaired source-confirmed omission of root Enabled from toolbar persistence. Versioned OPFS snapshot carries Enabled plus native User FCParam, migrates legacy User-only storage without overwriting the native gate, preserves System presets, rolls back root gate and layout on failure, and suppresses unchanged writes/notifications. Shared workspace now offers restore retry, guarded stale error feedback, and arrangement enter/cancel focus. Configured frontend build exit 0 (1.63s), no validation.",
|
||||
"implementedAt": "2026-09-23T03:49:15.385779+00:00",
|
||||
"continuationNotes": "This specific Enabled persistence and recovery repair is delivered. Reuse web/src/toolbar-preferences.ts, web/src/persistent-toolbar-workspace.tsx and docs/current/desktop-ui-toolbar-preferences-repair-20260923/build.log; do not rebuild unchanged inputs or run validation. OPFS snapshot is now Std_DlgParameter-PersistentToolbars.json with schema/root gate/native User FCParam; legacy .FCParam remains readable. System presets stay session-local. Current Part New Sketch owner work belongs to the existing scheduler and must not be duplicated.",
|
||||
"remainingFunctionalLimitations": [],
|
||||
"completionCorrection": "Previous record claimed root Enabled was in exported User-only snapshot. Source still exported only User; this turn implements the missing durable gate. Historical record is retained, new record describes actual storage format and limits.",
|
||||
"manualCheckpoint": {
|
||||
"at": "2026-09-23T03:49:15.385792+00:00",
|
||||
"modifiedSources": [
|
||||
"web/src/toolbar-preferences.ts",
|
||||
"web/src/persistent-toolbar-workspace.tsx"
|
||||
],
|
||||
"buildDirectory": "web",
|
||||
"buildTarget": "configured Vite frontend",
|
||||
"buildCommand": "python3 tools/build_web_frontend.py",
|
||||
"exitCode": 0,
|
||||
"log": "docs/current/desktop-ui-toolbar-preferences-repair-20260923/build.log",
|
||||
"unfinishedBuildProcesses": [],
|
||||
"nextStep": "Reuse completed shared toolbar repair; continue queued UI packages in the existing scheduler. No validation."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# 共享工具栏显隐与右键菜单整组接线
|
||||
|
||||
状态:本次范围已实现,未验证。主线整体仍未完成。
|
||||
|
||||
## 桌面依据和具体缺口
|
||||
|
||||
`freecad-source/src/Gui/MainWindow.cpp::createPopupMenu/populateToolBarMenu` 将工具栏 toggleViewAction 放入上下文菜单。`ToolBarManager.cpp` 使用 `User parameter:BaseApp/MainWindow/Toolbars` 下以工具栏名字命名的 Bool 恢复和保存显隐。Web 原有 PersistentToolbarWorkspace 只能排列宿主,没有工具栏组显隐操作和对应原生偏好接线。
|
||||
|
||||
## 实现范围
|
||||
|
||||
- 新增 `web/src/toolbar-visibility-menu.tsx`,工具栏停靠区/排列控制区的右键和普通 Toolbars 按钮共享菜单。当前工作台工具栏组显示勾选状态、键盘上下/Home/End、Enter/Space 原生按钮激活、Esc/Tab 关闭及焦点恢复;菜单按视口边界定位、支持滚动和外部点击关闭。
|
||||
- `persistent-toolbar-workspace.tsx` 统一连接当前工作台组、可见性恢复及保存。宿主仅通过 hidden 隐藏,不卸载其命令组件;保留菜单入口,可恢复所有隐藏组。保存期间阻止重复切换;失败显示错误且保留先前可见性。布局编辑草稿期间禁用显隐操作,避免通知刷新丢弃草稿。
|
||||
- `toolbar-preferences.ts` 使用桌面 `BaseApp/MainWindow/Toolbars` Bool 参数;原生 read/set/export/import 通过现有 requestOwnerParameters,工具栏组显隐无需开发面板。
|
||||
- 显隐 FCParam 加入上一轮版本化快照,与 Enabled/User 布局共同持久化、广播和失败回滚。兼容旧 User-only FCParam 和尚无 visibilityParameters 的快照;旧快照不覆盖当前显隐。System 布局预设仍不导出。
|
||||
- 显隐独立于 Tux 布局持久化开关:关闭自动恢复排列不应禁止工具栏显示/隐藏偏好。参数编辑器对显隐路径及祖先的修改复用该入口同步。
|
||||
|
||||
覆盖现有宿主:Workbench、3D display controls,以及 Part 的 Solids/Part Tools/Boolean Tools、Spreadsheet tools。其他工作台尚未注册到该共享宿主的工具栏不因本次修改被宣称已交付。标准 View→Toolbars 菜单和工具栏锁定命令不在本次上下文显隐范围内,仍需主线后续按源码接续。
|
||||
|
||||
## 复用包
|
||||
|
||||
1. 上轮根 Enabled 和 User 快照实现:`docs/current/desktop-ui-toolbar-preferences-repair-20260923/README.md`;源码 `toolbar-preferences.ts`。
|
||||
2. 既有停靠区、Portal 宿主和布局生命周期:`persistent-toolbar-workspace.tsx`;原布局任务 `operator-gap-tux-workbench-toolbar-placement-persistence`。
|
||||
3. 原生参数/存储:`view-navigation-preferences.ts::requestOwnerParameters`、`parameter-storage.ts`、`WebParameterEditor.inc`,无新文档所有者。
|
||||
|
||||
## 构建、共享写入和接续
|
||||
|
||||
- 项目目录执行 `python3 tools/build_web_frontend.py`;日志 `docs/current/desktop-ui-toolbar-visibility-20260923/build.log`,退出码 0,built in 1.61s。没有原生源码修改,没有原生构建。
|
||||
- 本轮交付时原单一调度器也进入 Part 前端交付;发现既有 helper 没有串行保护。已修改 `tools/build_web_frontend.py`,下一次调用会取得 `web/.freecad-web-frontend-build.lock` 再运行 Vite。锁在 dist 外,子进程继承锁描述符;等待超过 1800 秒退出 124,保存修改后接续,不清理缓存或启动第二调度器。
|
||||
- 锁修改在本次前端构建后完成;未为工具脚本修改重跑无变化前端,也未运行任何锁测试或验证。已在锁前启动的进程不受追溯保护,不中断其交付。
|
||||
- 未执行测试、烟测、浏览器、截图、独立类型检查或验收。无本轮未结束构建进程。
|
||||
- 原调度器第 10784 轮继续 Part 当前文档 New Sketch,未改其 project-shell、Sketcher 或原生目标;本次共享工具栏修改按独立完整范围记录。
|
||||
|
||||
处理模块 | 对标项 | 源码落点 | 路由入口 | OCCT 边界 | 状态 | 结论
|
||||
--- | --- | --- | --- | --- | --- | ---
|
||||
共享工具栏/Web | 上下文显隐、偏好保存和键盘反馈 | toolbar-visibility-menu.tsx、persistent-toolbar-workspace.tsx、toolbar-preferences.ts | Toolbars/停靠区右键 → 原生参数接口 | 无几何算法变更 | 已补漏(未验证) | 保留
|
||||
共享交付 | 防止多个交付同时写 web/dist | tools/build_web_frontend.py | 原有前端构建 helper | 无原生重建 | 已补漏(未验证) | 保留
|
||||
362
docs/current/desktop-ui-toolbar-visibility-20260923/build.log
Normal file
362
docs/current/desktop-ui-toolbar-visibility-20260923/build.log
Normal file
@@ -0,0 +1,362 @@
|
||||
vite v8.2.1 building client environment for production...
|
||||
Public assets synchronized incrementally in 0.057s
|
||||
[2K
|
||||
transforming...✓ 2450 modules transformed.
|
||||
rendering chunks...
|
||||
computing gzip size...
|
||||
dist/assets/dynamic-worker-bootstrap-CX1JKGrb.js 0.73 kB
|
||||
dist/part-inspect.html 1.04 kB │ gzip: 0.48 kB
|
||||
dist/partdesign-save.html 1.04 kB │ gzip: 0.44 kB
|
||||
dist/sketcher-inspect.html 1.05 kB │ gzip: 0.49 kB
|
||||
dist/part.html 1.10 kB │ gzip: 0.49 kB
|
||||
dist/partdesign-inspect.html 1.11 kB │ gzip: 0.50 kB
|
||||
dist/part-io.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/points.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/sandbox.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/surface.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/start.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/sketcher.html 1.12 kB │ gzip: 0.49 kB
|
||||
dist/robot-save.html 1.12 kB │ gzip: 0.50 kB
|
||||
dist/meshpart.html 1.12 kB │ gzip: 0.50 kB
|
||||
dist/robot-create.html 1.13 kB │ gzip: 0.49 kB
|
||||
dist/robot-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/show-inspect.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/spreadsheet.html 1.13 kB │ gzip: 0.49 kB
|
||||
dist/part-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/show-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/sandbox-save.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/robot-inspect.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/templatepymod.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/start-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/sandbox-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/surface-save.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/points-save.html 1.14 kB │ gzip: 0.51 kB
|
||||
dist/sketcher-python.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/surface-python.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/sandbox-inspect.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/surface-inspect.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/start-inspect.html 1.14 kB │ gzip: 0.51 kB
|
||||
dist/partdesign-python.html 1.15 kB │ gzip: 0.51 kB
|
||||
dist/spreadsheet-save.html 1.15 kB │ gzip: 0.50 kB
|
||||
dist/spreadsheet-python.html 1.15 kB │ gzip: 0.50 kB
|
||||
dist/sketcher-persistence.html 1.15 kB │ gzip: 0.51 kB
|
||||
dist/templatepymod-python.html 1.16 kB │ gzip: 0.51 kB
|
||||
dist/spreadsheet-inspect.html 1.16 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-save.html 1.16 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-create.html 1.17 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-python.html 1.17 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-inspect.html 1.17 kB │ gzip: 0.51 kB
|
||||
dist/mesh.html 1.20 kB │ gzip: 0.51 kB
|
||||
dist/part-save.html 1.21 kB │ gzip: 0.52 kB
|
||||
dist/measure.html 1.22 kB │ gzip: 0.51 kB
|
||||
dist/material.html 1.23 kB │ gzip: 0.52 kB
|
||||
dist/jtreader.html 1.23 kB │ gzip: 0.52 kB
|
||||
dist/fem.html 1.26 kB │ gzip: 0.53 kB
|
||||
dist/core.html 1.26 kB │ gzip: 0.52 kB
|
||||
dist/document.html 1.26 kB │ gzip: 0.52 kB
|
||||
dist/techdraw-inspect.html 1.26 kB │ gzip: 0.53 kB
|
||||
dist/techdraw.html 1.27 kB │ gzip: 0.52 kB
|
||||
dist/openscad.html 1.31 kB │ gzip: 0.54 kB
|
||||
dist/tux.html 1.32 kB │ gzip: 0.53 kB
|
||||
dist/test.html 1.33 kB │ gzip: 0.53 kB
|
||||
dist/web.html 1.33 kB │ gzip: 0.53 kB
|
||||
dist/techdraw-native.html 1.35 kB │ gzip: 0.54 kB
|
||||
dist/techdraw-save.html 1.35 kB │ gzip: 0.54 kB
|
||||
dist/techdraw-python.html 1.35 kB │ gzip: 0.54 kB
|
||||
dist/web-persistence.html 1.36 kB │ gzip: 0.55 kB
|
||||
dist/points-python.html 1.37 kB │ gzip: 0.57 kB
|
||||
dist/points-inspect.html 1.38 kB │ gzip: 0.58 kB
|
||||
dist/help.html 1.39 kB │ gzip: 0.57 kB
|
||||
dist/python.html 1.44 kB │ gzip: 0.57 kB
|
||||
dist/bim.html 1.45 kB │ gzip: 0.57 kB
|
||||
dist/import.html 1.46 kB │ gzip: 0.57 kB
|
||||
dist/draft.html 1.46 kB │ gzip: 0.58 kB
|
||||
dist/inspection.html 1.48 kB │ gzip: 0.57 kB
|
||||
dist/cloud.html 1.49 kB │ gzip: 0.56 kB
|
||||
dist/plot-python.html 1.59 kB │ gzip: 0.60 kB
|
||||
dist/cam.html 1.61 kB │ gzip: 0.59 kB
|
||||
dist/assembly.html 1.64 kB │ gzip: 0.60 kB
|
||||
dist/partdesign.html 2.27 kB │ gzip: 0.73 kB
|
||||
dist/project.html 3.59 kB │ gzip: 0.98 kB
|
||||
dist/index.html 3.74 kB │ gzip: 1.00 kB
|
||||
dist/assets/robot-inspect.worker-CVODVnCd.js 4.53 kB
|
||||
dist/assets/web-persistence.worker-2Zxcj3QT.js 4.55 kB
|
||||
dist/assets/start-python.worker-B5vaxGty.js 4.56 kB
|
||||
dist/assets/tux.worker-BOg1krec.js 4.58 kB
|
||||
dist/assets/sketcher-inspect.worker-C9AvvwhY.js 4.59 kB
|
||||
dist/assets/sketcher-persistence.worker-CfeXMZbg.js 4.62 kB
|
||||
dist/assets/robot-create.worker-DPChGyOR.js 4.66 kB
|
||||
dist/assets/templatepymod.worker-hg1ejMJB.js 4.67 kB
|
||||
dist/assets/surface-inspect.worker-F-GdNdw_.js 4.72 kB
|
||||
dist/assets/surface-save.worker-CUtK-04F.js 4.74 kB
|
||||
dist/assets/python-runtime.worker-B-aJYkJT.js 4.76 kB
|
||||
dist/assets/techdraw-inspect.worker-xPT-ga1B.js 4.77 kB
|
||||
dist/assets/web-dock-overlay.worker-BCsmLfKX.js 4.79 kB
|
||||
dist/assets/sandbox-save.worker-DCebp-Q8.js 4.83 kB
|
||||
dist/assets/robot-save.worker-B9iH5YAv.js 4.89 kB
|
||||
dist/assets/show-inspect.worker-B2QD7gst.js 4.90 kB
|
||||
dist/assets/techdraw-save.worker-sojHDFzQ.js 4.91 kB
|
||||
dist/assets/start-inventory.worker-CFKlvZmC.js 4.96 kB
|
||||
dist/assets/sandbox-inspect.worker-C5YazrBn.js 5.03 kB
|
||||
dist/assets/spreadsheet-save.worker-BiNG1wGg.js 5.04 kB
|
||||
dist/assets/partdesign-inspect.worker-DeojPCWC.js 5.07 kB
|
||||
dist/assets/reverseengineering-save.worker-CgUN2OLj.js 5.07 kB
|
||||
dist/assets/partdesign-save.worker-gVh5xTPd.js 5.10 kB
|
||||
dist/assets/reverseengineering-inspect.worker-CpKjB0Ej.js 5.12 kB
|
||||
dist/assets/reverseengineering-create.worker-BnYvMNcQ.js 5.14 kB
|
||||
dist/assets/spreadsheet-inspect.worker-Qa8cffR8.js 5.31 kB
|
||||
dist/assets/techdraw-native.worker-DzdTH1-p.js 5.43 kB
|
||||
dist/assets/points-persistence.worker-Q2RjUTfj.js 5.66 kB
|
||||
dist/full.html 5.66 kB │ gzip: 1.26 kB
|
||||
dist/assets/techdraw.worker-mfYkJLI7.js 5.74 kB
|
||||
dist/assets/surface.worker-CxzaO0n3.js 5.75 kB
|
||||
dist/main-chain.html 5.90 kB │ gzip: 1.32 kB
|
||||
dist/assets/web.worker-C7hjw4lI.js 6.19 kB
|
||||
dist/assets/help.worker-_0RHyS-0.js 6.23 kB
|
||||
dist/assets/sandbox.worker-Q8ky_HwA.js 6.70 kB
|
||||
dist/assets/spreadsheet.worker-Bbehg6Py.js 6.74 kB
|
||||
dist/assets/workbench-command.worker-BQ7_z6oJ.js 6.95 kB
|
||||
dist/assets/start.worker-CIavZLau.js 6.98 kB
|
||||
dist/assets/plot-python.worker-DtIDsfZF.js 6.98 kB
|
||||
dist/assets/sketcher.worker-DF02tOmg.js 7.12 kB
|
||||
dist/assets/reverseengineering-python.worker-DKZTinnr.js 7.36 kB
|
||||
dist/assets/spreadsheet-python.worker-DMdejRPT.js 7.63 kB
|
||||
dist/assets/templatepymod-python.worker-PsnxyRD5.js 7.81 kB
|
||||
dist/assets/robot-python.worker-CtPujTTt.js 8.96 kB
|
||||
dist/assets/meshpart.worker-DZfzSxZ7.js 9.09 kB
|
||||
dist/workbench.html 9.98 kB │ gzip: 1.93 kB
|
||||
dist/assets/surface-python.worker-DSo_Egxm.js 10.79 kB
|
||||
dist/assets/show-python.worker-DPiddEMC.js 10.89 kB
|
||||
dist/assets/import.worker-C-AQ4J_x.js 12.43 kB
|
||||
dist/assets/partdesign-python.worker-ydIZ9rk7.js 12.48 kB
|
||||
dist/assets/fem.worker-DGQs784X.js 12.82 kB
|
||||
dist/assets/sandbox-python.worker-DQsLVBHZ.js 12.84 kB
|
||||
dist/assets/cloud.worker-DNuING5d.js 13.68 kB
|
||||
dist/assets/inspection.worker-Bk6WfE00.js 13.99 kB
|
||||
dist/assets/cam.worker-we3i0GjL.js 14.42 kB
|
||||
dist/assets/sketcher-python.worker-DCmXcCgC.js 14.45 kB
|
||||
dist/assets/material-python.worker-BcGZQmQt.js 14.67 kB
|
||||
dist/assets/material.worker-BcGZQmQt.js 14.67 kB
|
||||
dist/assets/draft.worker-CV1zzRV7.js 15.11 kB
|
||||
dist/assets/test.worker-CE1QL_n0.js 15.75 kB
|
||||
dist/assets/measure.worker-xIEFKJEl.js 15.96 kB
|
||||
dist/assets/openscad.worker-DrNMAW2-.js 19.09 kB
|
||||
dist/assets/points.worker-9rmxQC5I.js 19.81 kB
|
||||
dist/assets/mesh.worker-DtnBKzpD.js 20.93 kB
|
||||
dist/assets/techdraw-python.worker-DpV5IJ2G.js 21.30 kB
|
||||
dist/assets/bim.worker-B0fOxOsJ.js 22.85 kB
|
||||
dist/assets/partdesign.worker-kFU8GaIi.js 32.30 kB
|
||||
dist/assets/assembly.worker-Dw6ddz-J.js 34.39 kB
|
||||
dist/assets/core.worker-u_jdRzAP.js 35.22 kB
|
||||
dist/assets/PartDesignExample-BpwNhVch.FCStd 49.34 kB
|
||||
dist/assets/part-save.worker-CXZ1hJkW.js 62.20 kB
|
||||
dist/assets/part.worker-DfEBz_3G.js 80.32 kB
|
||||
dist/assets/EngineBlock-CVb0N6vw.FCStd 92.45 kB
|
||||
dist/assets/jtreader.worker-C8dK-7Jp.js 95.78 kB
|
||||
dist/assets/draft_test_objects-Ce4A2NfX.FCStd 113.95 kB
|
||||
dist/assets/AssemblyExample-Hf3Yo9S1.FCStd 173.57 kB
|
||||
dist/assets/FEMExample-HgXO02sk.FCStd 528.07 kB
|
||||
dist/assets/Schenkel-CxpzxKld.stp 590.05 kB
|
||||
dist/assets/ArchDetail-jj9o7oVJ.FCStd 1,876.08 kB
|
||||
dist/assets/BIMExample-47hOGPj8.FCStd 2,072.78 kB
|
||||
dist/assets/workbench-DtksdrdB.css 2.11 kB │ gzip: 0.74 kB
|
||||
dist/assets/partdesign-workspace-component-B0OVOYiL.css 2.38 kB │ gzip: 0.76 kB
|
||||
dist/assets/partdesign-c4C7lohl.css 4.62 kB │ gzip: 1.55 kB
|
||||
dist/assets/play-D-_ZNeFC.js 0.12 kB │ gzip: 0.12 kB
|
||||
dist/assets/show-python-family-facade-DMqlrdIP.js 0.19 kB │ gzip: 0.14 kB
|
||||
dist/assets/show-python-facade-ButUz4Qy.js 0.24 kB │ gzip: 0.20 kB
|
||||
dist/assets/file-input-D8HGScs0.js 0.24 kB │ gzip: 0.16 kB
|
||||
dist/assets/core-runtime-NDNIHpBU.js 0.24 kB │ gzip: 0.17 kB
|
||||
dist/assets/file-search-DnMZlG9r.js 0.26 kB │ gzip: 0.18 kB
|
||||
dist/assets/mesh-persistence-facade-DS9ndFlX.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/robot-create-facade-DsFLU8rr.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/surface-save-facade-DqYkLu2D.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/cloud-persistence-facade-slrLU7xE.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/points-persistence-facade-DVLoCS9l.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/robot-save-facade-46s9sMyB.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/part-python-facade-DuAK0FpU.js 0.27 kB │ gzip: 0.21 kB
|
||||
dist/assets/start-python-facade-B1CMhCGP.js 0.27 kB │ gzip: 0.21 kB
|
||||
dist/assets/route-BjiIcSqv.js 0.27 kB │ gzip: 0.17 kB
|
||||
dist/assets/measure-persistence-facade-Bcss4aJL.js 0.27 kB │ gzip: 0.22 kB
|
||||
dist/assets/robot-python-facade-q2JVYdPx.js 0.27 kB │ gzip: 0.22 kB
|
||||
dist/assets/web-facade-Ck29HNBt.js 0.27 kB │ gzip: 0.22 kB
|
||||
dist/assets/partdesign-save-facade-B34V-F79.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/points-python-facade-5Lmbu8kQ.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/jtreader-persistence-facade-CuW_tMqV.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/sandbox-python-facade-D7HVYX_W.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/surface-python-facade-8DAhyuZd.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/spreadsheet-save-facade--Pk6YEHd.js 0.28 kB │ gzip: 0.21 kB
|
||||
dist/assets/material-persistence-facade-MroMCUFU.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/part-facade-Dqdz2CMv.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/measure-python-facade-DXH12EoV.js 0.29 kB │ gzip: 0.21 kB
|
||||
dist/assets/sketcher-python-facade-s_09asE0.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/openscad-python-facade-CeB9lZUw.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/techdraw-python-facade-BtOLupRJ.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/tux-facade-D2bVxp8w.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/inspection-persistence-facade-nvVV4ARG.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/sketcher-persistence-facade-BDuI9FpW.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/points-facade-BXGQ-Ppz.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/techdraw-save-facade-BvoReCX8.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/part-save-facade-D_VXaZj8.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/web-persistence-facade-BoD2jJgN.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/part-io-facade-CbFxtVBz.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/show-inspect-facade-B7fdNKtX.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/partdesign-python-facade-CO-vQEgQ.js 0.31 kB │ gzip: 0.22 kB
|
||||
dist/assets/points-inspect-facade-BEeDt8S-.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/sandbox-facade-D7dzZFLP.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/sandbox-save-facade-Ct4d_E5A.js 0.31 kB │ gzip: 0.24 kB
|
||||
dist/assets/sketcher-facade-NnwQipJc.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/techdraw-facade-aagF3oSr.js 0.31 kB │ gzip: 0.25 kB
|
||||
dist/assets/templatepymod-python-facade-COub4Pml.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/robot-inspect-facade-DtM6g5eT.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/start-inventory-facade-B8L6WXtA.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/reverseengineering-save-facade-jaDJAoek.js 0.32 kB │ gzip: 0.22 kB
|
||||
dist/assets/partdesign-CmEboxME.js 0.32 kB │ gzip: 0.25 kB
|
||||
dist/assets/surface-facade-BkxAqFEl.js 0.32 kB │ gzip: 0.23 kB
|
||||
dist/assets/spreadsheet-python-facade-DNQ06kqq.js 0.32 kB │ gzip: 0.23 kB
|
||||
dist/assets/surface-inspect-facade--FM1PBKH.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/reverseengineering-create-facade-_gePfDI9.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/fem-facade-CTWbg8W4.js 0.33 kB │ gzip: 0.26 kB
|
||||
dist/assets/sandbox-inspect-facade-ChelQkD0.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/draft-facade-DuQJqsW2.js 0.33 kB │ gzip: 0.26 kB
|
||||
dist/assets/templatepymod-facade-BIEatdOS.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/techdraw-native-facade-DFqX--Jm.js 0.34 kB │ gzip: 0.26 kB
|
||||
dist/assets/openscad-inspect-facade-CD1x9jF-.js 0.35 kB │ gzip: 0.25 kB
|
||||
dist/assets/reverseengineering-python-facade-D0ETOYnk.js 0.35 kB │ gzip: 0.23 kB
|
||||
dist/assets/spreadsheet-inspect-facade-Be9KXsQS.js 0.37 kB │ gzip: 0.25 kB
|
||||
dist/assets/reverseengineering-inspect-facade-D4Ki188U.js 0.40 kB │ gzip: 0.24 kB
|
||||
dist/assets/start-facade-CUkdj_rn.js 0.43 kB │ gzip: 0.26 kB
|
||||
dist/assets/inspection-facade-CBfFciGw.js 0.43 kB │ gzip: 0.28 kB
|
||||
dist/assets/save-DdXNGgDu.js 0.44 kB │ gzip: 0.21 kB
|
||||
dist/assets/cam-persistence-facade-BubrF1Zi.js 0.47 kB │ gzip: 0.31 kB
|
||||
dist/assets/spreadsheet-facade-LrDmQfdq.js 0.49 kB │ gzip: 0.31 kB
|
||||
dist/assets/help-python-facade-DtavuPjQ.js 0.51 kB │ gzip: 0.31 kB
|
||||
dist/assets/rotate-ccw-DUGzGpVv.js 0.54 kB │ gzip: 0.38 kB
|
||||
dist/assets/bim-python-facade-b1qXNzBY.js 0.54 kB │ gzip: 0.31 kB
|
||||
dist/assets/facade-envelope-D0Aixh7X.js 0.61 kB │ gzip: 0.30 kB
|
||||
dist/assets/test-facade-8wF3KR_U.js 0.62 kB │ gzip: 0.35 kB
|
||||
dist/assets/boxes-v6_sbvwg.js 0.63 kB │ gzip: 0.26 kB
|
||||
dist/assets/plot-python-Dnb4yZAX.js 0.65 kB │ gzip: 0.43 kB
|
||||
dist/assets/jsx-runtime-CriDp1zE.js 0.66 kB │ gzip: 0.43 kB
|
||||
dist/assets/modulepreload-polyfill-P2Xu9kJm.js 0.67 kB │ gzip: 0.38 kB
|
||||
dist/assets/jtreader-inspect-facade-MuD5LO2G.js 0.67 kB │ gzip: 0.38 kB
|
||||
dist/assets/mesh-python-facade-BqFrGnKZ.js 0.77 kB │ gzip: 0.40 kB
|
||||
dist/assets/assembly-python-facade-lnqmT0xQ.js 0.78 kB │ gzip: 0.38 kB
|
||||
dist/assets/meshpart-python-facade-C0BClkPM.js 0.83 kB │ gzip: 0.39 kB
|
||||
dist/assets/cam-python-facade-B7bklSZs.js 0.86 kB │ gzip: 0.46 kB
|
||||
dist/assets/material-python-facade-D9fGPPR7.js 0.90 kB │ gzip: 0.44 kB
|
||||
dist/assets/cloud-python-facade-BnucAy4n.js 0.96 kB │ gzip: 0.52 kB
|
||||
dist/assets/export-service-3DLpdk4G.js 0.99 kB │ gzip: 0.57 kB
|
||||
dist/assets/import-python-facade-BSX0J_vZ.js 1.16 kB │ gzip: 0.53 kB
|
||||
dist/assets/preload-helper-Czpn1I53.js 1.19 kB │ gzip: 0.67 kB
|
||||
dist/assets/assembly-persistence-facade-Dn6bIi3m.js 1.30 kB │ gzip: 0.70 kB
|
||||
dist/assets/points-python-BMRX_Ssp.js 1.43 kB │ gzip: 0.65 kB
|
||||
dist/assets/points-inspect-Db6j5N-G.js 1.45 kB │ gzip: 0.66 kB
|
||||
dist/assets/draft-stretch-controls-l_shOKCY.js 1.48 kB │ gzip: 0.74 kB
|
||||
dist/assets/recovery-controller-O2kjgylK.js 1.54 kB │ gzip: 0.71 kB
|
||||
dist/assets/partdesign-save-CbeoS6zN.js 1.77 kB │ gzip: 0.73 kB
|
||||
dist/assets/openscad-format-facade-CDmXl5v1.js 1.99 kB │ gzip: 0.87 kB
|
||||
dist/assets/bim-panel-ifc-task-33ENMpVH.js 2.02 kB │ gzip: 0.95 kB
|
||||
dist/assets/lucide-Bj4YvMR1.js 2.10 kB │ gzip: 1.14 kB
|
||||
dist/assets/app-services-Cdf3CnXp.js 2.21 kB │ gzip: 1.26 kB
|
||||
dist/assets/spreadsheet-save-DpOiejKi.js 2.38 kB │ gzip: 1.03 kB
|
||||
dist/assets/openscad-desktop-adapter-CHdUZLWA.js 2.91 kB │ gzip: 1.24 kB
|
||||
dist/assets/test-FWLm90kJ.js 3.34 kB │ gzip: 1.47 kB
|
||||
dist/assets/web-Bm2cXYR8.js 3.35 kB │ gzip: 1.48 kB
|
||||
dist/assets/bim-panel-structure-task-BGdyN2BA.js 3.38 kB │ gzip: 1.24 kB
|
||||
dist/assets/tux-D80RpCwQ.js 3.39 kB │ gzip: 1.49 kB
|
||||
dist/assets/part-save-BORCDu7H.js 3.43 kB │ gzip: 1.52 kB
|
||||
dist/assets/part-python-_KRaawsm.js 3.44 kB │ gzip: 1.53 kB
|
||||
dist/assets/part-io-CSDGV46a.js 3.46 kB │ gzip: 1.54 kB
|
||||
dist/assets/web-action-03-Dr7GSCtb.js 3.53 kB │ gzip: 1.53 kB
|
||||
dist/assets/part-D5vqhDw2.js 3.54 kB │ gzip: 1.58 kB
|
||||
dist/assets/techdraw-save-C_vsQmHN.js 3.54 kB │ gzip: 1.55 kB
|
||||
dist/assets/robot-python-CYNqMOnV.js 3.54 kB │ gzip: 1.62 kB
|
||||
dist/assets/techdraw-python-DvetJb7U.js 3.57 kB │ gzip: 1.53 kB
|
||||
dist/assets/points-DN0w01T1.js 3.58 kB │ gzip: 1.59 kB
|
||||
dist/assets/ui-accessibility-CmcunZz9.js 3.60 kB │ gzip: 1.67 kB
|
||||
dist/assets/techdraw-native-_QNwBGlH.js 3.62 kB │ gzip: 1.56 kB
|
||||
dist/assets/robot-inspect-CYa2be4E.js 3.67 kB │ gzip: 1.65 kB
|
||||
dist/assets/sandbox-python-BCGBxJqf.js 3.68 kB │ gzip: 1.62 kB
|
||||
dist/assets/sketcher-python-BL2_OJ2j.js 3.69 kB │ gzip: 1.62 kB
|
||||
dist/assets/part-inspect-7StgHyvD.js 3.72 kB │ gzip: 1.63 kB
|
||||
dist/assets/sketcher-D2K5ZZoZ.js 3.72 kB │ gzip: 1.61 kB
|
||||
dist/assets/templatepymod-D8K3ra-l.js 3.76 kB │ gzip: 1.68 kB
|
||||
dist/assets/sandbox-C4VUMo8D.js 3.77 kB │ gzip: 1.62 kB
|
||||
dist/assets/show-python-DacsS6fQ.js 3.77 kB │ gzip: 1.67 kB
|
||||
dist/assets/templatepymod-python-CWQMDm0W.js 3.78 kB │ gzip: 1.68 kB
|
||||
dist/assets/spreadsheet-5hm81bzD.js 3.81 kB │ gzip: 1.67 kB
|
||||
dist/assets/surface-python-BZKBmjcs.js 3.82 kB │ gzip: 1.71 kB
|
||||
dist/assets/robot-create-CWrpDvJA.js 3.82 kB │ gzip: 1.69 kB
|
||||
dist/assets/sandbox-save-Bk1WFx3P.js 3.83 kB │ gzip: 1.66 kB
|
||||
dist/assets/storage-BcICQHZX.js 3.85 kB │ gzip: 1.30 kB
|
||||
dist/assets/robot-save-_Y-zT22T.js 3.85 kB │ gzip: 1.72 kB
|
||||
dist/assets/techdraw-inspect-BSrApN_Q.js 3.86 kB │ gzip: 1.63 kB
|
||||
dist/assets/partdesign-python-Bf3-NqsC.js 3.87 kB │ gzip: 1.66 kB
|
||||
dist/assets/show-inspect-CwJha_c4.js 3.88 kB │ gzip: 1.73 kB
|
||||
dist/assets/points-save-CGe_EUOx.js 3.91 kB │ gzip: 1.67 kB
|
||||
dist/assets/sandbox-inspect-Dja0gG9n.js 3.91 kB │ gzip: 1.69 kB
|
||||
dist/assets/surface-save-CBdaDQb7.js 3.92 kB │ gzip: 1.72 kB
|
||||
dist/assets/sketcher-persistence-0PpuZ_0a.js 3.93 kB │ gzip: 1.68 kB
|
||||
dist/assets/start-WtivU9aJ.js 3.94 kB │ gzip: 1.74 kB
|
||||
dist/assets/start-python-D3PwrGwY.js 3.95 kB │ gzip: 1.73 kB
|
||||
dist/assets/spreadsheet-python-Cd-0coHp.js 3.95 kB │ gzip: 1.74 kB
|
||||
dist/assets/surface-inspect-Bh4WXF9o.js 3.96 kB │ gzip: 1.74 kB
|
||||
dist/assets/start-inspect-CsarTd3K.js 3.98 kB │ gzip: 1.75 kB
|
||||
dist/assets/surface-BMuAvYRi.js 3.98 kB │ gzip: 1.73 kB
|
||||
dist/assets/reverseengineering-python-BrLJGi9x.js 4.00 kB │ gzip: 1.78 kB
|
||||
dist/assets/reverseengineering-create-HZcw3dYz.js 4.08 kB │ gzip: 1.79 kB
|
||||
dist/assets/reverseengineering-save-FzGCucym.js 4.09 kB │ gzip: 1.81 kB
|
||||
dist/assets/spreadsheet-inspect-dFA6Vq6g.js 4.11 kB │ gzip: 1.77 kB
|
||||
dist/assets/sketcher-inspect-Wm52KgOO.js 4.11 kB │ gzip: 1.77 kB
|
||||
dist/assets/reverseengineering-inspect-8nav7qXB.js 4.20 kB │ gzip: 1.82 kB
|
||||
dist/assets/partdesign-inspect-SAd-UGRC.js 4.33 kB │ gzip: 1.81 kB
|
||||
dist/assets/python-CaN_1Pu9.js 4.59 kB │ gzip: 2.08 kB
|
||||
dist/assets/bim-axis-material-task-D0PFD79p.js 4.80 kB │ gzip: 1.46 kB
|
||||
dist/assets/part-document-facade-CBPMQ3uN.js 4.91 kB │ gzip: 1.91 kB
|
||||
dist/assets/meshpart-Cq3YMnKd.js 5.32 kB │ gzip: 2.19 kB
|
||||
dist/assets/jtreader-D4wdvXBd.js 5.43 kB │ gzip: 2.22 kB
|
||||
dist/assets/material-BtydbLAq.js 5.76 kB │ gzip: 2.33 kB
|
||||
dist/assets/full-DWaFaW7Z.js 5.89 kB │ gzip: 2.60 kB
|
||||
dist/assets/techdraw-BJsb309W.js 5.95 kB │ gzip: 2.66 kB
|
||||
dist/assets/fem-BB7u_DYL.js 6.35 kB │ gzip: 2.48 kB
|
||||
dist/assets/openscad-CFW1QTCA.js 6.35 kB │ gzip: 2.43 kB
|
||||
dist/assets/test-desktop-output-B7b_AS0y.js 6.38 kB │ gzip: 2.43 kB
|
||||
dist/assets/session-persistence-Dn8_y31c.js 6.80 kB │ gzip: 2.31 kB
|
||||
dist/assets/mesh-CeS4WbVB.js 6.86 kB │ gzip: 2.71 kB
|
||||
dist/assets/help-BDS1O4w7.js 7.00 kB │ gzip: 2.60 kB
|
||||
dist/assets/draft-OYHAw_73.js 7.02 kB │ gzip: 2.67 kB
|
||||
dist/assets/react-CYoUnH_4.js 7.16 kB │ gzip: 2.83 kB
|
||||
dist/assets/measure-CBI0LP-w.js 7.66 kB │ gzip: 2.91 kB
|
||||
dist/assets/test-desktop-adapter-3EjrcfzZ.js 8.08 kB │ gzip: 2.43 kB
|
||||
dist/assets/import-CMv4bYIE.js 8.87 kB │ gzip: 3.42 kB
|
||||
dist/assets/inspection-DiCbiJhh.js 8.92 kB │ gzip: 3.18 kB
|
||||
dist/assets/bim-xGmbnKPO.js 8.92 kB │ gzip: 2.91 kB
|
||||
dist/assets/assembly-DlAANWeX.js 9.46 kB │ gzip: 3.09 kB
|
||||
dist/assets/document-C6cPH-tZ.js 10.00 kB │ gzip: 3.50 kB
|
||||
dist/assets/core-F-zBHDcX.js 10.00 kB │ gzip: 3.50 kB
|
||||
dist/assets/cam-CA5KV68x.js 12.62 kB │ gzip: 3.86 kB
|
||||
dist/assets/main-chain-2R6AVk_4.js 12.80 kB │ gzip: 4.47 kB
|
||||
dist/assets/stateful-family-dispatcher-DlrGRfkh.js 13.54 kB │ gzip: 3.86 kB
|
||||
dist/assets/cloud-Csavllry.js 14.41 kB │ gzip: 4.26 kB
|
||||
dist/assets/python-family-dispatcher-C3iPbGJV.js 17.54 kB │ gzip: 3.49 kB
|
||||
dist/assets/plot-axes-task-DrizjbDn.js 18.79 kB │ gzip: 6.13 kB
|
||||
dist/assets/draft-desktop-adapter-BZX2hBxE.js 19.62 kB │ gzip: 4.59 kB
|
||||
dist/assets/module-router-DylSyHDq.js 24.94 kB │ gzip: 6.97 kB
|
||||
dist/assets/bim-desktop-adapter-5rkAISAK.js 24.94 kB │ gzip: 6.48 kB
|
||||
dist/assets/sketcher-desktop-adapter-CVUOtLw7.js 25.94 kB │ gzip: 6.01 kB
|
||||
dist/assets/module-registry.generated-DIDdoq0I.js 29.46 kB │ gzip: 4.95 kB
|
||||
dist/assets/techdraw-desktop-adapter-BYxSjq3R.js 43.67 kB │ gzip: 7.59 kB
|
||||
dist/assets/client-Bz7rxqNA.js 132.71 kB │ gzip: 43.06 kB
|
||||
dist/assets/partdesign-workspace-component-u2b9EwZd.js 184.71 kB │ gzip: 47.79 kB
|
||||
dist/assets/project-shell-C5kGSplH.js 190.79 kB │ gzip: 51.72 kB
|
||||
dist/assets/web-view-task-CdQzOrOj.js 209.24 kB │ gzip: 49.17 kB
|
||||
dist/assets/import-export-preferences-BmhoeWSA.js 357.79 kB │ gzip: 76.21 kB
|
||||
dist/assets/three.module-CmYU8HHc.js 565.41 kB │ gzip: 142.71 kB
|
||||
dist/assets/workbench-QM-AFhDB.js 1,094.70 kB │ gzip: 272.71 kB
|
||||
|
||||
[plugin builtin:vite-reporter]
|
||||
(!) Some chunks are larger than 500 kB after minification. Consider:
|
||||
- Using dynamic import() to code-split the application
|
||||
- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting
|
||||
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"continuationNotes": "Resume ONLY actual residuals from docs/current/desktop-ui-part-entry-repair-20260923/sketch-delivery.md (cycle 10784). Mesh conversion, sampling, attachment and Part DocumentProbe box selection are already mounted; PartNewSketchTask and same-document sketcherCommand now exist, with real empty Sketcher::SketchObject plane/support creation and retained editing. Reuse completed FreeCADWebDocumentProbe/FreeCADWebSketcherProbe links in sketch-native-build.log and frontend exit 0 in sketch-frontend-build.log; no unchanged rebuild. Next: project-shell.tsx designDocuments/nativeOwnerRequest mapping lacks sketcherCommand; trace PartDesign and actual remote owner consumers rather than assuming Part DocumentProbe is universal. Continue part-new-sketch-task.tsx near line 100 for remaining edit/close controls. Remaining shared Part UI: native shortcut metadata, Appearance per Face in View menu, correct cross-owner box selection. Old README/continuation.md absent-panel/new-sketch statements are historical and must not drive reimplementation. Shared toolbar context visibility/persistence is delivered separately at docs/current/desktop-ui-toolbar-visibility-20260923/README.md. Future frontend deliveries must use python3 tools/build_web_frontend.py, now serialized by a shared lock. No verification."
|
||||
}
|
||||
22646
docs/current/execution-policy.json
Normal file
22646
docs/current/execution-policy.json
Normal file
File diff suppressed because it is too large
Load Diff
22646
docs/current/execution-policy.last-good.json
Normal file
22646
docs/current/execution-policy.last-good.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
# Start 首次启动常规设置补漏
|
||||
|
||||
状态:已实现,未验证;复用既有 Start 包,不新增孤立任务。
|
||||
|
||||
## 具体修复
|
||||
|
||||
桌面依据 `freecad-source/src/Mod/Start/Gui/GeneralSettingsWidget.cpp`:首次启动页面中的 Language、Unit System、Navigation Style 会立即写入 General、Units.UserSchema 和 View.NavigationStyle,并与 ThemeSelector 一起作用。Web 原先只有 `StartPreferencesEditor` 的后置偏好页,`StartLaunchPanel` 首次启动区只有 Theme、ShowExamples、CustomFolder 和 closeStart,导致首次设置入口漏掉三项常规设置;并且 UserSchema 数值被当成 String 发给参数桥。
|
||||
|
||||
本轮复用并集中修复:
|
||||
|
||||
- `web/src/start-preferences.tsx` 新增 `readStartGeneral`、`saveStartGeneral` 和 `FirstStartGeneralSettings`,从已有 PartBox 参数所有者读取并立即保存 Language、UserSchema、NavigationStyle、Theme;UserSchema 使用原生 Int 类型,保存失败保留错误提示和可恢复当前值。
|
||||
- `applyStartTheme` 统一写入 `data-theme`、`color-scheme`,并广播 `freecad-theme-changed`;既有后置 General settings 复用同一个控件和原生桥,不再保留两份行为不同的实现。
|
||||
- `web/src/start-launch-panel.tsx` 的 First start setup 挂载同一 General settings 控件,保留原有 FirstStart2024、ShowExamples、CustomFolder、closeStart、Done、Reload、Dismiss 生命周期;主题不再只写 localStorage,而由共享控件实时应用。
|
||||
|
||||
参数路径与桌面语义:
|
||||
|
||||
- `User parameter:BaseApp/Preferences/General/Language`
|
||||
- `User parameter:BaseApp/Preferences/Units/UserSchema`(Int)
|
||||
- `User parameter:BaseApp/Preferences/View/NavigationStyle`
|
||||
- Web shell theme via `document.documentElement.dataset.theme` and `colorScheme`
|
||||
|
||||
## 复用与边界
|
||||
|
||||
复用 `startParameters`、`requestOwnerParameters`、`navigationStyles`、PartBox/DocumentProbe 参数持久化和现有 StartPreferencesEditor;不新建文档、不改变 Start 文件打开/新建/示例/自定义目录流程,不改 OCCT 或 Three.js。
|
||||
|
||||
语言选择写入原生 General 参数,并广播设置刷新事件;当前仓库没有独立 Web 翻译目录/运行时 locale loader,本轮保留已有静态 shell 文案机制,未虚构桌面 Qt 的即时翻译能力。后续若加入实际 locale loader,应复用该事件和参数 owner,而不是重新添加第二套首次启动页面。
|
||||
|
||||
## 构建
|
||||
|
||||
- 修改:`web/src/start-preferences.tsx`、`web/src/start-launch-panel.tsx`。
|
||||
- 必要前端构建:`python3 tools/build_web_frontend.py`。
|
||||
- 日志:`docs/current/start-first-start-general-settings-20260923/build.log`,退出码 0,built in 1.64s。
|
||||
- 未运行测试、类型检查、浏览器、截图、烟测或验收;没有原生源码变化,无原生构建。
|
||||
|
||||
处理模块 | 对标项 | 源码落点 | 路由入口 | OCCT 边界 | 状态 | 结论
|
||||
--- | --- | --- | --- | --- | --- | ---
|
||||
Start | 首次启动语言/单位/导航/主题 | start-launch-panel.tsx;start-preferences.tsx | Start_Start → FirstStartGeneralSettings → requestOwnerParameters | 仅原生偏好,无几何算法 | 已补漏(未验证) | 保留
|
||||
@@ -0,0 +1,362 @@
|
||||
vite v8.2.1 building client environment for production...
|
||||
Public assets synchronized incrementally in 0.055s
|
||||
[2K
|
||||
transforming...✓ 2450 modules transformed.
|
||||
rendering chunks...
|
||||
computing gzip size...
|
||||
dist/assets/dynamic-worker-bootstrap-CX1JKGrb.js 0.73 kB
|
||||
dist/part-inspect.html 1.04 kB │ gzip: 0.48 kB
|
||||
dist/partdesign-save.html 1.04 kB │ gzip: 0.44 kB
|
||||
dist/sketcher-inspect.html 1.05 kB │ gzip: 0.49 kB
|
||||
dist/part.html 1.10 kB │ gzip: 0.49 kB
|
||||
dist/partdesign-inspect.html 1.11 kB │ gzip: 0.50 kB
|
||||
dist/part-io.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/points.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/sandbox.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/surface.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/start.html 1.11 kB │ gzip: 0.49 kB
|
||||
dist/sketcher.html 1.12 kB │ gzip: 0.49 kB
|
||||
dist/robot-save.html 1.12 kB │ gzip: 0.50 kB
|
||||
dist/meshpart.html 1.12 kB │ gzip: 0.50 kB
|
||||
dist/robot-create.html 1.13 kB │ gzip: 0.49 kB
|
||||
dist/robot-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/show-inspect.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/spreadsheet.html 1.13 kB │ gzip: 0.49 kB
|
||||
dist/part-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/show-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/sandbox-save.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/robot-inspect.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/templatepymod.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/start-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/sandbox-python.html 1.13 kB │ gzip: 0.50 kB
|
||||
dist/surface-save.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/points-save.html 1.14 kB │ gzip: 0.51 kB
|
||||
dist/sketcher-python.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/surface-python.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/sandbox-inspect.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/surface-inspect.html 1.14 kB │ gzip: 0.50 kB
|
||||
dist/start-inspect.html 1.14 kB │ gzip: 0.51 kB
|
||||
dist/partdesign-python.html 1.15 kB │ gzip: 0.51 kB
|
||||
dist/spreadsheet-save.html 1.15 kB │ gzip: 0.50 kB
|
||||
dist/spreadsheet-python.html 1.15 kB │ gzip: 0.50 kB
|
||||
dist/sketcher-persistence.html 1.15 kB │ gzip: 0.51 kB
|
||||
dist/templatepymod-python.html 1.16 kB │ gzip: 0.51 kB
|
||||
dist/spreadsheet-inspect.html 1.16 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-save.html 1.16 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-create.html 1.17 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-python.html 1.17 kB │ gzip: 0.51 kB
|
||||
dist/reverseengineering-inspect.html 1.17 kB │ gzip: 0.51 kB
|
||||
dist/mesh.html 1.20 kB │ gzip: 0.51 kB
|
||||
dist/part-save.html 1.21 kB │ gzip: 0.52 kB
|
||||
dist/measure.html 1.22 kB │ gzip: 0.51 kB
|
||||
dist/material.html 1.23 kB │ gzip: 0.52 kB
|
||||
dist/jtreader.html 1.23 kB │ gzip: 0.52 kB
|
||||
dist/fem.html 1.26 kB │ gzip: 0.53 kB
|
||||
dist/core.html 1.26 kB │ gzip: 0.52 kB
|
||||
dist/document.html 1.26 kB │ gzip: 0.52 kB
|
||||
dist/techdraw-inspect.html 1.26 kB │ gzip: 0.53 kB
|
||||
dist/techdraw.html 1.27 kB │ gzip: 0.52 kB
|
||||
dist/openscad.html 1.31 kB │ gzip: 0.54 kB
|
||||
dist/tux.html 1.32 kB │ gzip: 0.53 kB
|
||||
dist/test.html 1.33 kB │ gzip: 0.53 kB
|
||||
dist/web.html 1.33 kB │ gzip: 0.53 kB
|
||||
dist/techdraw-native.html 1.35 kB │ gzip: 0.54 kB
|
||||
dist/techdraw-save.html 1.35 kB │ gzip: 0.54 kB
|
||||
dist/techdraw-python.html 1.35 kB │ gzip: 0.54 kB
|
||||
dist/web-persistence.html 1.36 kB │ gzip: 0.55 kB
|
||||
dist/points-python.html 1.37 kB │ gzip: 0.57 kB
|
||||
dist/points-inspect.html 1.38 kB │ gzip: 0.58 kB
|
||||
dist/help.html 1.39 kB │ gzip: 0.57 kB
|
||||
dist/python.html 1.44 kB │ gzip: 0.57 kB
|
||||
dist/bim.html 1.45 kB │ gzip: 0.57 kB
|
||||
dist/import.html 1.46 kB │ gzip: 0.57 kB
|
||||
dist/draft.html 1.46 kB │ gzip: 0.58 kB
|
||||
dist/inspection.html 1.48 kB │ gzip: 0.57 kB
|
||||
dist/cloud.html 1.49 kB │ gzip: 0.56 kB
|
||||
dist/plot-python.html 1.59 kB │ gzip: 0.60 kB
|
||||
dist/cam.html 1.61 kB │ gzip: 0.59 kB
|
||||
dist/assembly.html 1.64 kB │ gzip: 0.61 kB
|
||||
dist/partdesign.html 2.27 kB │ gzip: 0.73 kB
|
||||
dist/project.html 3.59 kB │ gzip: 0.99 kB
|
||||
dist/index.html 3.74 kB │ gzip: 1.01 kB
|
||||
dist/assets/robot-inspect.worker-CVODVnCd.js 4.53 kB
|
||||
dist/assets/web-persistence.worker-2Zxcj3QT.js 4.55 kB
|
||||
dist/assets/start-python.worker-B5vaxGty.js 4.56 kB
|
||||
dist/assets/tux.worker-BOg1krec.js 4.58 kB
|
||||
dist/assets/sketcher-inspect.worker-C9AvvwhY.js 4.59 kB
|
||||
dist/assets/sketcher-persistence.worker-CfeXMZbg.js 4.62 kB
|
||||
dist/assets/robot-create.worker-DPChGyOR.js 4.66 kB
|
||||
dist/assets/templatepymod.worker-hg1ejMJB.js 4.67 kB
|
||||
dist/assets/surface-inspect.worker-F-GdNdw_.js 4.72 kB
|
||||
dist/assets/surface-save.worker-CUtK-04F.js 4.74 kB
|
||||
dist/assets/python-runtime.worker-B-aJYkJT.js 4.76 kB
|
||||
dist/assets/techdraw-inspect.worker-xPT-ga1B.js 4.77 kB
|
||||
dist/assets/web-dock-overlay.worker-BCsmLfKX.js 4.79 kB
|
||||
dist/assets/sandbox-save.worker-DCebp-Q8.js 4.83 kB
|
||||
dist/assets/robot-save.worker-B9iH5YAv.js 4.89 kB
|
||||
dist/assets/show-inspect.worker-B2QD7gst.js 4.90 kB
|
||||
dist/assets/techdraw-save.worker-sojHDFzQ.js 4.91 kB
|
||||
dist/assets/start-inventory.worker-CFKlvZmC.js 4.96 kB
|
||||
dist/assets/sandbox-inspect.worker-C5YazrBn.js 5.03 kB
|
||||
dist/assets/spreadsheet-save.worker-BiNG1wGg.js 5.04 kB
|
||||
dist/assets/partdesign-inspect.worker-DeojPCWC.js 5.07 kB
|
||||
dist/assets/reverseengineering-save.worker-CgUN2OLj.js 5.07 kB
|
||||
dist/assets/partdesign-save.worker-gVh5xTPd.js 5.10 kB
|
||||
dist/assets/reverseengineering-inspect.worker-CpKjB0Ej.js 5.12 kB
|
||||
dist/assets/reverseengineering-create.worker-BnYvMNcQ.js 5.14 kB
|
||||
dist/assets/spreadsheet-inspect.worker-Qa8cffR8.js 5.31 kB
|
||||
dist/assets/techdraw-native.worker-DzdTH1-p.js 5.43 kB
|
||||
dist/assets/points-persistence.worker-Q2RjUTfj.js 5.66 kB
|
||||
dist/full.html 5.66 kB │ gzip: 1.26 kB
|
||||
dist/assets/techdraw.worker-mfYkJLI7.js 5.74 kB
|
||||
dist/assets/surface.worker-CxzaO0n3.js 5.75 kB
|
||||
dist/main-chain.html 5.90 kB │ gzip: 1.32 kB
|
||||
dist/assets/web.worker-C7hjw4lI.js 6.19 kB
|
||||
dist/assets/help.worker-_0RHyS-0.js 6.23 kB
|
||||
dist/assets/sandbox.worker-Q8ky_HwA.js 6.70 kB
|
||||
dist/assets/spreadsheet.worker-Bbehg6Py.js 6.74 kB
|
||||
dist/assets/workbench-command.worker-BQ7_z6oJ.js 6.95 kB
|
||||
dist/assets/start.worker-CIavZLau.js 6.98 kB
|
||||
dist/assets/plot-python.worker-DtIDsfZF.js 6.98 kB
|
||||
dist/assets/sketcher.worker-DF02tOmg.js 7.12 kB
|
||||
dist/assets/reverseengineering-python.worker-DKZTinnr.js 7.36 kB
|
||||
dist/assets/spreadsheet-python.worker-DMdejRPT.js 7.63 kB
|
||||
dist/assets/templatepymod-python.worker-PsnxyRD5.js 7.81 kB
|
||||
dist/assets/robot-python.worker-CtPujTTt.js 8.96 kB
|
||||
dist/assets/meshpart.worker-DZfzSxZ7.js 9.09 kB
|
||||
dist/workbench.html 9.98 kB │ gzip: 1.93 kB
|
||||
dist/assets/surface-python.worker-DSo_Egxm.js 10.79 kB
|
||||
dist/assets/show-python.worker-DPiddEMC.js 10.89 kB
|
||||
dist/assets/import.worker-C-AQ4J_x.js 12.43 kB
|
||||
dist/assets/partdesign-python.worker-ydIZ9rk7.js 12.48 kB
|
||||
dist/assets/fem.worker-DGQs784X.js 12.82 kB
|
||||
dist/assets/sandbox-python.worker-DQsLVBHZ.js 12.84 kB
|
||||
dist/assets/cloud.worker-DNuING5d.js 13.68 kB
|
||||
dist/assets/inspection.worker-Bk6WfE00.js 13.99 kB
|
||||
dist/assets/cam.worker-we3i0GjL.js 14.42 kB
|
||||
dist/assets/sketcher-python.worker-DCmXcCgC.js 14.45 kB
|
||||
dist/assets/material-python.worker-BcGZQmQt.js 14.67 kB
|
||||
dist/assets/material.worker-BcGZQmQt.js 14.67 kB
|
||||
dist/assets/draft.worker-CV1zzRV7.js 15.11 kB
|
||||
dist/assets/test.worker-CE1QL_n0.js 15.75 kB
|
||||
dist/assets/measure.worker-xIEFKJEl.js 15.96 kB
|
||||
dist/assets/openscad.worker-DrNMAW2-.js 19.09 kB
|
||||
dist/assets/points.worker-9rmxQC5I.js 19.81 kB
|
||||
dist/assets/techdraw-python.worker-DpV5IJ2G.js 21.30 kB
|
||||
dist/assets/mesh.worker-CdHgCr3i.js 21.50 kB
|
||||
dist/assets/bim.worker-B0fOxOsJ.js 22.85 kB
|
||||
dist/assets/partdesign.worker-BKGepl5j.js 32.32 kB
|
||||
dist/assets/assembly.worker-D7Cm_S8g.js 34.41 kB
|
||||
dist/assets/core.worker-u_jdRzAP.js 35.22 kB
|
||||
dist/assets/PartDesignExample-BpwNhVch.FCStd 49.34 kB
|
||||
dist/assets/part-save.worker-CXZ1hJkW.js 62.20 kB
|
||||
dist/assets/part.worker-DfEBz_3G.js 80.32 kB
|
||||
dist/assets/EngineBlock-CVb0N6vw.FCStd 92.45 kB
|
||||
dist/assets/jtreader.worker-C8dK-7Jp.js 95.78 kB
|
||||
dist/assets/draft_test_objects-Ce4A2NfX.FCStd 113.95 kB
|
||||
dist/assets/AssemblyExample-Hf3Yo9S1.FCStd 173.57 kB
|
||||
dist/assets/FEMExample-HgXO02sk.FCStd 528.07 kB
|
||||
dist/assets/Schenkel-CxpzxKld.stp 590.05 kB
|
||||
dist/assets/ArchDetail-jj9o7oVJ.FCStd 1,876.08 kB
|
||||
dist/assets/BIMExample-47hOGPj8.FCStd 2,072.78 kB
|
||||
dist/assets/workbench-DtksdrdB.css 2.11 kB │ gzip: 0.74 kB
|
||||
dist/assets/partdesign-workspace-component-B0OVOYiL.css 2.38 kB │ gzip: 0.76 kB
|
||||
dist/assets/partdesign-c4C7lohl.css 4.62 kB │ gzip: 1.55 kB
|
||||
dist/assets/play-D-_ZNeFC.js 0.12 kB │ gzip: 0.12 kB
|
||||
dist/assets/show-python-family-facade-DMqlrdIP.js 0.19 kB │ gzip: 0.14 kB
|
||||
dist/assets/show-python-facade-ButUz4Qy.js 0.24 kB │ gzip: 0.20 kB
|
||||
dist/assets/file-input-D8HGScs0.js 0.24 kB │ gzip: 0.16 kB
|
||||
dist/assets/core-runtime-NDNIHpBU.js 0.24 kB │ gzip: 0.17 kB
|
||||
dist/assets/file-search-DnMZlG9r.js 0.26 kB │ gzip: 0.18 kB
|
||||
dist/assets/mesh-persistence-facade-DS9ndFlX.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/robot-create-facade-DsFLU8rr.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/surface-save-facade-DqYkLu2D.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/cloud-persistence-facade-slrLU7xE.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/points-persistence-facade-DVLoCS9l.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/robot-save-facade-46s9sMyB.js 0.26 kB │ gzip: 0.21 kB
|
||||
dist/assets/part-python-facade-DuAK0FpU.js 0.27 kB │ gzip: 0.21 kB
|
||||
dist/assets/start-python-facade-B1CMhCGP.js 0.27 kB │ gzip: 0.21 kB
|
||||
dist/assets/route-BjiIcSqv.js 0.27 kB │ gzip: 0.17 kB
|
||||
dist/assets/measure-persistence-facade-Bcss4aJL.js 0.27 kB │ gzip: 0.22 kB
|
||||
dist/assets/robot-python-facade-q2JVYdPx.js 0.27 kB │ gzip: 0.22 kB
|
||||
dist/assets/web-facade-Ck29HNBt.js 0.27 kB │ gzip: 0.22 kB
|
||||
dist/assets/partdesign-save-facade-B34V-F79.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/points-python-facade-5Lmbu8kQ.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/jtreader-persistence-facade-CuW_tMqV.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/sandbox-python-facade-D7HVYX_W.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/surface-python-facade-8DAhyuZd.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/spreadsheet-save-facade--Pk6YEHd.js 0.28 kB │ gzip: 0.21 kB
|
||||
dist/assets/material-persistence-facade-MroMCUFU.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/part-facade-Dqdz2CMv.js 0.28 kB │ gzip: 0.22 kB
|
||||
dist/assets/measure-python-facade-DXH12EoV.js 0.29 kB │ gzip: 0.21 kB
|
||||
dist/assets/sketcher-python-facade-s_09asE0.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/openscad-python-facade-CeB9lZUw.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/techdraw-python-facade-BtOLupRJ.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/tux-facade-D2bVxp8w.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/inspection-persistence-facade-nvVV4ARG.js 0.29 kB │ gzip: 0.22 kB
|
||||
dist/assets/sketcher-persistence-facade-BDuI9FpW.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/points-facade-BXGQ-Ppz.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/techdraw-save-facade-BvoReCX8.js 0.29 kB │ gzip: 0.23 kB
|
||||
dist/assets/part-save-facade-D_VXaZj8.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/web-persistence-facade-BoD2jJgN.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/part-io-facade-CbFxtVBz.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/show-inspect-facade-B7fdNKtX.js 0.30 kB │ gzip: 0.23 kB
|
||||
dist/assets/partdesign-python-facade-CO-vQEgQ.js 0.31 kB │ gzip: 0.22 kB
|
||||
dist/assets/points-inspect-facade-BEeDt8S-.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/sandbox-facade-D7dzZFLP.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/sandbox-save-facade-Ct4d_E5A.js 0.31 kB │ gzip: 0.24 kB
|
||||
dist/assets/sketcher-facade-NnwQipJc.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/techdraw-facade-aagF3oSr.js 0.31 kB │ gzip: 0.25 kB
|
||||
dist/assets/templatepymod-python-facade-COub4Pml.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/robot-inspect-facade-DtM6g5eT.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/start-inventory-facade-B8L6WXtA.js 0.31 kB │ gzip: 0.23 kB
|
||||
dist/assets/reverseengineering-save-facade-jaDJAoek.js 0.32 kB │ gzip: 0.22 kB
|
||||
dist/assets/partdesign-BQdKhBdz.js 0.32 kB │ gzip: 0.25 kB
|
||||
dist/assets/surface-facade-BkxAqFEl.js 0.32 kB │ gzip: 0.23 kB
|
||||
dist/assets/spreadsheet-python-facade-DNQ06kqq.js 0.32 kB │ gzip: 0.23 kB
|
||||
dist/assets/surface-inspect-facade--FM1PBKH.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/reverseengineering-create-facade-_gePfDI9.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/fem-facade-CTWbg8W4.js 0.33 kB │ gzip: 0.26 kB
|
||||
dist/assets/sandbox-inspect-facade-ChelQkD0.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/draft-facade-DuQJqsW2.js 0.33 kB │ gzip: 0.26 kB
|
||||
dist/assets/templatepymod-facade-BIEatdOS.js 0.33 kB │ gzip: 0.23 kB
|
||||
dist/assets/techdraw-native-facade-DFqX--Jm.js 0.34 kB │ gzip: 0.26 kB
|
||||
dist/assets/openscad-inspect-facade-CD1x9jF-.js 0.35 kB │ gzip: 0.25 kB
|
||||
dist/assets/reverseengineering-python-facade-D0ETOYnk.js 0.35 kB │ gzip: 0.23 kB
|
||||
dist/assets/spreadsheet-inspect-facade-Be9KXsQS.js 0.37 kB │ gzip: 0.25 kB
|
||||
dist/assets/reverseengineering-inspect-facade-D4Ki188U.js 0.40 kB │ gzip: 0.24 kB
|
||||
dist/assets/start-facade-CUkdj_rn.js 0.43 kB │ gzip: 0.26 kB
|
||||
dist/assets/inspection-facade-CBfFciGw.js 0.43 kB │ gzip: 0.28 kB
|
||||
dist/assets/save-DdXNGgDu.js 0.44 kB │ gzip: 0.21 kB
|
||||
dist/assets/cam-persistence-facade-BubrF1Zi.js 0.47 kB │ gzip: 0.31 kB
|
||||
dist/assets/spreadsheet-facade-LrDmQfdq.js 0.49 kB │ gzip: 0.31 kB
|
||||
dist/assets/help-python-facade-DtavuPjQ.js 0.51 kB │ gzip: 0.31 kB
|
||||
dist/assets/rotate-ccw-DUGzGpVv.js 0.54 kB │ gzip: 0.38 kB
|
||||
dist/assets/bim-python-facade-b1qXNzBY.js 0.54 kB │ gzip: 0.31 kB
|
||||
dist/assets/facade-envelope-D0Aixh7X.js 0.61 kB │ gzip: 0.30 kB
|
||||
dist/assets/test-facade-8wF3KR_U.js 0.62 kB │ gzip: 0.35 kB
|
||||
dist/assets/boxes-v6_sbvwg.js 0.63 kB │ gzip: 0.26 kB
|
||||
dist/assets/plot-python-Dnb4yZAX.js 0.65 kB │ gzip: 0.43 kB
|
||||
dist/assets/jsx-runtime-CriDp1zE.js 0.66 kB │ gzip: 0.43 kB
|
||||
dist/assets/modulepreload-polyfill-P2Xu9kJm.js 0.67 kB │ gzip: 0.38 kB
|
||||
dist/assets/jtreader-inspect-facade-MuD5LO2G.js 0.67 kB │ gzip: 0.38 kB
|
||||
dist/assets/mesh-python-facade-BqFrGnKZ.js 0.77 kB │ gzip: 0.40 kB
|
||||
dist/assets/assembly-python-facade-lnqmT0xQ.js 0.78 kB │ gzip: 0.38 kB
|
||||
dist/assets/meshpart-python-facade-C0BClkPM.js 0.83 kB │ gzip: 0.39 kB
|
||||
dist/assets/cam-python-facade-B7bklSZs.js 0.86 kB │ gzip: 0.46 kB
|
||||
dist/assets/material-python-facade-D9fGPPR7.js 0.90 kB │ gzip: 0.44 kB
|
||||
dist/assets/cloud-python-facade-BnucAy4n.js 0.96 kB │ gzip: 0.52 kB
|
||||
dist/assets/export-service-3DLpdk4G.js 0.99 kB │ gzip: 0.57 kB
|
||||
dist/assets/import-python-facade-BSX0J_vZ.js 1.16 kB │ gzip: 0.53 kB
|
||||
dist/assets/preload-helper-Czpn1I53.js 1.19 kB │ gzip: 0.67 kB
|
||||
dist/assets/assembly-persistence-facade-Dn6bIi3m.js 1.30 kB │ gzip: 0.70 kB
|
||||
dist/assets/points-python-BMRX_Ssp.js 1.43 kB │ gzip: 0.65 kB
|
||||
dist/assets/points-inspect-Db6j5N-G.js 1.45 kB │ gzip: 0.66 kB
|
||||
dist/assets/draft-stretch-controls-l_shOKCY.js 1.48 kB │ gzip: 0.74 kB
|
||||
dist/assets/recovery-controller-O2kjgylK.js 1.54 kB │ gzip: 0.71 kB
|
||||
dist/assets/partdesign-save-CbeoS6zN.js 1.77 kB │ gzip: 0.73 kB
|
||||
dist/assets/openscad-format-facade-CDmXl5v1.js 1.99 kB │ gzip: 0.87 kB
|
||||
dist/assets/bim-panel-ifc-task-33ENMpVH.js 2.02 kB │ gzip: 0.95 kB
|
||||
dist/assets/lucide-Bj4YvMR1.js 2.10 kB │ gzip: 1.14 kB
|
||||
dist/assets/app-services-Cdf3CnXp.js 2.21 kB │ gzip: 1.26 kB
|
||||
dist/assets/spreadsheet-save-DpOiejKi.js 2.38 kB │ gzip: 1.03 kB
|
||||
dist/assets/openscad-desktop-adapter-CHdUZLWA.js 2.91 kB │ gzip: 1.24 kB
|
||||
dist/assets/test-FWLm90kJ.js 3.34 kB │ gzip: 1.47 kB
|
||||
dist/assets/web-Bm2cXYR8.js 3.35 kB │ gzip: 1.48 kB
|
||||
dist/assets/bim-panel-structure-task-BGdyN2BA.js 3.38 kB │ gzip: 1.24 kB
|
||||
dist/assets/tux-D80RpCwQ.js 3.39 kB │ gzip: 1.49 kB
|
||||
dist/assets/part-save-BORCDu7H.js 3.43 kB │ gzip: 1.52 kB
|
||||
dist/assets/part-python-_KRaawsm.js 3.44 kB │ gzip: 1.53 kB
|
||||
dist/assets/part-io-CSDGV46a.js 3.46 kB │ gzip: 1.54 kB
|
||||
dist/assets/web-action-03-Dr7GSCtb.js 3.53 kB │ gzip: 1.53 kB
|
||||
dist/assets/part-D5vqhDw2.js 3.54 kB │ gzip: 1.58 kB
|
||||
dist/assets/techdraw-save-C_vsQmHN.js 3.54 kB │ gzip: 1.55 kB
|
||||
dist/assets/robot-python-CYNqMOnV.js 3.54 kB │ gzip: 1.62 kB
|
||||
dist/assets/techdraw-python-DvetJb7U.js 3.57 kB │ gzip: 1.53 kB
|
||||
dist/assets/points-DN0w01T1.js 3.58 kB │ gzip: 1.59 kB
|
||||
dist/assets/ui-accessibility-Bx2JN2aO.js 3.60 kB │ gzip: 1.67 kB
|
||||
dist/assets/techdraw-native-_QNwBGlH.js 3.62 kB │ gzip: 1.56 kB
|
||||
dist/assets/robot-inspect-CYa2be4E.js 3.67 kB │ gzip: 1.65 kB
|
||||
dist/assets/sandbox-python-BCGBxJqf.js 3.68 kB │ gzip: 1.62 kB
|
||||
dist/assets/sketcher-python-BL2_OJ2j.js 3.69 kB │ gzip: 1.62 kB
|
||||
dist/assets/part-inspect-7StgHyvD.js 3.72 kB │ gzip: 1.63 kB
|
||||
dist/assets/sketcher-D2K5ZZoZ.js 3.72 kB │ gzip: 1.61 kB
|
||||
dist/assets/templatepymod-D8K3ra-l.js 3.76 kB │ gzip: 1.68 kB
|
||||
dist/assets/sandbox-C4VUMo8D.js 3.77 kB │ gzip: 1.62 kB
|
||||
dist/assets/show-python-DacsS6fQ.js 3.77 kB │ gzip: 1.67 kB
|
||||
dist/assets/templatepymod-python-CWQMDm0W.js 3.78 kB │ gzip: 1.68 kB
|
||||
dist/assets/spreadsheet-5hm81bzD.js 3.81 kB │ gzip: 1.67 kB
|
||||
dist/assets/surface-python-BZKBmjcs.js 3.82 kB │ gzip: 1.71 kB
|
||||
dist/assets/robot-create-CWrpDvJA.js 3.82 kB │ gzip: 1.69 kB
|
||||
dist/assets/sandbox-save-Bk1WFx3P.js 3.83 kB │ gzip: 1.66 kB
|
||||
dist/assets/storage-BcICQHZX.js 3.85 kB │ gzip: 1.30 kB
|
||||
dist/assets/robot-save-_Y-zT22T.js 3.85 kB │ gzip: 1.72 kB
|
||||
dist/assets/techdraw-inspect-BSrApN_Q.js 3.86 kB │ gzip: 1.63 kB
|
||||
dist/assets/partdesign-python-Bf3-NqsC.js 3.87 kB │ gzip: 1.66 kB
|
||||
dist/assets/show-inspect-CwJha_c4.js 3.88 kB │ gzip: 1.73 kB
|
||||
dist/assets/points-save-CGe_EUOx.js 3.91 kB │ gzip: 1.67 kB
|
||||
dist/assets/sandbox-inspect-Dja0gG9n.js 3.91 kB │ gzip: 1.69 kB
|
||||
dist/assets/surface-save-CBdaDQb7.js 3.92 kB │ gzip: 1.72 kB
|
||||
dist/assets/sketcher-persistence-0PpuZ_0a.js 3.93 kB │ gzip: 1.68 kB
|
||||
dist/assets/start-WtivU9aJ.js 3.94 kB │ gzip: 1.74 kB
|
||||
dist/assets/start-python-D3PwrGwY.js 3.95 kB │ gzip: 1.73 kB
|
||||
dist/assets/spreadsheet-python-Cd-0coHp.js 3.95 kB │ gzip: 1.74 kB
|
||||
dist/assets/surface-inspect-Bh4WXF9o.js 3.96 kB │ gzip: 1.74 kB
|
||||
dist/assets/start-inspect-CsarTd3K.js 3.98 kB │ gzip: 1.75 kB
|
||||
dist/assets/surface-BMuAvYRi.js 3.98 kB │ gzip: 1.73 kB
|
||||
dist/assets/reverseengineering-python-BrLJGi9x.js 4.00 kB │ gzip: 1.78 kB
|
||||
dist/assets/reverseengineering-create-HZcw3dYz.js 4.08 kB │ gzip: 1.79 kB
|
||||
dist/assets/reverseengineering-save-FzGCucym.js 4.09 kB │ gzip: 1.81 kB
|
||||
dist/assets/spreadsheet-inspect-dFA6Vq6g.js 4.11 kB │ gzip: 1.77 kB
|
||||
dist/assets/sketcher-inspect-Wm52KgOO.js 4.11 kB │ gzip: 1.77 kB
|
||||
dist/assets/reverseengineering-inspect-8nav7qXB.js 4.20 kB │ gzip: 1.82 kB
|
||||
dist/assets/partdesign-inspect-SAd-UGRC.js 4.33 kB │ gzip: 1.81 kB
|
||||
dist/assets/python-Ba27GsIn.js 4.59 kB │ gzip: 2.08 kB
|
||||
dist/assets/bim-axis-material-task-D0PFD79p.js 4.80 kB │ gzip: 1.46 kB
|
||||
dist/assets/part-document-facade-CBPMQ3uN.js 4.91 kB │ gzip: 1.91 kB
|
||||
dist/assets/meshpart-Cq3YMnKd.js 5.32 kB │ gzip: 2.19 kB
|
||||
dist/assets/jtreader-D4wdvXBd.js 5.43 kB │ gzip: 2.22 kB
|
||||
dist/assets/material-BtydbLAq.js 5.76 kB │ gzip: 2.33 kB
|
||||
dist/assets/full-C3TosNrX.js 5.89 kB │ gzip: 2.60 kB
|
||||
dist/assets/techdraw-BJsb309W.js 5.95 kB │ gzip: 2.66 kB
|
||||
dist/assets/fem-BB7u_DYL.js 6.35 kB │ gzip: 2.48 kB
|
||||
dist/assets/openscad-CFW1QTCA.js 6.35 kB │ gzip: 2.43 kB
|
||||
dist/assets/test-desktop-output-B7b_AS0y.js 6.38 kB │ gzip: 2.43 kB
|
||||
dist/assets/session-persistence-Dn8_y31c.js 6.80 kB │ gzip: 2.31 kB
|
||||
dist/assets/mesh-CHc1j622.js 6.86 kB │ gzip: 2.71 kB
|
||||
dist/assets/help-BDS1O4w7.js 7.00 kB │ gzip: 2.60 kB
|
||||
dist/assets/draft-OYHAw_73.js 7.02 kB │ gzip: 2.67 kB
|
||||
dist/assets/react-CYoUnH_4.js 7.16 kB │ gzip: 2.83 kB
|
||||
dist/assets/measure-CBI0LP-w.js 7.66 kB │ gzip: 2.91 kB
|
||||
dist/assets/test-desktop-adapter-3EjrcfzZ.js 8.08 kB │ gzip: 2.43 kB
|
||||
dist/assets/import-CMv4bYIE.js 8.87 kB │ gzip: 3.42 kB
|
||||
dist/assets/inspection-DiCbiJhh.js 8.92 kB │ gzip: 3.18 kB
|
||||
dist/assets/bim-xGmbnKPO.js 8.92 kB │ gzip: 2.91 kB
|
||||
dist/assets/assembly-BKfj7iHh.js 9.46 kB │ gzip: 3.09 kB
|
||||
dist/assets/document-C6cPH-tZ.js 10.00 kB │ gzip: 3.50 kB
|
||||
dist/assets/core-F-zBHDcX.js 10.00 kB │ gzip: 3.50 kB
|
||||
dist/assets/cam-CA5KV68x.js 12.62 kB │ gzip: 3.86 kB
|
||||
dist/assets/main-chain-CmvdvKLc.js 12.80 kB │ gzip: 4.47 kB
|
||||
dist/assets/stateful-family-dispatcher-DlrGRfkh.js 13.54 kB │ gzip: 3.86 kB
|
||||
dist/assets/cloud-Csavllry.js 14.41 kB │ gzip: 4.26 kB
|
||||
dist/assets/python-family-dispatcher-C3iPbGJV.js 17.54 kB │ gzip: 3.49 kB
|
||||
dist/assets/plot-axes-task-DrizjbDn.js 18.79 kB │ gzip: 6.13 kB
|
||||
dist/assets/draft-desktop-adapter-BZX2hBxE.js 19.62 kB │ gzip: 4.59 kB
|
||||
dist/assets/module-router-CTLekBqC.js 24.94 kB │ gzip: 6.97 kB
|
||||
dist/assets/bim-desktop-adapter-5rkAISAK.js 24.94 kB │ gzip: 6.48 kB
|
||||
dist/assets/sketcher-desktop-adapter-CVUOtLw7.js 25.94 kB │ gzip: 6.01 kB
|
||||
dist/assets/module-registry.generated-C_xajxGw.js 29.46 kB │ gzip: 4.95 kB
|
||||
dist/assets/techdraw-desktop-adapter-BYxSjq3R.js 43.67 kB │ gzip: 7.59 kB
|
||||
dist/assets/client-Bz7rxqNA.js 132.71 kB │ gzip: 43.06 kB
|
||||
dist/assets/partdesign-workspace-component-6g59gGkR.js 184.75 kB │ gzip: 47.81 kB
|
||||
dist/assets/project-shell-D-4Q5sGi.js 190.74 kB │ gzip: 51.78 kB
|
||||
dist/assets/web-view-task-BjjKw_QO.js 209.59 kB │ gzip: 49.26 kB
|
||||
dist/assets/import-export-preferences-B2mEfXKQ.js 361.69 kB │ gzip: 77.23 kB
|
||||
dist/assets/three.module-B4Clv3wn.js 566.00 kB │ gzip: 142.94 kB
|
||||
dist/assets/workbench-tsiS8CU2.js 1,103.37 kB │ gzip: 274.98 kB
|
||||
|
||||
[plugin builtin:vite-reporter]
|
||||
(!) Some chunks are larger than 500 kB after minification. Consider:
|
||||
- Using dynamic import() to code-split the application
|
||||
- Use build.rolldownOptions.output.codeSplitting to improve chunking: https://rolldown.rs/reference/OutputOptions.codeSplitting
|
||||
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
|
||||
47
tools/build_web_frontend.py
Normal file
47
tools/build_web_frontend.py
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the configured operator frontend from its fixed working directory.
|
||||
|
||||
No npm lifecycle hooks, standalone typechecks, tests or native builds. The
|
||||
caller groups related frontend edits before invoking this delivery command.
|
||||
"""
|
||||
import os
|
||||
import fcntl
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def main():
|
||||
web = Path(__file__).resolve().parents[1] / "web"
|
||||
vite = web / "node_modules/vite/bin/vite.js"
|
||||
node = shutil.which("node")
|
||||
if not node or not vite.is_file():
|
||||
print(f"Frontend dependency unavailable: node={node}, Vite={vite}", file=sys.stderr)
|
||||
return 2
|
||||
env = {**os.environ, "FREECAD_WEB_FAST_LINK": "ON"}
|
||||
# The foreground session and the sole scheduler share web/dist. Keep the
|
||||
# lock outside dist: Vite clears that directory before writing a bundle.
|
||||
with (web / ".freecad-web-frontend-build.lock").open("a") as lock:
|
||||
started = time.monotonic()
|
||||
waiting = False
|
||||
while True:
|
||||
try:
|
||||
fcntl.flock(lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
break
|
||||
except BlockingIOError:
|
||||
if not waiting:
|
||||
print("Waiting for the existing frontend delivery to release web/dist", flush=True)
|
||||
waiting = True
|
||||
if time.monotonic() - started >= 1800:
|
||||
print("Frontend delivery lock timed out; retain edits and resume after the active build", file=sys.stderr)
|
||||
return 124
|
||||
time.sleep(0.25)
|
||||
# Vite retains the descriptor if the outer runner exits mid-delivery.
|
||||
return subprocess.run([node, str(vite), "build"], cwd=web, env=env,
|
||||
pass_fds=(lock.fileno(),), check=False).returncode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
347
tools/prepare_implementation_business_batch.py
Normal file
347
tools/prepare_implementation_business_batch.py
Normal file
@@ -0,0 +1,347 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Select the next implementation-first business batch across phase boundaries."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from implementation_first import implementation_ready_ids, user_validation_deferred, actionable_confirmed_gap
|
||||
from record_runtime_issue import ACTIVE_STATUSES
|
||||
from failure_reconciliation import write as write_json
|
||||
from scheduler_progress import cooling_ids, retry_details
|
||||
from policy_io import read_policy
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CAPABILITIES = ROOT / "docs/current/feature-capability-registry.json"
|
||||
ISSUES = ROOT / "docs/current/runtime-issue-ledger.json"
|
||||
BATCH = ROOT / "docs/current/module-batch-manifest.json"
|
||||
POLICY = "delivery-workflow-first-v5"
|
||||
|
||||
|
||||
def coverage_development_due(history: list[dict]) -> bool:
|
||||
"""Reserve case authoring after three model rounds without new delivery."""
|
||||
attempts = 0
|
||||
for row in reversed(history):
|
||||
if row.get("outcome") == "provider_wait":
|
||||
continue
|
||||
if row.get("businessLane") == "development" or row.get("firstBusinessPassIds"):
|
||||
return False
|
||||
# Local evidence refresh and historical reconciliation do not reset
|
||||
# delivery starvation, but neither spends a model opportunity here.
|
||||
if row.get("tier") not in {"feature-workflow", "business-acceptance"}:
|
||||
continue
|
||||
if row.get("outcome") in {"no_progress", "source_edits_pending_outcome"} or row.get("fileChanges"):
|
||||
attempts += 1
|
||||
if attempts >= 3:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def recovery_budget_spent(history: list[dict]) -> bool:
|
||||
failed_turns = 0
|
||||
for row in reversed(history):
|
||||
if row.get("outcome") == "provider_wait":
|
||||
continue
|
||||
if row.get("businessLane") == "development" or row.get("tier") != "business-acceptance":
|
||||
return False
|
||||
if row.get("outcome") == "progress":
|
||||
if not row.get("fileChanges"):
|
||||
continue # A local evidence refresh must not reset failed model recovery work.
|
||||
return False
|
||||
if row.get("outcome") in {"no_progress", "source_edits_pending_outcome"}:
|
||||
failed_turns += 1
|
||||
if failed_turns >= 2:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def continuous_coverage_due(history: list[dict]) -> bool:
|
||||
refreshes = 0
|
||||
for row in reversed(history):
|
||||
if row.get("outcome") == "provider_wait":
|
||||
continue
|
||||
if row.get("businessLane") == "development" or row.get("firstBusinessPassIds"):
|
||||
return False
|
||||
if row.get("tier") == "business-acceptance":
|
||||
refreshes += 1
|
||||
if refreshes >= 2:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def load(path: Path) -> dict:
|
||||
if path.name == "execution-policy.json":
|
||||
return read_policy(path)
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return {}
|
||||
return value if isinstance(value, dict) else {}
|
||||
|
||||
|
||||
def gap_reuse_context(row: dict, repairs: list[dict]) -> dict:
|
||||
"""Pass existing implementation notes, not another acceptance audit."""
|
||||
shared_hosts = {"web/src/project-shell.tsx", "web/src/workbench-shell.tsx"}
|
||||
anchors = set(row.get("sourceAnchors", [])) - shared_hosts
|
||||
completed = [candidate for candidate in repairs
|
||||
if candidate.get("key") != row.get("key")
|
||||
and candidate.get("status") in {"implemented_unvalidated", "implemented_pending_validation", "resolved"}
|
||||
and (candidate.get("module") == row.get("module")
|
||||
or anchors.intersection(candidate.get("sourceAnchors", [])))]
|
||||
# Keyed entries are append-oriented. Related components take priority over
|
||||
# general module history; at most three summaries enter the next prompt.
|
||||
completed = sorted(reversed(completed),
|
||||
key=lambda candidate: bool(anchors.intersection(candidate.get("sourceAnchors", []))),
|
||||
reverse=True)[:3]
|
||||
return {
|
||||
"instruction": "Reuse these existing implementation notes and named artifacts; this is not a request to recheck completed work. Fix only the selected source-confirmed gap. Do not duplicate its document owner, controls, geometry implementation or build pipeline. On completion update this exact key INSIDE docs/current/execution-policy.json confirmedGapRepairs with status=implemented_unvalidated, record, result, implementedAt and remainingFunctionalLimitations. A separate implementation note alone does not dequeue the task. If your note already records completion but this keyed entry is still open, copy the existing implementation bookkeeping into the keyed entry without repeating builds or verification.",
|
||||
"relatedCompleted": [{"key": candidate.get("key"), "status": candidate.get("status"),
|
||||
"record": candidate.get("record"), "result": str(candidate.get("result", ""))[:900],
|
||||
"sourceAnchors": candidate.get("sourceAnchors", [])[:8]}
|
||||
for candidate in completed],
|
||||
"continuationRecord": row.get("record"),
|
||||
"sourceAccess": "The user already authorizes bounded source reads of actual native document owners and dependencies needed by this functional package. The source reader accepts exact CoreProbe owner/helper files and direct relative frontend imports from named packet anchors for confirmed packages. Missing packet metadata is not a need for permission. Follow already located include/import/call paths, retain the file/line checkpoint, and continue implementation without asking to add those existing owners to an allowlist. Keep source-window budgets and avoid broad rescans; when the budget is exhausted, save the next exact file/line for a fresh bounded turn instead of repeating completed reads.",
|
||||
"publishedBuildTree": "Resolve each target's existing artifact source in web/tools/sync-assets.mjs before building. A target may exist in several configured trees; use the one actually published. AssemblyProbe shares the common configure tree; AssemblyKernelProbe retains assembly-configure. Group targets only when they share that published tree. Do not compile an alternate tree and then sync stale artifacts from another.",
|
||||
"buildRecordScope": "Necessary builds are authorized by the implementation task, not by a pipeline flag. packet.build is a default hint, NOT an exhaustive allowlist. Ignore another batch/target's pipeline report; never wait for packet authorization. Trace changed sources/headers/resources to their actual document-owner targets and build only those targets in the existing trees under tools/web_build_lock.py, -j8, grouping targets in the same tree. CMake/Ninja owns dependency regeneration. Reuse completed unchanged builds; dependencyDirty=false alone does not cover changed headers. Explicitly cd to web before Vite; a shell cwd error is not missing dependencies.",
|
||||
"policyWrite": "Write receipt fields to a separate JSON patch file, then run python3 tools/policy_io.py --key <existing-key> --patch-file <receipt-patch.json>. This helper locks, reads fresh policy, changes only that key and atomically publishes with a durable backup. Never open execution-policy.json in write mode, loop json.dump over live policy, or overwrite the entire policy from a receipt. Missing or malformed policy never enables validation.",
|
||||
"frontendBuild": {
|
||||
"command": "python3 /home/mes123456/FreeCADWasmDev/working/FreeCAD/tools/build_web_frontend.py",
|
||||
"reason": "Use the existing Vite configuration and normal web/dist output directly. npm run build invokes prebuild geometry audit and tsc --noEmit, which are not authorized in the current no-validation phase. Do not run npm run build, its prebuild hook, or replace the config with a temporary build. Keep the repository audit scripts unchanged for a future explicitly authorized validation phase.",
|
||||
"reuse": "If a later completed configured frontend build already includes this task's last source edit, reuse that delivery record instead of rebuilding just to refresh a task note. Do not run evidence verification.",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def related_gap_package(ready: list[dict], policy: dict) -> list[dict]:
|
||||
"""Group concrete work sharing an editor/owner, retaining partial packages."""
|
||||
config = policy.get("functionalPackages", {})
|
||||
if not config.get("enabled"):
|
||||
return ready[:1]
|
||||
limit = max(1, min(8, int(config.get("maxMembers", 6))))
|
||||
previous = load(BATCH)
|
||||
prior_keys = {row.get("key") for row in previous.get("confirmedGaps", [])}
|
||||
carried = [row for row in ready if row.get("key") in prior_keys]
|
||||
lead = carried[0] if carried else ready[0]
|
||||
# UI packages keep command families on one mounted host/build ownership.
|
||||
# Broad topic membership alone must never merge unrelated runtime owners.
|
||||
ui = policy.get("desktopUiParity", {})
|
||||
if ui.get("enabled"):
|
||||
order = ui.get("packageOrder", [])
|
||||
ready = sorted(ready, key=lambda row: order.index(row.get("desktopUiPackage"))
|
||||
if row.get("desktopUiPackage") in order else len(order))
|
||||
requested = [row for row in ready if row.get("desktopUiPackage") == ui.get("nextPackage")]
|
||||
lead = requested[0] if requested else carried[0] if carried else ready[0]
|
||||
generic = {"web/src/project-shell.tsx", "web/src/workbench-shell.tsx",
|
||||
"web/src/desktop-command-dispatcher.ts", "web/src/module-router.ts",
|
||||
"web/src/module-registry.generated.ts"}
|
||||
|
||||
def anchors(row):
|
||||
return {path for path in [row.get("source"), *row.get("sourceAnchors", [])]
|
||||
if isinstance(path, str) and path.startswith("web/src/") and path not in generic}
|
||||
|
||||
group = lead.get("functionalPackage")
|
||||
shared = anchors(lead)
|
||||
selected = [lead]
|
||||
for row in [*carried, *ready]:
|
||||
if row in selected or len(selected) >= limit:
|
||||
continue
|
||||
same_group = bool(group) and row.get("functionalPackage") == group and row.get("module") == lead.get("module")
|
||||
same_owner = row.get("module") == lead.get("module") and bool(shared & anchors(row))
|
||||
ui_owner = (ui.get("enabled") and lead.get("desktopUiPackage")
|
||||
and row.get("desktopUiPackage") == lead.get("desktopUiPackage")
|
||||
and bool(lead.get("uiBuildGroup")) and row.get("uiBuildGroup") == lead.get("uiBuildGroup"))
|
||||
if same_group or same_owner or ui_owner:
|
||||
selected.append(row)
|
||||
return selected
|
||||
|
||||
|
||||
def feature_phase_remaining() -> int:
|
||||
rows = [row for row in load(CAPABILITIES).get("records", []) if isinstance(row, dict) and row.get("id")]
|
||||
ready = implementation_ready_ids()
|
||||
active_issues = {
|
||||
str(row.get("capabilityId"))
|
||||
for row in load(ISSUES).get("issues", [])
|
||||
if isinstance(row, dict) and str(row.get("status", "open")) in ACTIVE_STATUSES
|
||||
}
|
||||
return len(({str(row["id"]) for row in rows} - ready) | active_issues)
|
||||
|
||||
|
||||
def phase_order(previous: dict, remaining: int) -> list[tuple[str, list[str]]]:
|
||||
policy = load(ROOT / "docs/current/execution-policy.json")
|
||||
if policy.get("continuousDevelopment") is True and policy.get("completionFocus") == "business-acceptance":
|
||||
history = load(ROOT / "docs/current/scheduler-progress.json").get("history", [])
|
||||
development = ("P3-uncovered-business-development", ["tools/prepare_business_acceptance_batch.py", "--uncovered-only"])
|
||||
refresh = ("P2-current-executable-acceptance", ["tools/prepare_business_acceptance_batch.py", "--covered-only"])
|
||||
if policy.get("prioritizeCoverageGaps") is True:
|
||||
return [
|
||||
("P0-uncovered-business-development", development[1]),
|
||||
("P1-first-acceptance-of-completed-gaps", refresh[1]),
|
||||
("P2-remaining-implementation", ["tools/prepare_feature_capability_batch.py"]),
|
||||
("P3-product-integration", ["tools/prepare_product_business_batch.py"]),
|
||||
("P3-format-integration", ["tools/prepare_format_business_batch.py"]),
|
||||
]
|
||||
return [
|
||||
("P0-authorized-priority-acceptance", ["tools/prepare_business_acceptance_batch.py", "--priority-only"]),
|
||||
("P1-current-business-repair", ["tools/prepare_feature_capability_batch.py", "--runtime-only"]),
|
||||
*([development, refresh] if continuous_coverage_due(history) else [refresh, development]),
|
||||
("P4-remaining-implementation", ["tools/prepare_feature_capability_batch.py"]),
|
||||
("P4-product-integration", ["tools/prepare_product_business_batch.py"]),
|
||||
("P4-format-integration", ["tools/prepare_format_business_batch.py"]),
|
||||
("P5-failure-reconciliation", ["tools/prepare_failure_reconciliation_batch.py"]),
|
||||
]
|
||||
if user_validation_deferred():
|
||||
return [
|
||||
("P1-primary-workspace-missing-integration", ["tools/prepare_product_business_batch.py"]),
|
||||
("P0-reported-functional-repair", ["tools/prepare_feature_capability_batch.py", "--runtime-only"]),
|
||||
("P1-connect-delivery-workflows", ["tools/prepare_feature_capability_batch.py", "--delivery-only"]),
|
||||
("P2-connect-all-features", ["tools/prepare_feature_capability_batch.py"]),
|
||||
("P4-connect-format-workflows", ["tools/prepare_format_business_batch.py"]),
|
||||
]
|
||||
history = load(ROOT / "docs/current/scheduler-progress.json").get("history", [])
|
||||
last_tier = previous.get("selected", {}).get("tier")
|
||||
if last_tier == "failure-reconciliation":
|
||||
last_tier = next((row.get("tier") for row in reversed(load(ROOT / "docs/current/scheduler-progress.json").get("history", []))
|
||||
if row.get("tier") and row["tier"] != "failure-reconciliation"), None)
|
||||
implementation = [("P2-delivery-implementation", ["tools/prepare_feature_capability_batch.py", "--delivery-only"])]
|
||||
acceptance = [("P2-delivery-acceptance", ["tools/prepare_business_acceptance_batch.py", "--delivery-only"])]
|
||||
development = []
|
||||
if previous.get("businessLane") != "development" and (recovery_budget_spent(history) or coverage_development_due(history)):
|
||||
command = ["tools/prepare_business_acceptance_batch.py", "--uncovered-only"]
|
||||
module = previous.get("selected", {}).get("module")
|
||||
if module:
|
||||
command.extend(["--exclude-module", module])
|
||||
development = [("P1-uncovered-business-development", command)]
|
||||
return [
|
||||
*development,
|
||||
("P0-observed-runtime-repair", ["tools/prepare_feature_capability_batch.py", "--runtime-only"]),
|
||||
("P0-executable-business-acceptance", ["tools/prepare_business_acceptance_batch.py", "--covered-only"]),
|
||||
*(implementation + acceptance if last_tier == "business-acceptance" else acceptance + implementation),
|
||||
("P3-react-product-integration", ["tools/prepare_product_business_batch.py"]),
|
||||
("P4-module-import-export-workflows", ["tools/prepare_format_business_batch.py"]),
|
||||
*([("P5-remaining-feature-implementation", ["tools/prepare_feature_capability_batch.py"])] if remaining else []),
|
||||
("P6-remaining-business-acceptance", ["tools/prepare_business_acceptance_batch.py"]),
|
||||
("P5-failure-reconciliation", ["tools/prepare_failure_reconciliation_batch.py"]),
|
||||
]
|
||||
|
||||
|
||||
def acceptance_preempts(batch: dict) -> bool:
|
||||
if user_validation_deferred():
|
||||
return False
|
||||
if batch.get("selected", {}).get("tier") != "feature-workflow":
|
||||
return False
|
||||
policy = load(ROOT / "docs/current/execution-policy.json")
|
||||
if policy.get("continuousDevelopment") is True and policy.get("completionFocus") == "business-acceptance":
|
||||
return batch.get("selected", {}).get("family") != "runtime-issue"
|
||||
from prepare_business_acceptance_batch import candidates, inventory
|
||||
state = inventory()
|
||||
if candidates(state, covered_only=True):
|
||||
return True
|
||||
history = load(ROOT / "docs/current/scheduler-progress.json").get("history", [])
|
||||
if coverage_development_due(history) and candidates(state, uncovered_only=True):
|
||||
return True
|
||||
if candidates(state, delivery_only=True) and batch.get("selectionScope") != "delivery":
|
||||
return True
|
||||
if batch.get("schedulingPolicy") != POLICY:
|
||||
return True
|
||||
history = [row for row in load(ROOT / "docs/current/scheduler-progress.json").get("history", [])
|
||||
if row.get("tier") and row["tier"] != "failure-reconciliation" and row.get("outcome") != "provider_wait"]
|
||||
# Bound a preserved implementation turn even when its selected IDs change.
|
||||
return bool(len(history) >= 2 and all(row["tier"] == "feature-workflow" for row in history[-2:])
|
||||
and candidates(state))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
policy = load(ROOT / "docs/current/execution-policy.json")
|
||||
if policy.get("workScope") == "gap-repair-only":
|
||||
# The migration is already delivered. Old inventories and missing test
|
||||
# receipts must not manufacture another full-port development queue.
|
||||
repairs = [row for row in policy.get("confirmedGapRepairs", []) if actionable_confirmed_gap(row)]
|
||||
if not repairs:
|
||||
write_json(BATCH, {"schema": "freecad-web-module-function-batch/v1", "status": "no_ready_batch",
|
||||
"schedulingPolicy": "confirmed-gap-repair-only", "selectionScope": "confirmed-gaps",
|
||||
"records": [], "queue": [],
|
||||
"reason": "No confirmed gap queued. Reuse completed migration; do not restart historical backlog or verification."})
|
||||
print(json.dumps({"status": "no_ready_batch", "reason": "confirmed gaps only; completed migration preserved"}))
|
||||
return 0
|
||||
deferred = cooling_ids(candidates={row["id"] for row in repairs})
|
||||
ready = [row for row in repairs if row["id"] not in deferred]
|
||||
if not ready:
|
||||
details = retry_details({row["id"] for row in repairs})
|
||||
write_json(BATCH, {"schema": "freecad-web-module-function-batch/v1", "status": "waiting_for_retry",
|
||||
"schedulingPolicy": "confirmed-gap-repair-only", "records": [], "queue": [], **details})
|
||||
print(json.dumps({"status": "waiting_for_retry", **details}))
|
||||
return 0
|
||||
members = related_gap_package(ready, policy)
|
||||
row = dict(members[0])
|
||||
row["reuseContext"] = gap_reuse_context(row, policy.get("confirmedGapRepairs", []))
|
||||
if policy.get("desktopUiParity", {}).get("enabled"):
|
||||
row["reuseContext"]["desktopUiParity"] = policy["desktopUiParity"]
|
||||
row["reuseContext"]["instruction"] += (
|
||||
" Current mainline: desktop UI parity packages. Reuse existing workbench/task/adapter/native owners. "
|
||||
"Implement command registration -> desktop menu/toolbar -> task/property/dialog -> native call -> status feedback "
|
||||
"as one coherent family, including desktop labels, icons, order, shortcuts, enabled state, spacing and cancel semantics. "
|
||||
"The complete UI scope includes menus, buttons, property pages, dialogs, operation interfaces, control/task panels, context menus and status feedback. "
|
||||
"Use docs/current/desktop-ui-parity-inventory.json as a source locator, including Python InitGui/task panels and shared src/Gui, never as proof of parity. "
|
||||
"Do not drop remaining original UI items as unrelated or close an entire package after a subset. Record precise unfinished commands/surfaces and the next edit on the same original key. "
|
||||
"No JSON/Python console required for ordinary operators. Geometry remains native OCCT WASM; Three.js displays only. "
|
||||
"Report: module | UI items | source | route | OCCT boundary | status | conclusion. "
|
||||
"Use tools/policy_io.py --key KEY --patch-file PATH to save receipts; never truncate live policy.")
|
||||
import hashlib
|
||||
product = row["module"] == "Web" and row["id"].startswith("product:")
|
||||
tier = "product-integration" if product else "feature-workflow"
|
||||
family = "gap-repair-" + str(row.get("key") or row["id"].replace(":", "-"))
|
||||
strategy = "module-business-first-deferred-validation" if product else "feature-first-deferred-validation"
|
||||
parts = [row["module"], tier, family, row["id"]] if product else [row["module"], family, row["id"]]
|
||||
if len(members) > 1:
|
||||
parts.extend(member["id"] for member in members[1:])
|
||||
identity = hashlib.sha256("|".join(parts).encode()).hexdigest()[:24]
|
||||
write_json(BATCH, {"schema": "freecad-web-module-function-batch/v1", "status": "ready",
|
||||
"batchId": identity, "strategy": strategy, "schedulingPolicy": "confirmed-gap-repair-only",
|
||||
"minimumRecords": 1, "singletonFallback": True,
|
||||
"selectionScope": "confirmed-gaps", "taskId": load(ROOT / "docs/current/task-state.json").get("active_task"),
|
||||
"selected": {"module": row["module"], "tier": tier, "family": family, "recordCount": len(members)},
|
||||
"records": [[member["id"], member.get("command", member["id"]), "implementation_required", member["source"], member.get("line", 1)] for member in members],
|
||||
"confirmedGaps": members,
|
||||
"functionalPackage": {"name": row.get("functionalPackage") or row["module"] + " shared workflow",
|
||||
"keys": [member["key"] for member in members],
|
||||
"rule": "Implement related members together, share source reading and integration, then build each affected target once and one configured frontend bundle. Record each member truthfully; preserve unfinished members without repeating completed work."},
|
||||
"confirmedGap": row, "businessWorkflow": "repair-existing-function"})
|
||||
print(json.dumps({"status": "ready", "selectedGap": row["id"]}))
|
||||
return 0
|
||||
previous = load(BATCH)
|
||||
remaining = feature_phase_remaining()
|
||||
waits = []
|
||||
for phase, command in phase_order(previous, remaining):
|
||||
result = subprocess.run([sys.executable, *command], cwd=ROOT, text=True, capture_output=True, check=False)
|
||||
if result.stdout:
|
||||
print(result.stdout, end="")
|
||||
if result.stderr:
|
||||
print(result.stderr, end="", file=sys.stderr)
|
||||
if result.returncode:
|
||||
return result.returncode
|
||||
batch = load(BATCH)
|
||||
if batch.get("status") == "ready":
|
||||
batch["schedulingPolicy"] = POLICY
|
||||
write_json(BATCH, batch)
|
||||
# Runtime issues have their own priority phase. Historical review
|
||||
# must not replace an executable business batch at module handoff.
|
||||
print(json.dumps({"implementationPhase": phase, "featureRemaining": remaining}, ensure_ascii=False))
|
||||
return 0
|
||||
if batch.get("status") == "waiting_for_retry":
|
||||
waits.append(batch)
|
||||
elif batch.get("status") != "no_ready_batch":
|
||||
print(json.dumps({"status": "error", "reason": "planner returned an unknown batch state", "phase": phase}))
|
||||
return 2
|
||||
if waits:
|
||||
batch = min(waits, key=lambda value: float(value["retryAt"]))
|
||||
batch["deferredIds"] = sorted({record_id for value in waits for record_id in value.get("deferredIds", [])})
|
||||
batch["deferredRecords"] = len(batch["deferredIds"])
|
||||
BATCH.write_text(json.dumps(batch, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
235
tools/read_implementation_source.py
Normal file
235
tools/read_implementation_source.py
Normal file
@@ -0,0 +1,235 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read a bounded missing patch anchor from the current implementation packet."""
|
||||
import argparse
|
||||
import fcntl
|
||||
import hashlib
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PACKET = ROOT / "docs/current/module-implementation-packet.json"
|
||||
MAX_WINDOWS = 4
|
||||
CONTINUOUS_MAX_WINDOWS = 16
|
||||
MAX_TOTAL_WINDOW_BYTES = 48000
|
||||
MAX_WINDOW_BYTES = 12000
|
||||
|
||||
|
||||
def window_limit():
|
||||
try:
|
||||
policy = json.loads((ROOT / "docs/current/execution-policy.json").read_text())
|
||||
except (OSError, ValueError):
|
||||
return MAX_WINDOWS
|
||||
return CONTINUOUS_MAX_WINDOWS if policy.get("continuousDevelopment") is True else MAX_WINDOWS
|
||||
|
||||
|
||||
def source_limits(packet):
|
||||
limit = window_limit()
|
||||
count = len({row.get("apiId") for row in packet.get("records", [])
|
||||
if isinstance(row, dict) and isinstance(row.get("apiId"), str) and row["apiId"]})
|
||||
scale = min(3, max(1, count)) if limit == CONTINUOUS_MAX_WINDOWS else 1
|
||||
return limit * scale, MAX_TOTAL_WINDOW_BYTES * scale
|
||||
|
||||
|
||||
def packet_paths(packet):
|
||||
names = {row.get("path") for key in ("supportFiles", "editableExcerpts") for row in packet.get(key, [])}
|
||||
gap = packet.get("confirmedGap") or {}
|
||||
names.add(gap.get("source"))
|
||||
names.update(name for name in gap.get("sourceAnchors", []) if isinstance(name, str))
|
||||
for member in packet.get("confirmedGaps", []):
|
||||
names.add(member.get("source"))
|
||||
names.update(name for name in member.get("sourceAnchors", []) if isinstance(name, str))
|
||||
contract_name = packet.get("acceptanceContract")
|
||||
if isinstance(contract_name, str):
|
||||
contract_path = (ROOT / contract_name).resolve()
|
||||
if contract_path.is_relative_to(ROOT) and contract_path.is_relative_to((ROOT / "docs/business-acceptance").resolve()) and contract_path.is_file():
|
||||
try:
|
||||
contract = json.loads(contract_path.read_text(encoding="utf-8"))
|
||||
if isinstance(contract, dict):
|
||||
names.add(contract_name)
|
||||
command = contract.get("command", [])
|
||||
if isinstance(command, list) and len(command) > 1 and isinstance(command[1], str):
|
||||
names.add(command[1])
|
||||
for key in ("dependencies", "caseDependencies"):
|
||||
paths = contract.get(key, [])
|
||||
if isinstance(paths, list):
|
||||
names.update(name for name in paths if isinstance(name, str))
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
for row in packet.get("records", []):
|
||||
source = row.get("source")
|
||||
if source:
|
||||
names.add(source if (ROOT / source).is_file() else str(Path("freecad-source") / source))
|
||||
names.update(match.get("path") for match in row.get("nativeMatches", []))
|
||||
for gap in row.get("implementationGaps", []):
|
||||
names.update(match.get("path") for match in gap.get("sourceCandidates", []))
|
||||
return {(ROOT / name).resolve() for name in names if isinstance(name, str) and name}
|
||||
|
||||
|
||||
def module_source_path(path, packet):
|
||||
"""Permit named in-module implementation anchors before case declaration."""
|
||||
module = packet.get("module")
|
||||
if window_limit() != CONTINUOUS_MAX_WINDOWS or not isinstance(module, str) or not module.isalnum():
|
||||
return False
|
||||
if not path.is_relative_to(ROOT):
|
||||
return False
|
||||
relative = path.relative_to(ROOT)
|
||||
if packet.get("confirmedGaps") and relative.parent == Path("freecad-source/src/WebAssembly/FreeCADCoreProbe"):
|
||||
# Functional packages legitimately cross module boundaries through
|
||||
# existing document owners. Keep exact-file/window budgets, but don't
|
||||
# turn missing packet metadata into a user-authorization blocker.
|
||||
return path.suffix.lower() in {".cpp", ".h", ".hpp", ".py"} or path.name == "CMakeLists.txt"
|
||||
if relative.is_relative_to(Path("freecad-source/src/Mod") / module):
|
||||
return path.suffix.lower() in {".cpp", ".cxx", ".h", ".hpp", ".py", ".ui", ".xml", ".txt"}
|
||||
slug = module.lower()
|
||||
if relative.parent == Path("tools"):
|
||||
return path.name.startswith(slug + "-") and path.name.endswith("-business-cases.mjs")
|
||||
return (relative.parent == Path("web/src") and path.name.startswith(slug + "-")
|
||||
and path.suffix in {".ts", ".tsx", ".json"})
|
||||
|
||||
|
||||
def frontend_dependency_path(path, packet):
|
||||
"""Allow one exact relative import from a confirmed package's named sources.
|
||||
|
||||
Workbench UI packages cross module boundaries through document facades and
|
||||
shared task components. Resolve only direct imports; this is not a recursive
|
||||
module crawl and it does not change the existing excerpt/turn budgets.
|
||||
"""
|
||||
if window_limit() != CONTINUOUS_MAX_WINDOWS or not (packet.get("confirmedGaps") or packet.get("confirmedGap")):
|
||||
return False
|
||||
frontend = (ROOT / "web/src").resolve()
|
||||
if not path.is_relative_to(frontend) or path.suffix not in {".ts", ".tsx", ".json"}:
|
||||
return False
|
||||
for anchor in sorted(packet_paths(packet)):
|
||||
if (not anchor.is_relative_to(frontend) or anchor.suffix not in {".ts", ".tsx"}
|
||||
or not anchor.is_file() or anchor.stat().st_size > 512 * 1024):
|
||||
continue
|
||||
source = anchor.read_text(encoding="utf-8")
|
||||
for specifier in re.findall(r'''(?:\bfrom\s*|\bimport\s*(?:\(\s*)?)["'](\.[^"'\n]+)["']''', source):
|
||||
dependency = (anchor.parent / specifier).resolve()
|
||||
candidates = {dependency}
|
||||
if dependency.suffix == ".js":
|
||||
candidates.update(dependency.with_suffix(suffix) for suffix in (".ts", ".tsx"))
|
||||
elif not dependency.suffix:
|
||||
candidates.update(Path(str(dependency) + suffix) for suffix in (".ts", ".tsx", ".json"))
|
||||
if path in candidates:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def packet_summary(packet):
|
||||
# Task metadata is structured JSON, not a source file to slice by lines.
|
||||
result = {"batchId": packet.get("batchId"), "records": [], "supportPaths": [],
|
||||
"instruction": "This is task metadata, not source lines. Use sourceReadCommand below; changing --start on the packet does not read C++ or Python."}
|
||||
retained = packet.get("retainedBusinessResults", [])
|
||||
result["retainedBusinessResults"] = []
|
||||
for row in retained:
|
||||
result["retainedBusinessResults"].append(row)
|
||||
if len(json.dumps(result).encode("utf-8")) > MAX_WINDOW_BYTES // 2:
|
||||
result["retainedBusinessResults"].pop()
|
||||
break
|
||||
for row in packet.get("records", []):
|
||||
entry = {key: row[key] for key in ("apiId", "name", "source", "excerptLine") if key in row}
|
||||
if isinstance(row.get("source"), str) and row.get("excerptLine"):
|
||||
source = row["source"]
|
||||
if not (ROOT / source).is_file(): source = str(Path("freecad-source") / source)
|
||||
entry["sourceReadCommand"] = shlex.join([
|
||||
"python3", "tools/read_implementation_source.py", source,
|
||||
"--start", str(max(1, int(row["excerptLine"]))), "--lines", "60",
|
||||
"--reason", "Read selected command native source anchor",
|
||||
])
|
||||
entry["gaps"] = [{key: gap[key] for key in ("stage", "classification", "reason") if key in gap}
|
||||
for gap in row.get("implementationGaps", [])]
|
||||
result["records"].append(entry)
|
||||
if len(json.dumps(result).encode("utf-8")) > MAX_WINDOW_BYTES - 1000:
|
||||
result["records"].pop()
|
||||
break
|
||||
result["omittedRecords"] = len(packet.get("records", [])) - len(result["records"])
|
||||
for path in sorted(packet_paths(packet)):
|
||||
if not path.is_relative_to(ROOT) or not path.is_file():
|
||||
continue
|
||||
result["supportPaths"].append(str(path.relative_to(ROOT)))
|
||||
if len(json.dumps(result).encode("utf-8")) > MAX_WINDOW_BYTES - 200:
|
||||
result["supportPaths"].pop()
|
||||
break
|
||||
return json.dumps(result)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("path")
|
||||
parser.add_argument("--start", required=True, type=int)
|
||||
parser.add_argument("--lines", type=int, default=60)
|
||||
parser.add_argument("--reason", required=True)
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
marker_value = os.environ.get("FREECAD_SOURCE_BUDGET_MARKER")
|
||||
if not marker_value:
|
||||
raise ValueError("This reader requires a scheduler-owned turn budget")
|
||||
if not 1 <= args.lines <= 100 or args.start < 1:
|
||||
raise ValueError("Use a positive --start and 1..100 --lines")
|
||||
if not args.reason.strip() or len(args.reason) > 240:
|
||||
raise ValueError("Explain the missing anchor in --reason (1..240 characters)")
|
||||
packet = json.loads(PACKET.read_text(encoding="utf-8"))
|
||||
path = (ROOT / args.path).resolve()
|
||||
metadata = path == PACKET.resolve()
|
||||
if path.is_relative_to(ROOT) and not path.is_file():
|
||||
raise ValueError("Requested source file does not exist or is not a file; locate the exact filename with a scoped file search")
|
||||
if not path.is_relative_to(ROOT) or (not metadata and path not in packet_paths(packet)
|
||||
and not module_source_path(path, packet)
|
||||
and not frontend_dependency_path(path, packet)) or not path.is_file():
|
||||
raise ValueError("Use an exact packet dependency, its direct frontend import, or current-module source/case path; no directory scans")
|
||||
if path.stat().st_size > 16 * 1024 * 1024:
|
||||
raise ValueError("This file exceeds the bounded source reader size limit")
|
||||
if metadata:
|
||||
excerpt = packet_summary(packet)
|
||||
args.start = 1
|
||||
else:
|
||||
with path.open(encoding="utf-8") as source:
|
||||
excerpt = "".join(itertools.islice(source, args.start - 1, args.start - 1 + args.lines))
|
||||
if not excerpt:
|
||||
raise ValueError("The requested anchor is beyond EOF; use the packet's named line")
|
||||
size = len(excerpt.encode("utf-8"))
|
||||
if size > MAX_WINDOW_BYTES:
|
||||
raise ValueError("Excerpt exceeds 12000 bytes; reduce --lines, do not dump a minified file")
|
||||
relative = str(path.relative_to(ROOT))
|
||||
fingerprint = hashlib.sha256(f"{relative}:{args.start}:{excerpt}".encode("utf-8")).hexdigest()
|
||||
# Per-turn file locking prevents parallel requests from exceeding the
|
||||
# bounded allowance. Repeated unchanged windows add no context.
|
||||
budget_path = Path(marker_value + ".windows.json")
|
||||
with budget_path.open("a+", encoding="utf-8") as budget:
|
||||
fcntl.flock(budget, fcntl.LOCK_EX)
|
||||
budget.seek(0)
|
||||
data = budget.read()
|
||||
state = json.loads(data) if data else {"batchId": packet["batchId"], "windows": [], "bytes": 0}
|
||||
if state["batchId"] != packet["batchId"]:
|
||||
raise ValueError("Batch changed during this turn; retain the current implementation checkpoint")
|
||||
if any(row["fingerprint"] == fingerprint for row in state["windows"]):
|
||||
print(json.dumps({"status": "already_in_context", "path": relative, "start": args.start,
|
||||
"instruction": "Reuse the unchanged excerpt already returned in this turn"}))
|
||||
return 0
|
||||
limit, byte_limit = source_limits(packet)
|
||||
if len(state["windows"]) >= limit or state["bytes"] + size > byte_limit:
|
||||
raise ValueError(f"Supplemental source budget reached ({limit} windows / {byte_limit} bytes); reuse retained excerpts and continue the identified repair")
|
||||
state["windows"].append({"path": relative, "start": args.start, "lines": args.lines,
|
||||
"fingerprint": fingerprint, "bytes": size, "reason": args.reason})
|
||||
state["bytes"] += size
|
||||
budget.seek(0)
|
||||
budget.truncate()
|
||||
budget.write(json.dumps(state, separators=(",", ":")))
|
||||
budget.flush()
|
||||
print(json.dumps({"status": "packet_summary" if metadata else "source_window", "batchId": packet["batchId"], "path": relative,
|
||||
"start": args.start, "bytes": size, "remainingWindows": limit - len(state["windows"]),
|
||||
"excerpt": excerpt}, ensure_ascii=False))
|
||||
return 0
|
||||
except (OSError, ValueError, KeyError) as error:
|
||||
print(f"IMPLEMENTATION_SOURCE_WINDOW_REJECTED: {error}", file=sys.stderr)
|
||||
return 64
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
90
web/public/icons/part/Part_Attachment.svg
Normal file
90
web/public/icons/part/Part_Attachment.svg
Normal file
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="64px" height="64px" id="svg2726" version="1.1">
|
||||
<defs id="defs2728">
|
||||
<linearGradient id="linearGradient3860">
|
||||
<stop style="stop-color:#204a87;stop-opacity:1" offset="0" id="stop3862" />
|
||||
<stop style="stop-color:#729fcf;stop-opacity:1" offset="1" id="stop3864" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3828">
|
||||
<stop style="stop-color:#729fcf;stop-opacity:1" offset="0" id="stop3830" />
|
||||
<stop style="stop-color:#204a87;stop-opacity:1" offset="1" id="stop3832" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3766">
|
||||
<stop style="stop-color:#004bed;stop-opacity:1" offset="0" id="stop3768" />
|
||||
<stop style="stop-color:#bbd7ff;stop-opacity:1" offset="1" id="stop3770" />
|
||||
</linearGradient>
|
||||
<radialGradient xlink:href="#linearGradient3144-4" id="radialGradient3850-9" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,0.6985294,0,202.82863)" cx="225.26402" cy="672.79736" fx="225.26402" fy="672.79736" r="34.345188" />
|
||||
<linearGradient id="linearGradient3144-4">
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="0" id="stop3146-2" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:0" offset="1" id="stop3148-0" />
|
||||
</linearGradient>
|
||||
<linearGradient xlink:href="#linearGradient3766-6" id="linearGradient3774-3" x1="-670.94934" y1="-470.34323" x2="-296.345" y2="-470.34323" gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient id="linearGradient3766-6">
|
||||
<stop style="stop-color:#004bed;stop-opacity:1" offset="0" id="stop3768-7" />
|
||||
<stop style="stop-color:#bbd7ff;stop-opacity:1" offset="1" id="stop3770-5" />
|
||||
</linearGradient>
|
||||
<linearGradient xlink:href="#linearGradient3828" id="linearGradient3834" x1="-2.2241571" y1="759.33002" x2="281.50192" y2="728.49023" gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient xlink:href="#linearGradient3828" id="linearGradient3840" gradientUnits="userSpaceOnUse" x1="-2.2241571" y1="759.33002" x2="281.50192" y2="728.49023" />
|
||||
<linearGradient xlink:href="#linearGradient3860" id="linearGradient3866" x1="35.238464" y1="33.039242" x2="27.980391" y2="20.136003" gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<metadata id="metadata2731">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>[DeepSOIC]</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:title>Part_Attachment</dc:title>
|
||||
<dc:date>2016-05-17</dc:date>
|
||||
<dc:relation>https://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier>FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Attachment.svg</dc:identifier>
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD LGPL2+</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title>[agryson] Alexander Gryson</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g id="layer1">
|
||||
<g id="g4289" transform="matrix(0.1621282,0,0,0.1621282,6.3605986,-66.108806)">
|
||||
<g transform="matrix(0,-1,-1,0,738.60196,738.60196)" id="g3888-9">
|
||||
<path transform="matrix(6.1679584,0,0,6.1679584,-39.231908,407.75637)" id="path3868-1" d="M 37,15 C 28.799646,14.910168 22.4325,19.708889 17,27" style="fill:none;stroke:#a40000;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3868-2-2" d="m 188.98255,500.27574 c -50.57944,-0.55408 -89.851731,29.04423 -123.359166,74.0155" style="fill:none;stroke:#ef2929;stroke-width:12.33591652;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
<g id="g3888" transform="translate(0,-24.671834)">
|
||||
<path transform="matrix(6.1679584,0,0,6.1679584,-39.231908,407.75637)" id="path3868" d="M 37,15 C 28.799646,14.910168 22.4325,19.708889 17,27" style="fill:none;stroke:#a40000;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3868-2" d="m 188.98255,500.27574 c -50.57944,-0.55408 -89.851731,29.04423 -123.359166,74.0155" style="fill:none;stroke:#ef2929;stroke-width:12.33591652;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
<path style="color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#0b1521;stroke-width:1.36476552;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path2998" d="m 40.88363,26.587624 a 8.8709764,8.8709764 0 1 1 -17.741953,0 8.8709764,8.8709764 0 1 1 17.741953,0 z" transform="matrix(9.0388539,0,0,9.0388539,-32.527599,266.12206)" />
|
||||
<g id="g3836" transform="matrix(0.72717141,0.71036381,-0.72717141,0.71036381,555.43637,22.856283)">
|
||||
<rect y="709.98633" x="-20.728033" height="61.679588" width="320.73383" id="rect3808" style="fill:#729fcf;stroke:#0b1521;stroke-width:12.13661671;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.5;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:20.4" />
|
||||
<rect y="722.32227" x="-8.3921156" height="37.007721" width="296.06201" id="rect3808-5" style="fill:url(#linearGradient3840);fill-opacity:1;stroke:#729fcf;stroke-width:12.13661671;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.5;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:20.4" />
|
||||
</g>
|
||||
<path style="color:#000000;fill:url(#linearGradient3866);fill-opacity:1;fill-rule:nonzero;stroke:#729fcf;stroke-width:1.61290479;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path2998-6" d="m 40.88363,26.587624 a 8.8709764,8.8709764 0 1 1 -17.741953,0 8.8709764,8.8709764 0 1 1 17.741953,0 z" transform="matrix(7.6482608,0,0,7.6482608,11.988976,303.09463)" />
|
||||
<g transform="matrix(0,-1,-1,0,787.94563,732.434)" id="g3888-9-7">
|
||||
<path transform="matrix(6.1679584,0,0,6.1679584,-39.231908,407.75637)" id="path3868-1-0" d="M 37,15 C 28.799646,14.910168 15.432499,17.708888 9.9999992,24.999999" style="fill:none;stroke:#a40000;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3868-2-2-9" d="M 188.98255,500.27574 C 138.40311,499.72166 55.955106,516.98405 22.447671,561.95532" style="fill:none;stroke:#ef2929;stroke-width:12.33591652;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
<g transform="matrix(-0.58735857,0.80932682,0.80932682,0.58735857,-215.63037,164.37488)" id="g3888-9-7-3">
|
||||
<path transform="matrix(6.1679584,0,0,6.1679584,-39.231908,407.75637)" id="path3868-1-0-6" d="M 31.459093,15.861765 C 23.258739,15.771933 15.432499,17.708888 9.9999992,24.999999" style="fill:none;stroke:#a40000;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3868-2-2-9-0" d="M 154.80647,505.59108 C 104.22703,505.037 55.955106,516.98405 22.447671,561.95532" style="fill:none;stroke:#ef2929;stroke-width:12.33591652;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.8 KiB |
140
web/public/icons/part/Part_BoxSelection.svg
Normal file
140
web/public/icons/part/Part_BoxSelection.svg
Normal file
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="64px" height="64px" id="svg3612" version="1.1">
|
||||
<defs id="defs3614">
|
||||
<marker orient="auto" refY="0.0" refX="0.0" id="Arrow1Lstart" style="overflow:visible">
|
||||
<path id="path3809" d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none" transform="scale(0.8) translate(12.5,0)" />
|
||||
</marker>
|
||||
<linearGradient id="linearGradient3144-6">
|
||||
<stop id="stop3146-9" style="stop-color:#ffffff;stop-opacity:1" offset="0" />
|
||||
<stop id="stop3148-2" style="stop-color:#ffffff;stop-opacity:0" offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3701">
|
||||
<stop id="stop3703" style="stop-color:#ffffff;stop-opacity:1" offset="0" />
|
||||
<stop id="stop3705" style="stop-color:#ffffff;stop-opacity:0" offset="1" />
|
||||
</linearGradient>
|
||||
<radialGradient xlink:href="#linearGradient3144-6" id="radialGradient3688" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,0.6985294,0,202.82863)" cx="225.26402" cy="672.79736" fx="225.26402" fy="672.79736" r="34.345188" />
|
||||
<linearGradient id="linearGradient3708">
|
||||
<stop id="stop3710" style="stop-color:#ffffff;stop-opacity:1" offset="0" />
|
||||
<stop id="stop3712" style="stop-color:#ffffff;stop-opacity:0" offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3864-0-0">
|
||||
<stop id="stop3866-5-7" offset="0" style="stop-color:#0619c0;stop-opacity:1" />
|
||||
<stop id="stop3868-7-6" offset="1" style="stop-color:#379cfb;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3377">
|
||||
<stop id="stop3379" offset="0" style="stop-color:#ffaa00;stop-opacity:1" />
|
||||
<stop id="stop3381" offset="1" style="stop-color:#faff2b;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3864-0">
|
||||
<stop id="stop3866-5" offset="0" style="stop-color:#0619c0;stop-opacity:1" />
|
||||
<stop id="stop3868-7" offset="1" style="stop-color:#379cfb;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient5048">
|
||||
<stop style="stop-color:black;stop-opacity:0" offset="0" id="stop5050" />
|
||||
<stop id="stop5056" offset="0.5" style="stop-color:black;stop-opacity:1" />
|
||||
<stop style="stop-color:black;stop-opacity:0" offset="1" id="stop5052" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3841-0-3">
|
||||
<stop id="stop3843-1-3" offset="0" style="stop-color:#0619c0;stop-opacity:1" />
|
||||
<stop id="stop3845-0-8" offset="1" style="stop-color:#379cfb;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<radialGradient gradientUnits="userSpaceOnUse" fy="114.5684" fx="20.892099" r="5.256" cy="114.5684" cx="20.892099" id="aigrd2">
|
||||
<stop id="stop15566" style="stop-color:#F0F0F0" offset="0" />
|
||||
<stop id="stop15568" style="stop-color:#9a9a9a;stop-opacity:1" offset="1.0000000" />
|
||||
</radialGradient>
|
||||
<radialGradient gradientUnits="userSpaceOnUse" fy="64.567902" fx="20.892099" r="5.257" cy="64.567902" cx="20.892099" id="aigrd3">
|
||||
<stop id="stop15573" style="stop-color:#F0F0F0" offset="0" />
|
||||
<stop id="stop15575" style="stop-color:#9a9a9a;stop-opacity:1" offset="1.0000000" />
|
||||
</radialGradient>
|
||||
<linearGradient id="linearGradient15662">
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="0.0000000" id="stop15664" />
|
||||
<stop style="stop-color:#f8f8f8;stop-opacity:1" offset="1.0000000" id="stop15666" />
|
||||
</linearGradient>
|
||||
<radialGradient r="86.70845" fy="35.736916" fx="33.966679" cy="35.736916" cx="33.966679" gradientTransform="matrix(0.96049297,0,0,1.041132,-52.144249,-702.33158)" gradientUnits="userSpaceOnUse" id="radialGradient4452" xlink:href="#linearGradient259" />
|
||||
<linearGradient id="linearGradient259">
|
||||
<stop style="stop-color:#fafafa;stop-opacity:1" offset="0.0000000" id="stop260" />
|
||||
<stop style="stop-color:#bbbbbb;stop-opacity:1" offset="1.0000000" id="stop261" />
|
||||
</linearGradient>
|
||||
<radialGradient r="37.751713" fy="3.7561285" fx="8.824419" cy="3.7561285" cx="8.824419" gradientTransform="matrix(0.96827297,0,0,1.032767,-48.790699,-701.68513)" gradientUnits="userSpaceOnUse" id="radialGradient4454" xlink:href="#linearGradient269" />
|
||||
<linearGradient id="linearGradient269">
|
||||
<stop style="stop-color:#a3a3a3;stop-opacity:1" offset="0.0000000" id="stop270" />
|
||||
<stop style="stop-color:#4c4c4c;stop-opacity:1" offset="1.0000000" id="stop271" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient4095">
|
||||
<stop style="stop-color:#005bff;stop-opacity:1" offset="0" id="stop4097" />
|
||||
<stop style="stop-color:#c1e3f7;stop-opacity:1" offset="1" id="stop4099" />
|
||||
</linearGradient>
|
||||
<linearGradient xlink:href="#linearGradient4247" id="linearGradient4253" x1="394.15784" y1="185.1304" x2="434.73947" y2="140.22731" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.94231826,0,0,0.94231826,23.727549,8.8262536)" />
|
||||
<linearGradient id="linearGradient4247">
|
||||
<stop style="stop-color:#2e8207;stop-opacity:1" offset="0" id="stop4249" />
|
||||
<stop style="stop-color:#52ff00;stop-opacity:1" offset="1" id="stop4251" />
|
||||
</linearGradient>
|
||||
<linearGradient y2="140.22731" x2="434.73947" y1="185.1304" x1="394.15784" gradientTransform="matrix(0.94231826,0,0,0.94231826,23.727549,8.8262536)" gradientUnits="userSpaceOnUse" id="linearGradient5087" xlink:href="#linearGradient4247" />
|
||||
</defs>
|
||||
<g id="layer1">
|
||||
<g id="g931" transform="translate(-5.0212139,-6)">
|
||||
<path id="path3953-1" d="m 20.999999,19 h 4" style="fill:none;stroke:#204a87;stroke-width:5.99999952;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3957-5" d="M 53.000001,19 C 57,19 57,19 57,19" style="fill:none;stroke:#204a87;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-1" d="m 20.999999,19 h 4" style="fill:none;stroke:#729fcf;stroke-width:1.99999988;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3957-5-8" d="M 53.000001,19 C 57,19 57,19 57,19" style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-5" d="m 37,19 h 4" style="fill:none;stroke:#204a87;stroke-width:5.99999952;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-1-7" d="m 37,19 h 4" style="fill:none;stroke:#729fcf;stroke-width:1.99999988;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-6" d="m 19,57 h 4" style="fill:none;stroke:#204a87;stroke-width:5.99999952;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3957-5-1" d="m 51.000002,57 c 3.999999,0 3.999999,0 3.999999,0" style="fill:none;stroke:#204a87;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-1-8" d="m 19,57 h 4" style="fill:none;stroke:#729fcf;stroke-width:1.99999988;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3957-5-8-9" d="m 51.000002,57 c 3.999999,0 3.999999,0 3.999999,0" style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-5-2" d="m 35.000001,57 h 4" style="fill:none;stroke:#204a87;stroke-width:5.99999952;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-1-7-7" d="m 35.000001,57 h 4" style="fill:none;stroke:#729fcf;stroke-width:1.99999988;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-9" d="M 17,26.999999 V 31" style="fill:none;stroke:#204a87;stroke-width:5.99999952;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-1-4" d="M 17,26.999999 V 31" style="fill:none;stroke:#729fcf;stroke-width:1.99999988;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-5-1" d="M 17,43.000001 V 47" style="fill:none;stroke:#204a87;stroke-width:5.99999952;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-1-7-2" d="M 17,43.000001 V 47" style="fill:none;stroke:#729fcf;stroke-width:1.99999988;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-9-3" d="M 57,28.999999 V 33" style="fill:none;stroke:#204a87;stroke-width:5.99999952;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-1-4-3" d="M 57,28.999999 V 33" style="fill:none;stroke:#729fcf;stroke-width:1.99999988;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-5-1-4" d="M 57,45.000001 V 49" style="fill:none;stroke:#204a87;stroke-width:5.99999952;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3953-1-1-7-2-1" d="M 57,45.000001 V 49" style="fill:none;stroke:#729fcf;stroke-width:1.99999988;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<metadata id="metadata5469">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<cc:license rdf:resource="" />
|
||||
<dc:date>Mon Oct 10 13:44:52 2011 +0000</dc:date>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>[wmayer]</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD LGPL2+</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier>FreeCAD/src/Mod/Draft/Resources/icons/Draft_SelectGroup.svg</dc:identifier>
|
||||
<dc:relation>https://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title>[agryson] Alexander Gryson</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>hierarchy</rdf:li>
|
||||
<rdf:li>group</rdf:li>
|
||||
<rdf:li>selection</rdf:li>
|
||||
<rdf:li>tree</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<dc:description>A hierarchical tree structure with two blue child elements of a white parent element, both of which are surrounded by the same dotted box</dc:description>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
281
web/public/icons/part/Part_PointsFromMesh.svg
Normal file
281
web/public/icons/part/Part_PointsFromMesh.svg
Normal file
@@ -0,0 +1,281 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
id="svg2985"
|
||||
height="64px"
|
||||
width="64px">
|
||||
<title
|
||||
id="title889">Part_PointsFromMesh</title>
|
||||
<defs
|
||||
id="defs2987">
|
||||
<linearGradient
|
||||
id="linearGradient4387">
|
||||
<stop
|
||||
id="stop4389"
|
||||
offset="0"
|
||||
style="stop-color:#71b2f8;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop4391"
|
||||
offset="1"
|
||||
style="stop-color:#002795;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6321">
|
||||
<stop
|
||||
id="stop6323"
|
||||
offset="0"
|
||||
style="stop-color:#71b2f8;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop6325"
|
||||
offset="1"
|
||||
style="stop-color:#002795;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
gradientTransform="translate(-0.23443224,0.23443198)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
r="19.467436"
|
||||
fy="28.869568"
|
||||
fx="45.883327"
|
||||
cy="28.869568"
|
||||
cx="45.883327"
|
||||
id="radialGradient3692"
|
||||
xlink:href="#linearGradient3377" />
|
||||
<linearGradient
|
||||
id="linearGradient3377">
|
||||
<stop
|
||||
style="stop-color:#faff2b;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3379" />
|
||||
<stop
|
||||
style="stop-color:#ffaa00;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3381" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3377-3">
|
||||
<stop
|
||||
style="stop-color:#faff2b;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3379-8" />
|
||||
<stop
|
||||
style="stop-color:#ffaa00;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3381-3" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
r="19.467436"
|
||||
fy="28.869568"
|
||||
fx="45.883327"
|
||||
cy="28.869568"
|
||||
cx="45.883327"
|
||||
gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient6412"
|
||||
xlink:href="#linearGradient3377-3" />
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient3036"
|
||||
id="linearGradient3049"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="rotate(33.834399,20.280412,56.143946)"
|
||||
x1="14.824193"
|
||||
y1="50.468616"
|
||||
x2="20.93985"
|
||||
y2="56.000233" />
|
||||
<linearGradient
|
||||
id="linearGradient3036">
|
||||
<stop
|
||||
style="stop-color:#ef2929;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3038" />
|
||||
<stop
|
||||
style="stop-color:#a40000;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop3040" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata2990">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>Part_PointsFromMesh</dc:title>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>[bitacovir]</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:title>Part_Shape_from_Mesh</dc:title>
|
||||
<dc:date>2021-03-21</dc:date>
|
||||
<dc:relation>https://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier />
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD LGPL2+</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title>Based on Alexander Gryson's work</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
style="stroke-width:3.5;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="g3042"
|
||||
transform="matrix(0.67067175,0,0,0.55048347,-120.90697,-52.046252)">
|
||||
<path
|
||||
id="path3150-7-4"
|
||||
d="m 226.49973,114.52887 v 36.3317 l -17.89251,14.53268 v -36.33171 z"
|
||||
style="fill:#4e9a06;stroke:none" />
|
||||
<path
|
||||
id="path3930-0"
|
||||
d="M 208.60722,161.76008 226.49973,147.2274"
|
||||
style="fill:none;stroke:#73d216;stroke-width:3.29157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3932-9"
|
||||
d="m 223.51764,154.49374 v -36.3317"
|
||||
style="fill:none;stroke:#73d216;stroke-width:3.29157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3934-4"
|
||||
d="m 226.49973,118.16204 -17.89251,14.53267"
|
||||
style="fill:none;stroke:#73d216;stroke-width:3.29157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3936-8"
|
||||
d="m 211.5893,125.42838 v 36.3317"
|
||||
style="fill:none;stroke:#73d216;stroke-width:3.29157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3152-1-8"
|
||||
d="m 208.60722,165.39325 -23.85668,-14.53268 v -36.3317 l 23.85668,14.53267 z"
|
||||
style="fill:#73d216;stroke:none" />
|
||||
<path
|
||||
id="path3938-2"
|
||||
d="m 205.62513,129.06154 v 32.69854"
|
||||
style="fill:none;stroke:#8ae234;stroke-width:3.29157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3940-4"
|
||||
d="m 184.75054,147.2274 23.85668,14.53268"
|
||||
style="fill:none;stroke:#8ae234;stroke-width:3.29157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3942-5"
|
||||
d="M 187.73263,150.86057 V 118.16204"
|
||||
style="fill:none;stroke:#8ae234;stroke-width:3.29157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3150-5"
|
||||
d="m 226.49973,114.52887 v 36.3317 l -17.89251,14.53268 v -36.33171 z"
|
||||
style="fill:none;stroke:#172a04;stroke-width:3.29157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3944-1"
|
||||
d="M 208.60722,132.69471 184.75054,118.16204"
|
||||
style="fill:none;stroke:#8ae234;stroke-width:3.29157;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3152-7"
|
||||
d="m 208.60722,165.39325 -23.85668,-14.53268 v -36.3317 l 23.85668,14.53267 z"
|
||||
style="fill:none;stroke:#172a04;stroke-width:3.29157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3156-1"
|
||||
d="m 226.49973,114.52887 -17.89251,14.53267 -23.85668,-14.53267 17.89251,-14.532675 23.85668,14.532675"
|
||||
style="fill:#8ae234;stroke:#172a04;stroke-width:3.29157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3928-5"
|
||||
d="m 184.75054,114.52887 h 41.74919"
|
||||
style="fill:none;stroke:#172a04;stroke-width:3.29157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3928-5-3"
|
||||
d="m 184.75054,150.86057 23.85668,-21.79902"
|
||||
style="fill:none;stroke:#172a04;stroke-width:3.29157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path3928-5-6"
|
||||
d="m 208.60722,165.39325 17.89251,-50.86438"
|
||||
style="fill:none;stroke:#172a04;stroke-width:3.29157;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
<path
|
||||
style="display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient3049);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.2;marker:none;enable-background:accumulate"
|
||||
d="m 14.577858,56.963669 14.422159,0.03633 -5.51345,-13.326736 -2.227176,3.322601 -9.967806,-6.681533 -4.4543552,6.645202 9.9678062,6.681534 z"
|
||||
id="rect3165" />
|
||||
<path
|
||||
style="fill:none;stroke:#ef2929;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="m 18.466336,54.754657 1.113588,-1.6613 -9.9678041,-6.681534 3.3407661,-4.983901"
|
||||
id="path4087" />
|
||||
<path
|
||||
style="fill:none;stroke:#ef2929;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 15.768199,54.861321 12.089968,0.0052"
|
||||
id="path4089" />
|
||||
<path
|
||||
style="fill:none;stroke:#ef2929;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 17.918624,51.979768 0.547712,2.774889"
|
||||
id="path4091" />
|
||||
<path
|
||||
style="fill:none;stroke:#ef2929;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 27.041007,56.356585 22.755847,45.99721"
|
||||
id="path4042" />
|
||||
<path
|
||||
style="display:inline;overflow:visible;visibility:visible;fill:none;stroke:#280000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
|
||||
d="m 15.058839,56.727355 14.422158,0.03633 -5.51345,-13.326735 -2.227175,3.322601 -9.967805,-6.681534 -4.4543552,6.645203 9.9678052,6.681533 z"
|
||||
id="rect3165-1" />
|
||||
<ellipse
|
||||
id="path3896-0-1-8"
|
||||
style="display:inline;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#2e3436;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
cx="44.866348"
|
||||
cy="26.883219"
|
||||
rx="1.9999998"
|
||||
ry="2.0000005" />
|
||||
<ellipse
|
||||
ry="2.0000005"
|
||||
rx="1.9999998"
|
||||
cy="37.621815"
|
||||
cx="58.983444"
|
||||
style="display:inline;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#2e3436;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3896-0-1-8-6" />
|
||||
<ellipse
|
||||
ry="2.0000005"
|
||||
rx="1.9999998"
|
||||
cy="44.767174"
|
||||
cx="48.710922"
|
||||
style="display:inline;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#2e3436;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3896-0-1-8-4" />
|
||||
<ellipse
|
||||
ry="2.0000005"
|
||||
rx="1.9999998"
|
||||
cy="51.824265"
|
||||
cx="34.651279"
|
||||
style="display:inline;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#2e3436;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3896-0-1-8-9" />
|
||||
<ellipse
|
||||
ry="2.0000005"
|
||||
rx="1.9999998"
|
||||
cy="35.536747"
|
||||
cx="34.120564"
|
||||
style="display:inline;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#2e3436;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3896-0-1-8-92" />
|
||||
<ellipse
|
||||
ry="2.0000005"
|
||||
rx="1.9999998"
|
||||
cy="58.979542"
|
||||
cx="49.133045"
|
||||
style="display:inline;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#2e3436;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3896-0-1-8-41" />
|
||||
<ellipse
|
||||
ry="2.0000005"
|
||||
rx="1.9999998"
|
||||
cy="52.966244"
|
||||
cx="58.995705"
|
||||
style="display:inline;fill:#d3d7cf;fill-opacity:1;fill-rule:nonzero;stroke:#2e3436;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3896-0-1-8-3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
107
web/public/icons/part/Part_Shape_from_Mesh.svg
Normal file
107
web/public/icons/part/Part_Shape_from_Mesh.svg
Normal file
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="64px" height="64px" id="svg2985" version="1.1">
|
||||
<defs id="defs2987">
|
||||
<linearGradient id="linearGradient4387">
|
||||
<stop style="stop-color:#71b2f8;stop-opacity:1" offset="0" id="stop4389" />
|
||||
<stop style="stop-color:#002795;stop-opacity:1" offset="1" id="stop4391" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient6321">
|
||||
<stop style="stop-color:#71b2f8;stop-opacity:1" offset="0" id="stop6323" />
|
||||
<stop style="stop-color:#002795;stop-opacity:1" offset="1" id="stop6325" />
|
||||
</linearGradient>
|
||||
<radialGradient xlink:href="#linearGradient3377" id="radialGradient3692" cx="45.883327" cy="28.869568" fx="45.883327" fy="28.869568" r="19.467436" gradientUnits="userSpaceOnUse" gradientTransform="translate(-0.23443224,0.23443198)" />
|
||||
<linearGradient id="linearGradient3377">
|
||||
<stop id="stop3379" offset="0" style="stop-color:#faff2b;stop-opacity:1" />
|
||||
<stop id="stop3381" offset="1" style="stop-color:#ffaa00;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3377-3">
|
||||
<stop id="stop3379-8" offset="0" style="stop-color:#faff2b;stop-opacity:1" />
|
||||
<stop id="stop3381-3" offset="1" style="stop-color:#ffaa00;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<radialGradient xlink:href="#linearGradient3377-3" id="radialGradient6412" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)" cx="45.883327" cy="28.869568" fx="45.883327" fy="28.869568" r="19.467436" />
|
||||
<linearGradient y2="56.000233" x2="20.93985" y1="50.468616" x1="14.824193" gradientTransform="matrix(0.83065033,0.55679442,-0.55679442,0.83065033,34.695117,-1.7840615)" gradientUnits="userSpaceOnUse" id="linearGradient3049" xlink:href="#linearGradient3036" />
|
||||
<linearGradient id="linearGradient3036">
|
||||
<stop id="stop3038" offset="0" style="stop-color:#ef2929;stop-opacity:1" />
|
||||
<stop id="stop3040" offset="1" style="stop-color:#a40000;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<metadata id="metadata2990">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>[jmaustpc]</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:title>Part_Shape_from_Mesh</dc:title>
|
||||
<dc:date>2013-12-13</dc:date>
|
||||
<dc:relation>https://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier>FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Shape_from_Mesh.svg</dc:identifier>
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD LGPL2+</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title>[agryson] Alexander Gryson</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g id="layer1">
|
||||
<g id="g3038" transform="matrix(1,0,0,0.85817381,37.193787,2.8828554)">
|
||||
<g style="stroke-width:3.5;stroke-miterlimit:4;stroke-dasharray:none" transform="matrix(0.67067175,0,0,0.64145918,-128.10074,-38.37114)" id="g3618-3">
|
||||
<g id="g3060" transform="translate(44.731271,39.96487)">
|
||||
<path id="path3150-7" d="m 181.76846,74.564006 0,36.331694 -17.89251,14.53268 0,-36.3317 z" style="fill:#204a87;stroke:none" />
|
||||
<path id="path3930" d="m 163.87595,121.79521 17.89251,-14.53268" style="fill:none;stroke:#3465a4;stroke-width:3.29156828;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3932" d="m 178.78637,114.52887 0,-36.331697" style="fill:none;stroke:#3465a4;stroke-width:3.29156828;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3934" d="m 181.76846,78.197173 -17.89251,14.53268" style="fill:none;stroke:#3465a4;stroke-width:3.29156828;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3936" d="m 166.85803,85.463513 0,36.331697" style="fill:none;stroke:#3465a4;stroke-width:3.29156828;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3152-1" d="m 163.87595,125.42838 -23.85668,-14.53268 0,-36.331697 23.85668,14.532677 z" style="fill:#3465a4;stroke:none" />
|
||||
<path id="path3938" d="m 160.89386,89.096683 0,32.698527" style="fill:none;stroke:#729fcf;stroke-width:3.29156828;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3940" d="m 140.01927,107.26253 23.85668,14.53268" style="fill:none;stroke:#729fcf;stroke-width:3.29156828;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3942" d="m 143.00136,110.8957 0,-32.698527" style="fill:none;stroke:#729fcf;stroke-width:3.29156828;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3150" d="m 181.76846,74.564003 0,36.331697 -17.89251,14.53268 0,-36.331697 z" style="fill:none;stroke:#0b1521;stroke-width:3.29156828;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path id="path3944" d="M 163.87595,92.729853 140.01927,78.197173" style="fill:none;stroke:#729fcf;stroke-width:3.29156828;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path id="path3152" d="m 163.87595,125.42838 -23.85668,-14.53268 0,-36.331697 23.85668,14.53268 z" style="fill:none;stroke:#0b1521;stroke-width:3.29156828;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
<path id="path3156" d="m 181.76846,74.564003 -17.89251,14.53268 -23.85668,-14.53268 17.89251,-14.53268 23.85668,14.53268" style="fill:#729fcf;stroke:#0b1521;stroke-width:3.29156828;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
|
||||
</g>
|
||||
<g id="g3042" transform="translate(-44.731295,-39.96487)">
|
||||
<path id="path3150-7-4" d="m 226.49973,114.52887 0,36.3317 -17.89251,14.53268 0,-36.33171 z" style="fill:#4e9a06;stroke:none" />
|
||||
<path id="path3930-0" d="M 208.60722,161.76008 226.49973,147.2274" style="fill:none;stroke:#73d216;stroke-width:3.29156804;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3932-9" d="m 223.51764,154.49374 0,-36.3317" style="fill:none;stroke:#73d216;stroke-width:3.29156804;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3934-4" d="m 226.49973,118.16204 -17.89251,14.53267" style="fill:none;stroke:#73d216;stroke-width:3.29156804;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3936-8" d="m 211.5893,125.42838 0,36.3317" style="fill:none;stroke:#73d216;stroke-width:3.29156804;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3152-1-8" d="m 208.60722,165.39325 -23.85668,-14.53268 0,-36.3317 23.85668,14.53267 z" style="fill:#73d216;stroke:none" />
|
||||
<path id="path3938-2" d="m 205.62513,129.06154 0,32.69854" style="fill:none;stroke:#8ae234;stroke-width:3.29156804;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3940-4" d="m 184.75054,147.2274 23.85668,14.53268" style="fill:none;stroke:#8ae234;stroke-width:3.29156804;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3942-5" d="m 187.73263,150.86057 0,-32.69853" style="fill:none;stroke:#8ae234;stroke-width:3.29156804;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3150-5" d="m 226.49973,114.52887 0,36.3317 -17.89251,14.53268 0,-36.33171 z" style="fill:none;stroke:#172a04;stroke-width:3.29156804;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3944-1" d="M 208.60722,132.69471 184.75054,118.16204" style="fill:none;stroke:#8ae234;stroke-width:3.29156804;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3152-7" d="m 208.60722,165.39325 -23.85668,-14.53268 0,-36.3317 23.85668,14.53267 z" style="fill:none;stroke:#172a04;stroke-width:3.29156804;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3156-1" d="m 226.49973,114.52887 -17.89251,14.53267 -23.85668,-14.53267 17.89251,-14.532675 23.85668,14.532675" style="fill:#8ae234;stroke:#172a04;stroke-width:3.29156804;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3928-5" d="m 184.75054,114.52887 41.74919,0" style="fill:none;stroke:#172a04;stroke-width:3.29156804;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3928-5-3" d="m 184.75054,150.86057 23.85668,-21.79902" style="fill:none;stroke:#172a04;stroke-width:3.29156804;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
<path id="path3928-5-6" d="m 208.60722,165.39325 17.89251,-50.86438" style="fill:none;stroke:#172a04;stroke-width:3.29156804;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path style="fill:url(#linearGradient3049);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.20000005;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" d="m 14.577858,56.963669 14.422159,0.03633 -5.51345,-13.326736 -2.227176,3.322601 -9.967806,-6.681533 -4.4543552,6.645202 9.9678062,6.681534 z" id="rect3165" />
|
||||
<path style="fill:none;stroke:#ef2929;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" d="m 18.466336,54.754657 1.113588,-1.6613 -9.9678041,-6.681534 3.3407661,-4.983901" id="path4087" />
|
||||
<path style="fill:none;stroke:#ef2929;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 15.768199,54.861321 12.089968,0.0052" id="path4089" />
|
||||
<path style="fill:none;stroke:#ef2929;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 17.918624,51.979768 0.547712,2.774889" id="path4091" />
|
||||
<path style="fill:none;stroke:#ef2929;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 27.041007,56.356585 22.755847,45.99721" id="path4042" />
|
||||
<path style="fill:none;stroke:#280000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" d="m 15.058839,56.727355 14.422158,0.03633 -5.51345,-13.326735 -2.227175,3.322601 -9.967805,-6.681534 -4.4543552,6.645203 9.9678052,6.681533 z" id="rect3165-1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
81
web/public/icons/part/Sketcher_NewSketch.svg
Normal file
81
web/public/icons/part/Sketcher_NewSketch.svg
Normal file
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg width="64px" height="64px" id="svg5821" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs id="defs5823">
|
||||
<linearGradient id="linearGradient3776">
|
||||
<stop style="stop-color:#cc0000;stop-opacity:1" offset="0" id="stop3778" />
|
||||
<stop style="stop-color:#ef2929;stop-opacity:1" offset="1" id="stop3780" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3768">
|
||||
<stop style="stop-color:#cc0000;stop-opacity:1" offset="0" id="stop3770" />
|
||||
<stop style="stop-color:#ef2929;stop-opacity:1" offset="1" id="stop3772" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient6349">
|
||||
<stop style="stop-color:#000000;stop-opacity:1" offset="0" id="stop6351" />
|
||||
<stop style="stop-color:#000000;stop-opacity:0" offset="1" id="stop6353" />
|
||||
</linearGradient>
|
||||
<linearGradient id="linearGradient3377">
|
||||
<stop style="stop-color:#0019a3;stop-opacity:1" offset="0" id="stop3379" />
|
||||
<stop style="stop-color:#0069ff;stop-opacity:1" offset="1" id="stop3381" />
|
||||
</linearGradient>
|
||||
<linearGradient xlink:href="#linearGradient3377" id="linearGradient3383" x1="901.1875" y1="1190.875" x2="1267.9062" y2="1190.875" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1,0,0,1,2199.356,0)" />
|
||||
<radialGradient xlink:href="#linearGradient6349" id="radialGradient6355" cx="1103.6399" cy="1424.4465" fx="1103.6399" fy="1424.4465" r="194.40614" gradientTransform="matrix(-1.4307499,-1.3605156e-7,-1.202713e-8,0.1264801,2674.7488,1244.2826)" gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient xlink:href="#linearGradient3768" id="linearGradient3774" x1="1178.153" y1="1453.8708" x2="1105.0463" y2="1059.0945" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.70270076,0,0,0.78787889,315.88365,269.84952)" />
|
||||
<linearGradient xlink:href="#linearGradient3776" id="linearGradient3782" x1="33.052631" y1="73.676765" x2="23.483253" y2="19.131313" gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient gradientTransform="matrix(7.3106737,0,0,7.3106737,841.86203,1044.4732)" xlink:href="#linearGradient3874" id="linearGradient3880" x1="56" y1="47" x2="53" y2="39" gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient id="linearGradient3874">
|
||||
<stop style="stop-color:#555753;stop-opacity:1" offset="0" id="stop3876" />
|
||||
<stop style="stop-color:#babdb6;stop-opacity:1" offset="1" id="stop3878" />
|
||||
</linearGradient>
|
||||
<linearGradient xlink:href="#linearGradient3866" id="linearGradient3872" x1="35" y1="53" x2="24" y2="9" gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient id="linearGradient3866">
|
||||
<stop style="stop-color:#d3d7cf;stop-opacity:1" offset="0" id="stop3868" />
|
||||
<stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop3870" />
|
||||
</linearGradient>
|
||||
<linearGradient gradientTransform="matrix(0,7.3106737,7.3106737,0,900.34735,985.98777)" y2="20.000008" x2="6.000001" y1="38.000008" x1="58" gradientUnits="userSpaceOnUse" id="linearGradient3127" xlink:href="#linearGradient3866" />
|
||||
</defs>
|
||||
<metadata id="metadata5826">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>[wmayer]</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:date>2011-10-10</dc:date>
|
||||
<dc:relation>https://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier>FreeCAD/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_NewSketch.svg</dc:identifier>
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD LGPL2+</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title>[agryson] Alexander Gryson</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g id="layer1">
|
||||
<g id="g3360" transform="matrix(0.1367863,0,0,0.1367863,-119.15519,-134.86962)">
|
||||
<path d="m 922.27941,1007.9198 365.53359,0 0,350.9123 -73.1067,73.1069 -292.42689,0 z" id="rect3005" style="color:#000000;fill:url(#linearGradient3127);fill-opacity:1;fill-rule:evenodd;stroke:#3f3f3f;stroke-width:14.62134743;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<path style="fill:none;stroke:#ffffff;stroke-width:14.62134743;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 1273.1917,1022.5412 -336.29092,0 0,394.7763 271.22592,0 65.065,-66.5271 z" id="path3096" />
|
||||
<path d="m 1287.8132,1358.8322 c 0,-7.3107 -29.2428,-29.2427 -58.4855,-29.2427 0,0 14.6213,73.1067 -14.6214,102.3494 z" id="path3778" style="color:#000000;fill:url(#linearGradient3880);fill-opacity:1;fill-rule:evenodd;stroke:#3f3f3f;stroke-width:14.26466179;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<path style="fill:none;stroke:#babdb6;stroke-width:14.62134743;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 1229.9528,1396.295 48.6777,-47.1803" id="path3125" />
|
||||
<path d="m 1287.8132,1358.8322 c 0,-7.3107 -29.2428,-29.2427 -58.4855,-29.2427 0,0 14.6213,73.1067 -14.6214,102.3494 z" id="path3778-3" style="color:#000000;fill:none;stroke:#3f3f3f;stroke-width:14.26466179;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect style="color:#000000;fill:none;stroke:url(#linearGradient3774);stroke-width:29.24269676;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="rect3860-3" width="190.077" height="190.07753" x="958.83331" y="1190.6866" />
|
||||
<path style="color:#000000;fill:none;stroke:url(#linearGradient3782);stroke-width:5.53487444;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path3862-6" d="m 48.363636,33.272728 c 0,10.041541 -8.140277,18.181818 -18.181818,18.181818 C 20.140277,51.454546 12,43.314269 12,33.272728 12,23.231187 20.140277,15.09091 30.181818,15.09091 c 10.041541,0 18.181818,8.140277 18.181818,18.181818 z" transform="matrix(5.4281292,0,0,5.1424383,996.04635,999.19155)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
204
web/src/part-workbench-ui.tsx
Normal file
204
web/src/part-workbench-ui.tsx
Normal file
@@ -0,0 +1,204 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { PART_DESKTOP_MENUS } from "./part-desktop-adapter.js";
|
||||
import { ToolbarGroup } from "./persistent-toolbar-workspace.js";
|
||||
|
||||
export interface PartUiAction {
|
||||
command: string; label: string; task: string; icon?: string;
|
||||
primitive?: "Cylinder" | "Sphere" | "Cone" | "Torus";
|
||||
boolean?: "Cut" | "Fuse" | "Common";
|
||||
join?: "connect" | "embed" | "cutout";
|
||||
split?: "fragments" | "slice-apart" | "slice" | "xor";
|
||||
}
|
||||
// Interaction mapping only. Geometry and document mutations stay in the existing adapters.
|
||||
// Menu families come from the existing Part desktop adapter. Native command labels/icons
|
||||
// correspond to Part/Gui/Command.cpp, CommandParametric.cpp and Resources/Part.qrc.
|
||||
const rows: Array<[string, string, string, string?, Partial<PartUiAction>?]> = [
|
||||
["Part_Box", "Cube", "box", "Part_Box_Parametric"],
|
||||
["Part_Cylinder", "Cylinder", "primitive", "Part_Cylinder_Parametric", { primitive: "Cylinder" }],
|
||||
["Part_Sphere", "Sphere", "primitive", "Part_Sphere_Parametric", { primitive: "Sphere" }],
|
||||
["Part_Cone", "Cone", "primitive", "Part_Cone_Parametric", { primitive: "Cone" }],
|
||||
["Part_Torus", "Torus", "primitive", "Part_Torus_Parametric", { primitive: "Torus" }],
|
||||
["Part_Tube", "Tube", "tube", "Part_Tube"],
|
||||
["Part_Primitives", "Primitive", "primitives", "Part_Primitives"],
|
||||
["Part_ToleranceSet", "Set Tolerance", "tolerance", "Part_ToleranceSet"],
|
||||
["Part_Builder", "Shape Builder", "shape-builder", "Part_Shapebuilder"],
|
||||
["Part_SimpleCopy", "Simple Copy", "simple-copy", "Part_3D_object"],
|
||||
["Part_TransformedCopy", "Transformed Copy", "transformed-copy", "Part_Transformed_Copy"],
|
||||
["Part_ElementCopy", "Element Copy", "element-copy", "Part_Element_Copy"],
|
||||
["Part_RefineShape", "Refine Shape", "refine-shape", "Part_Refine_Shape"],
|
||||
["Part_Boolean", "Boolean Operation", "boolean", "Part_Booleans"],
|
||||
["Part_Cut", "Cut", "boolean", "Part_Cut", { boolean: "Cut" }],
|
||||
["Part_Fuse", "Union", "boolean", "Part_Fuse", { boolean: "Fuse" }],
|
||||
["Part_Common", "Intersection", "boolean", "Part_Common", { boolean: "Common" }],
|
||||
["Part_JoinConnect", "Connect Shapes", "join", "Part_JoinConnect", { join: "connect" }],
|
||||
["Part_JoinEmbed", "Embed Shapes", "join", "Part_JoinEmbed", { join: "embed" }],
|
||||
["Part_JoinCutout", "Cutout for Object", "join", "Part_JoinCutout", { join: "cutout" }],
|
||||
["Part_BooleanFragments", "Boolean Fragments", "split", "Part_BooleanFragments", { split: "fragments" }],
|
||||
["Part_SliceApart", "Slice Apart", "split", "Part_SliceApart", { split: "slice-apart" }],
|
||||
["Part_Slice", "Slice to Compound", "split", "Part_Slice", { split: "slice" }],
|
||||
["Part_XOR", "Boolean XOR", "split", "Part_XOR", { split: "xor" }],
|
||||
["Part_Compound", "Compound", "compound", "Part_Compound"],
|
||||
["Part_ExplodeCompound", "Explode Compound", "explode-compound", "Part_ExplodeCompound"],
|
||||
["Part_CompoundFilter", "Compound Filter", "compound-filter", "Part_CompoundFilter"],
|
||||
["Part_Extrude", "Extrude", "extrude", "Part_Extrude"],
|
||||
["Part_Revolve", "Revolve", "revolve", "Part_Revolve"],
|
||||
["Part_Mirror", "Mirror", "mirror", "Part_Mirror"],
|
||||
["Part_Scale", "Scale", "scale", "Part_Scale"],
|
||||
["Part_Fillet", "Fillet", "fillet", "Part_Fillet"],
|
||||
["Part_Chamfer", "Chamfer", "chamfer", "Part_Chamfer"],
|
||||
["Part_MakeFace", "Face From Wires", "make-face", "Part_MakeFace"],
|
||||
["Part_RuledSurface", "Ruled Surface", "ruled-surface", "Part_RuledSurface"],
|
||||
["Part_Loft", "Loft", "loft", "Part_Loft"],
|
||||
["Part_Sweep", "Sweep", "sweep", "Part_Sweep"],
|
||||
["Part_Section", "Section", "section", "Part_Section"],
|
||||
["Part_CrossSections", "Cross-Sections", "cross-sections", "Part_CrossSections"],
|
||||
["Part_Offset", "3D Offset", "offset", "Part_Offset"],
|
||||
["Part_Offset2D", "2D Offset", "offset2d", "Part_Offset2D"],
|
||||
["Part_Thickness", "Thickness", "thickness", "Part_Thickness"],
|
||||
["Part_ProjectionOnSurface", "Project on Surface", "projection-on-surface", "Part_ProjectionOnSurface"],
|
||||
["Part_SectionCut", "Persistent Section Cut", "section-cut", "Part_SectionCut"],
|
||||
["Part_CheckGeometry", "Check Geometry", "check-geometry", "Part_CheckGeometry"],
|
||||
["Part_Defeaturing", "Defeaturing", "defeaturing", "Part_Defeaturing"],
|
||||
["Part_MakeSolid", "Convert to Solid", "make-solid", "Part_MakeSolid"],
|
||||
["Part_ReverseShape", "Reverse Shapes", "reverse-shapes", "Part_Reverse_Shape"],
|
||||
["Part_ColorPerFace", "Appearance per Face", "face-colors", "Part_ColorFace"],
|
||||
["Part_BoxSelection", "Box Selection", "box-selection", "Part_BoxSelection"],
|
||||
["Part_ShapeFromMesh", "Shape From Mesh", "shape-from-mesh", "Part_Shape_from_Mesh"],
|
||||
["Part_PointsFromMesh", "Points From Shape", "points-from-mesh", "Part_PointsFromMesh"],
|
||||
["Part_EditAttachment", "Attachment", "edit-attachment", "Part_Attachment"],
|
||||
["Sketcher_NewSketch", "New Sketch", "sketcher-new-sketch", "Sketcher_NewSketch"],
|
||||
["Materials_InspectAppearance", "Inspect Appearance", "inspect-appearance"],
|
||||
["Materials_InspectMaterial", "Inspect Material", "inspect-material"],
|
||||
];
|
||||
export const partUiActions = Object.fromEntries(rows.map(([command, label, task, icon, options]) =>
|
||||
[command, { command, label, task, icon, ...options }])) as Record<string, PartUiAction>;
|
||||
const family = (name: string) => (PART_DESKTOP_MENUS[name] ?? []).filter(command => partUiActions[command]);
|
||||
const tools = ["Part_Extrude", "Part_Revolve", "Part_Mirror", "Part_Scale", "Part_Fillet", "Part_Chamfer", "Part_MakeFace", "Part_RuledSurface", "Part_Loft", "Part_Sweep", "Part_Section", "Part_CrossSections"];
|
||||
export const partUiCss = `
|
||||
.part-menu{position:relative}.part-menu>summary{list-style:none}.part-menu>summary::-webkit-details-marker{display:none}
|
||||
.part-command-menu{position:absolute;left:0;top:100%;z-index:50;min-width:240px;padding:3px;background:#17212b;color:#e7edf3;border:1px solid #435464;box-shadow:0 4px 12px #0006}
|
||||
.part-command-menu .part-submenu{position:relative}.part-command-menu .part-submenu>.part-command-menu{left:100%;top:0}
|
||||
.part-command-menu button,.part-command-menu summary{font:inherit;font-size:13px;display:flex;align-items:center;gap:8px;width:100%;padding:4px 8px;border:0;border-radius:0;background:transparent;color:inherit;white-space:nowrap;text-align:left;cursor:default}
|
||||
.part-command-menu summary{list-style:none}.part-command-menu summary::after{content:'▸';margin-left:auto}
|
||||
.part-command-menu button:hover,.part-command-menu button:focus-visible,.part-command-menu summary:hover,.part-command-menu summary:focus-visible{background:#264b56;outline:1px solid #66c7d5;outline-offset:-1px}
|
||||
.part-command-menu button:disabled{opacity:.45}.part-command-menu hr{border:0;border-top:1px solid #435464;margin:3px 2px}
|
||||
.part-command-menu img,.part-command-icon{width:20px;height:20px;object-fit:contain;flex-shrink:0}.part-command-menu summary{padding-left:36px}.part-tool-button{display:inline-flex;align-items:center;justify-content:center;background:transparent;border:1px solid transparent;border-radius:2px;color:inherit;padding:3px;min-width:30px;height:30px;cursor:pointer}
|
||||
.part-tool-button img{width:24px;height:24px}.part-tool-button:hover,.part-tool-button:focus-visible{border-color:#66c7d5;background:#264b56}.part-tool-button[aria-pressed=true]{border-color:#66c7d5}.part-tool-button:disabled{opacity:.4;cursor:default}
|
||||
.part-tool-group{position:relative}.part-tool-group>summary{list-style:none;font-size:12px;padding:4px;cursor:pointer}.part-tool-group>summary::after{content:' ▾'}.part-tool-group>.part-command-menu{top:100%}
|
||||
@media(max-width:650px){.part-command-menu .part-submenu>.part-command-menu{position:static;box-shadow:none;border:0;padding-left:14px}.part-menu>.part-command-menu{max-height:70vh;overflow:auto}}
|
||||
`;
|
||||
type Props = { disabled(action: PartUiAction): boolean; activate(action: PartUiAction): void; currentTask: string };
|
||||
function closeMenu(node: HTMLElement, focus = false) {
|
||||
const root = node.closest<HTMLDetailsElement>('.part-menu, .part-tool-group, .application-menus > details');
|
||||
root?.querySelectorAll<HTMLDetailsElement>('details[open]').forEach(item => { item.open = false; });
|
||||
if (root) { root.open = false; if (focus) root.querySelector<HTMLElement>('summary')?.focus(); }
|
||||
}
|
||||
function menuItems(menu: HTMLElement) {
|
||||
return [...menu.querySelectorAll<HTMLElement>('button:not(:disabled), summary')]
|
||||
.filter(item => item.closest('.part-command-menu') === menu);
|
||||
}
|
||||
function openGroup(group: HTMLDetailsElement, focus: 'first' | 'last' | false = false) {
|
||||
const siblings = group.parentElement?.children ?? [];
|
||||
for (const sibling of siblings) {
|
||||
if (sibling !== group && sibling instanceof HTMLDetailsElement) {
|
||||
sibling.querySelectorAll<HTMLDetailsElement>('details[open]').forEach(child => { child.open = false; });
|
||||
sibling.open = false;
|
||||
}
|
||||
}
|
||||
group.open = true;
|
||||
const menu = group.querySelector<HTMLElement>('.part-command-menu');
|
||||
if (focus && menu) {
|
||||
const items = menuItems(menu);
|
||||
items[focus === 'last' ? items.length - 1 : 0]?.focus();
|
||||
}
|
||||
}
|
||||
function menuKeys(event: React.KeyboardEvent<HTMLElement>) {
|
||||
const target = event.target as HTMLElement;
|
||||
const menu = target.closest<HTMLElement>('.part-command-menu');
|
||||
if (event.key === 'Tab') { closeMenu(target, true); return; }
|
||||
if (event.key === 'Escape' || event.key === 'ArrowLeft' && menu?.parentElement?.classList.contains('part-submenu')) {
|
||||
event.preventDefault(); event.stopPropagation();
|
||||
const parent = menu?.parentElement;
|
||||
if (parent instanceof HTMLDetailsElement && parent.classList.contains('part-submenu')) {
|
||||
parent.querySelectorAll<HTMLDetailsElement>('details[open]').forEach(child => { child.open = false; });
|
||||
parent.open = false; parent.querySelector<HTMLElement>('summary')?.focus();
|
||||
} else closeMenu(target, true);
|
||||
return;
|
||||
}
|
||||
const submenu = target.parentElement instanceof HTMLDetailsElement ? target.parentElement : undefined;
|
||||
if (target.tagName === 'SUMMARY' && submenu &&
|
||||
(['ArrowRight', 'Enter', ' '].includes(event.key) || !menu && ['ArrowDown', 'ArrowUp'].includes(event.key))) {
|
||||
event.preventDefault(); event.stopPropagation();
|
||||
openGroup(submenu, event.key === 'ArrowUp' ? 'last' : 'first'); return;
|
||||
}
|
||||
if (!menu) return;
|
||||
if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) return;
|
||||
event.preventDefault(); event.stopPropagation();
|
||||
const items = menuItems(menu);
|
||||
if (!items.length) return;
|
||||
const index = items.indexOf(target);
|
||||
const next = event.key === 'Home' ? 0 : event.key === 'End' ? items.length - 1 : (index + (event.key === 'ArrowUp' ? -1 : 1) + items.length) % items.length;
|
||||
items[next]?.focus();
|
||||
}
|
||||
function Action({ command, props, toolbar = false }: { command: string; props: Props; toolbar?: boolean }) {
|
||||
const item = partUiActions[command];
|
||||
if (!item) return null;
|
||||
return <button type="button" className={toolbar ? 'part-tool-button' : undefined} data-command={command}
|
||||
disabled={props.disabled(item)} aria-label={item.label} title={item.label} aria-pressed={toolbar ? props.currentTask === item.task : undefined}
|
||||
onClick={event => { closeMenu(event.currentTarget, true); props.activate(item); }}>
|
||||
{item.icon && <img src={`/icons/part/${item.icon}.svg`} alt="" aria-hidden="true" />}
|
||||
{!item.icon && !toolbar && <span className="part-command-icon" aria-hidden="true" />}
|
||||
{(!toolbar || !item.icon) && item.label}
|
||||
</button>;
|
||||
}
|
||||
function Group({ name, commands, props, toolbar = false }: { name: string; commands: string[]; props: Props; toolbar?: boolean }) {
|
||||
return <details className={toolbar ? 'part-tool-group' : 'part-submenu'} onKeyDown={toolbar ? menuKeys : undefined}>
|
||||
<summary onClick={event => {
|
||||
event.preventDefault();
|
||||
const group = event.currentTarget.parentElement as HTMLDetailsElement;
|
||||
if (group.open) group.open = false; else openGroup(group);
|
||||
}}>{name}</summary><div className="part-command-menu">{commands.map(command => <Action key={command} command={command} props={props} />)}</div>
|
||||
</details>;
|
||||
}
|
||||
export function PartWorkbenchMenu(props: Props) {
|
||||
const root = useRef<HTMLDetailsElement>(null);
|
||||
useEffect(() => {
|
||||
const dismiss = (event: PointerEvent) => { if (root.current && !root.current.contains(event.target as Node)) closeMenu(root.current); };
|
||||
document.addEventListener('pointerdown', dismiss); return () => document.removeEventListener('pointerdown', dismiss);
|
||||
}, []);
|
||||
return <details ref={root} className="part-menu" onKeyDown={menuKeys}><summary>Part</summary><div className="part-command-menu">
|
||||
<Action command="Part_BoxSelection" props={props} /><hr />
|
||||
<Group name="Primitives" commands={family('Primitives')} props={props} />
|
||||
<Action command="Part_Primitives" props={props} />
|
||||
<Action command="Part_Builder" props={props} /><hr />
|
||||
{['Part_ShapeFromMesh', 'Part_PointsFromMesh'].map(command => <Action key={command} command={command} props={props} />)}
|
||||
{['Part_MakeSolid', 'Part_ReverseShape'].map(command => <Action key={command} command={command} props={props} />)}
|
||||
<Group name="Copy" commands={family('Copy')} props={props} /><hr />
|
||||
{['Boolean', 'Join', 'Split', 'Compound'].map(name => <Group key={name} name={name} commands={family(name)} props={props} />)}<hr />
|
||||
<Action command="Sketcher_NewSketch" props={props} />
|
||||
{[...tools, 'Part_Offset', 'Part_Offset2D', 'Part_Thickness', 'Part_ProjectionOnSurface', 'Part_SectionCut'].map(command => <Action key={command} command={command} props={props} />)}<hr />
|
||||
<Action command="Part_EditAttachment" props={props} /><hr />
|
||||
{family('Inspection').map(command => <Action key={command} command={command} props={props} />)}
|
||||
</div></details>;
|
||||
}
|
||||
export function PartWorkbenchViewItems(props: Props) {
|
||||
return <Action command="Part_ColorPerFace" props={props} />;
|
||||
}
|
||||
export function PartWorkbenchToolbars(props: Props) {
|
||||
useEffect(() => {
|
||||
const dismiss = (event: PointerEvent) => document.querySelectorAll<HTMLDetailsElement>('.part-tool-group[open]').forEach(node => { if (!node.contains(event.target as Node)) closeMenu(node); });
|
||||
document.addEventListener('pointerdown', dismiss); return () => document.removeEventListener('pointerdown', dismiss);
|
||||
}, []);
|
||||
return <>
|
||||
<ToolbarGroup name="Solids">{family('Solids').map(command => <Action key={command} command={command} props={props} toolbar />)}</ToolbarGroup>
|
||||
<ToolbarGroup name="Part Tools">{['Sketcher_NewSketch', ...tools].map(command => <Action key={command} command={command} props={props} toolbar />)}
|
||||
<Group name="Offset" commands={['Part_Offset', 'Part_Offset2D']} props={props} toolbar />
|
||||
{['Part_Thickness', 'Part_ProjectionOnSurface', 'Part_ColorPerFace'].map(command => <Action key={command} command={command} props={props} toolbar />)}
|
||||
</ToolbarGroup>
|
||||
<ToolbarGroup name="Boolean Tools"><Group name="Compound Tools" commands={family('Compound')} props={props} toolbar />
|
||||
{family('Boolean').map(command => <Action key={command} command={command} props={props} toolbar />)}
|
||||
<Group name="Join Shapes" commands={family('Join')} props={props} toolbar /><Group name="Split Shapes" commands={family('Split')} props={props} toolbar />
|
||||
{['Part_CheckGeometry', 'Part_Defeaturing'].map(command => <Action key={command} command={command} props={props} toolbar />)}
|
||||
</ToolbarGroup>
|
||||
</>;
|
||||
}
|
||||
173
web/src/persistent-toolbar-workspace.tsx
Normal file
173
web/src/persistent-toolbar-workspace.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import React, { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import type { WebModuleRouter } from "./module-router.js";
|
||||
import { areas, onToolbarPreferencesChanged, restoreToolbars, saveToolbars, readToolbarVisibility, setToolbarVisibility, type Area, type Placement } from "./toolbar-preferences.js";
|
||||
import { ToolbarVisibilityMenu } from "./toolbar-visibility-menu.js";
|
||||
|
||||
const names = ["Workbench", "Solids", "Part Tools", "Boolean Tools", "Spreadsheet tools", "3D display controls"];
|
||||
const Hosts = createContext<Map<string, HTMLElement>>(new Map());
|
||||
export function ToolbarGroup({ name, children }: { name: string; children: React.ReactNode }) {
|
||||
const hosts = useContext(Hosts);
|
||||
const host = hosts.get(name);
|
||||
return host ? createPortal(children, host) : null;
|
||||
}
|
||||
export function PersistentToolbarWorkspace({ router, documentId, workbench, children }: {
|
||||
router: WebModuleRouter; documentId: string; workbench: string; children: React.ReactNode;
|
||||
}) {
|
||||
const defaults = useMemo<Placement[]>(() => names.filter(name =>
|
||||
name === "Workbench" || name === "3D display controls" || (workbench === "Part"
|
||||
? ["Solids", "Part Tools", "Boolean Tools"].includes(name) : workbench === "Spreadsheet" && name === "Spreadsheet tools"))
|
||||
.map(name => ({ name, area: "Top", breakBefore: false })), [workbench]);
|
||||
const hosts = useMemo(() => new Map(names.map(name => {
|
||||
const host = document.createElement("div"); host.className = "persistent-toolbar workbench-toolbar";
|
||||
host.dataset.toolbarName = name; host.setAttribute("role", "toolbar"); host.setAttribute("aria-label", name);
|
||||
return [name, host] as const;
|
||||
})), []);
|
||||
const root = useRef<HTMLDivElement>(null);
|
||||
const arrangeButton = useRef<HTMLButtonElement>(null);
|
||||
const editor = useRef<HTMLDivElement>(null);
|
||||
const [layout, setLayout] = useState<Placement[]>(defaults);
|
||||
const [visibility, setVisibility] = useState<Record<string, boolean>>({});
|
||||
const [draft, setDraft] = useState<Placement[] | null>(null);
|
||||
const [ready, setReady] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [restoreAttempt, setRestoreAttempt] = useState(0);
|
||||
const revision = useRef(0);
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
const refresh = () => {
|
||||
const token = ++revision.current;
|
||||
setDraft(null); setReady(false); setError("");
|
||||
void Promise.all([restoreToolbars(router, documentId, workbench, defaults), readToolbarVisibility(router, documentId)]).then(([value, visible]) => {
|
||||
if (!disposed && token === revision.current) { const seen = new Set<string>();
|
||||
const migrated = value.map(item => ({ ...item, name: item.name === "Part tools" ? "Part Tools" : item.name }))
|
||||
.filter(item => { if (seen.has(item.name) || !hosts.has(item.name)) return false; seen.add(item.name); return true; });
|
||||
setLayout(migrated); setVisibility(visible); setError(""); setReady(true); }
|
||||
}).catch(cause => { if (!disposed && token === revision.current) setError(String(cause)); });
|
||||
};
|
||||
refresh(); const unsubscribe = onToolbarPreferencesChanged(refresh);
|
||||
return () => { disposed = true; ++revision.current; unsubscribe(); };
|
||||
}, [router, documentId, workbench, defaults, hosts, restoreAttempt]);
|
||||
useLayoutEffect(() => {
|
||||
const node = root.current!;
|
||||
for (const area of areas) {
|
||||
const dock = node.querySelector<HTMLElement>(`[data-toolbar-area=${area}]`)!;
|
||||
const oldLines = Array.from(dock.children);
|
||||
let line: HTMLDivElement | undefined;
|
||||
for (const item of draft ?? layout) {
|
||||
const host = hosts.get(item.name);
|
||||
if (item.area !== area || !host) continue;
|
||||
if (!line || item.breakBefore) { line = document.createElement("div"); line.className = "toolbar-line"; dock.append(line); }
|
||||
line.append(host);
|
||||
}
|
||||
oldLines.forEach(item => item.remove());
|
||||
}
|
||||
}, [hosts, layout, draft]);
|
||||
useLayoutEffect(() => {
|
||||
for (const [name, host] of hosts) host.hidden = visibility[name] === false;
|
||||
}, [hosts, visibility]);
|
||||
useEffect(() => {
|
||||
const node = root.current!; const host = hosts.get("3D display controls")!;
|
||||
const attach = () => {
|
||||
const toolbar = node.querySelector<HTMLElement>(".scene-display-tools");
|
||||
if (toolbar && toolbar.parentElement !== host) host.append(toolbar);
|
||||
};
|
||||
attach(); const observer = new MutationObserver(attach); observer.observe(node, { childList: true, subtree: true });
|
||||
return () => observer.disconnect();
|
||||
}, [hosts]);
|
||||
useEffect(() => {
|
||||
if (!draft) return;
|
||||
editor.current?.querySelector<HTMLElement>('select:not(:disabled), button:not(:disabled)')?.focus();
|
||||
}, [draft !== null]);
|
||||
const cancelDraft = () => { setDraft(null); arrangeButton.current?.focus(); };
|
||||
useEffect(() => {
|
||||
if (!draft) return;
|
||||
const cancel = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Escape") return;
|
||||
event.preventDefault(); event.stopImmediatePropagation(); if (!busy) cancelDraft();
|
||||
};
|
||||
window.addEventListener("keydown", cancel, true);
|
||||
return () => window.removeEventListener("keydown", cancel, true);
|
||||
}, [draft, busy]);
|
||||
const move = (name: string, offset: number) => setDraft(previous => {
|
||||
if (!previous) return previous;
|
||||
const next = previous.map(item => ({ ...item })); const index = next.findIndex(item => item.name === name);
|
||||
const siblings = next.map((item, i) => item.area === next[index].area ? i : -1).filter(i => i >= 0);
|
||||
const target = siblings[siblings.indexOf(index) + offset];
|
||||
if (target !== undefined) [next[index], next[target]] = [next[target], next[index]];
|
||||
return next;
|
||||
});
|
||||
const apply = async () => {
|
||||
if (!draft || busy) return;
|
||||
const value = draft; const token = revision.current; setBusy(true); setError("");
|
||||
try {
|
||||
await saveToolbars(router, documentId, workbench, value);
|
||||
if (token === revision.current) { setLayout(value); setDraft(null); }
|
||||
} catch (cause) { if (token === revision.current) setError(`Toolbar placement could not be saved: ${String(cause)}`); }
|
||||
finally { setBusy(false); }
|
||||
};
|
||||
const toggleVisibility = async (name: string, visible: boolean) => {
|
||||
if (!ready || busy || draft) return;
|
||||
const token = revision.current; setBusy(true); setError("");
|
||||
try {
|
||||
await setToolbarVisibility(router, documentId, name, visible);
|
||||
if (token === revision.current) setVisibility(previous => ({ ...previous, [name]: visible }));
|
||||
} catch (cause) {
|
||||
if (token === revision.current) setError(`Toolbar visibility could not be saved: ${String(cause)}`);
|
||||
} finally { setBusy(false); }
|
||||
};
|
||||
return <Hosts.Provider value={hosts}><div ref={root} className="toolbar-workspace">
|
||||
<style>{`
|
||||
.toolbar-workspace{display:grid;grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:auto auto minmax(0,1fr) auto}
|
||||
.toolbar-placement-controls{grid-column:1/-1;padding:4px 14px;background:#17212b}
|
||||
.toolbar-dock{display:flex;flex-direction:column;align-content:flex-start;align-items:flex-start;gap:4px;min-width:0;background:#17212b}
|
||||
.toolbar-dock[data-toolbar-area=Top]{grid-column:1/-1;grid-row:2;flex-wrap:wrap}
|
||||
.toolbar-dock[data-toolbar-area=Bottom]{grid-column:1/-1;grid-row:4;flex-wrap:wrap}
|
||||
.toolbar-dock[data-toolbar-area=Left]{grid-column:1;grid-row:3}
|
||||
.toolbar-dock[data-toolbar-area=Right]{grid-column:3;grid-row:3}
|
||||
.toolbar-content{grid-column:2;grid-row:3;min-width:0}
|
||||
.persistent-toolbar{max-width:100%;padding:4px!important;gap:4px!important}
|
||||
.persistent-toolbar:empty{display:none}
|
||||
.persistent-toolbar[hidden]{display:none!important}
|
||||
.toolbar-visibility-menu{min-width:210px;max-width:calc(100vw - 8px);max-height:calc(100vh - 8px);overflow:auto;padding:3px;background:#17212b;color:#e7edf3;border:1px solid #435464;box-shadow:0 4px 12px #0006}
|
||||
.toolbar-visibility-menu button{display:flex;align-items:center;gap:8px;width:100%;padding:5px 8px;text-align:left;background:transparent;color:inherit;border:0;border-radius:0;font:inherit;font-size:13px}
|
||||
.toolbar-visibility-menu button span{width:16px;flex-shrink:0}.toolbar-visibility-menu button:hover,.toolbar-visibility-menu button:focus-visible{background:#264b56;outline:1px solid #66c7d5;outline-offset:-1px}
|
||||
.toolbar-dock .toolbar-break{flex-basis:100%;height:0}
|
||||
.toolbar-line{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px}.toolbar-dock[data-toolbar-area=Left],.toolbar-dock[data-toolbar-area=Right]{flex-direction:row;max-width:35vw;overflow:auto}.toolbar-dock[data-toolbar-area=Left] .toolbar-line,.toolbar-dock[data-toolbar-area=Right] .toolbar-line{flex-direction:column;flex-wrap:nowrap}
|
||||
.toolbar-dock[data-toolbar-area=Left] .persistent-toolbar,.toolbar-dock[data-toolbar-area=Right] .persistent-toolbar{width:150px;flex-direction:column;align-items:stretch}
|
||||
.toolbar-dock[data-toolbar-area=Left] .toolbar-break,.toolbar-dock[data-toolbar-area=Right] .toolbar-break{width:0;height:100%;flex-basis:100%}
|
||||
.persistent-toolbar:empty{display:none}
|
||||
.persistent-toolbar .scene-display-tools{position:static;max-width:100%;border:0;margin:0}
|
||||
.toolbar-placement-editor{display:flex;flex-wrap:wrap;gap:10px;padding:8px;border:1px solid #435464}
|
||||
.toolbar-placement-editor label{display:inline-flex;gap:5px;align-items:center}.toolbar-placement-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;width:100%}
|
||||
`}</style>
|
||||
<div className="toolbar-placement-controls">
|
||||
<ToolbarVisibilityMenu workspace={root} names={defaults.map(item => item.name)} visibility={visibility}
|
||||
disabled={!ready || busy || !!draft} toggle={(name, visible) => void toggleVisibility(name, visible)} />
|
||||
<button ref={arrangeButton} disabled={!ready || busy} onClick={() => setDraft(layout.map(item => ({ ...item })))}>Arrange toolbars</button>
|
||||
{error && <span role="alert">{error}</span>}
|
||||
{error && !ready && <button disabled={busy} onClick={() => setRestoreAttempt(attempt => attempt + 1)}>Retry toolbar preferences</button>}
|
||||
{draft && <div ref={editor} className="toolbar-placement-editor" role="dialog" aria-label="Toolbar placement" onKeyDown={event => {
|
||||
if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); if (!busy) cancelDraft(); }
|
||||
}}>
|
||||
{draft.filter(item => hosts.has(item.name)).map(item => <div className="toolbar-placement-row" key={item.name}>
|
||||
<strong>{item.name}</strong><label>Dock <select disabled={busy} value={item.area} onChange={event => {
|
||||
const area = event.target.value as Area;
|
||||
setDraft(previous => previous!.map(entry => entry.name === item.name ? { ...entry, area } : entry));
|
||||
}}>{areas.map(area => <option key={area}>{area}</option>)}</select></label>
|
||||
<button disabled={busy} aria-label={`Move ${item.name} earlier`} onClick={() => move(item.name, -1)}>Earlier</button>
|
||||
<button disabled={busy} aria-label={`Move ${item.name} later`} onClick={() => move(item.name, 1)}>Later</button>
|
||||
<label><input type="checkbox" disabled={busy} checked={item.breakBefore} onChange={event => {
|
||||
const breakBefore = event.target.checked;
|
||||
setDraft(previous => previous!.map(entry => entry.name === item.name ? { ...entry, breakBefore } : entry));
|
||||
}} />New row / column</label>
|
||||
</div>)}
|
||||
<button disabled={busy} onClick={() => void apply()}>Apply placement</button>
|
||||
<button disabled={busy} onClick={cancelDraft}>Cancel</button>
|
||||
</div>}
|
||||
</div>
|
||||
{areas.map(area => <div key={area} className="toolbar-dock" data-toolbar-area={area} aria-label={`${area} toolbar dock`} />)}
|
||||
<div className="toolbar-content">{children}</div>
|
||||
</div></Hosts.Provider>;
|
||||
}
|
||||
121
web/src/start-launch-panel.tsx
Normal file
121
web/src/start-launch-panel.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { WebModuleRouter } from "./module-router.js";
|
||||
import { createStartDesktopRequest, type StartDesktopResult } from "./start-facade.js";
|
||||
import { startParameters, startDefaults, StartPreferencesEditor, FirstStartGeneralSettings } from "./start-preferences.js";
|
||||
const exampleUrls = import.meta.glob("../../freecad-source/data/examples/*.{FCStd,stp}", { eager: true, query: "?url", import: "default" }) as Record<string, string>;
|
||||
|
||||
/** Start owns presentation only. All document operations belong to its host. */
|
||||
export function StartLaunchPanel({ router, epoch, busy, onClose, onNew, onOpen, savedFiles, onOpenSaved, onSpecialized, onOpenFiles, onLaunched }: {
|
||||
router: WebModuleRouter; epoch: number; busy: boolean; onClose: () => void;
|
||||
onNew: () => Promise<boolean | undefined>; onOpen: () => Promise<boolean | undefined>;
|
||||
onSpecialized?: (kind: "Assembly" | "Draft" | "Architecture") => Promise<boolean | undefined>;
|
||||
savedFiles: string[]; onOpenSaved: (name: string) => Promise<boolean | undefined>;
|
||||
onOpenFiles: (files: File[]) => Promise<boolean>;
|
||||
onLaunched: (specialized: boolean) => Promise<void>;
|
||||
}) {
|
||||
const panel = useRef<HTMLElement>(null);
|
||||
const pending = useRef(false);
|
||||
const [working, setWorking] = useState(false);
|
||||
const [view, setView] = useState<StartDesktopResult | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const [stored, setStored] = useState("");
|
||||
const [retry, setRetry] = useState(0);
|
||||
const [preferences, setPreferences] = useState(startDefaults);
|
||||
const [ready, setReady] = useState(false);
|
||||
const [generalReady, setGeneralReady] = useState(false);
|
||||
const [generalBusy, setGeneralBusy] = useState(false);
|
||||
const [folders, setFolders] = useState<Record<string, File[]>>({});
|
||||
const [setup, setSetup] = useState(() => localStorage.getItem("freecad.start.firstStartDone") !== "1");
|
||||
const [showExamples, setShowExamples] = useState(() => localStorage.getItem("freecad.start.showExamples") !== "0");
|
||||
const [customFolder, setCustomFolder] = useState(() => localStorage.getItem("freecad.start.customFolder") ?? "");
|
||||
const [closeStart, setCloseStart] = useState(() => localStorage.getItem("freecad.start.closeStart") === "1");
|
||||
const finishSetup = async () => {
|
||||
setWorking(true);
|
||||
try {
|
||||
await startParameters(router, { FirstStart2024: false, ShowExamples: showExamples, CustomFolder: customFolder, closeStart });
|
||||
setSetup(false);
|
||||
} catch (reason) { setError(String(reason)); }
|
||||
finally { setWorking(false); }
|
||||
};
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
const read = () => { void startParameters(router).then(value => {
|
||||
if (disposed) return;
|
||||
setPreferences(value); setSetup(value.FirstStart2024); setShowExamples(value.ShowExamples);
|
||||
setCustomFolder(value.CustomFolder); setCloseStart(value.closeStart); setReady(true);
|
||||
}).catch(reason => { if (!disposed) setError(String(reason)); }); };
|
||||
read(); window.addEventListener("freecad-start-preferences", read);
|
||||
return () => { disposed = true; window.removeEventListener("freecad-start-preferences", read); };
|
||||
}, [router, epoch, retry]);
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
panel.current?.focus();
|
||||
const request = createStartDesktopRequest("start-panel");
|
||||
void router.request("Start", request.command, request.payload, "start-view").then(response => {
|
||||
if (disposed) return;
|
||||
if (response.kind === "error") throw new Error(response.error.message);
|
||||
setView(response.result as StartDesktopResult); setError("");
|
||||
}).catch(reason => { if (!disposed) setError(String(reason)); });
|
||||
return () => { disposed = true; };
|
||||
}, [router, epoch, retry]);
|
||||
const launch = async (action: () => Promise<boolean | undefined>, specialized = false) => {
|
||||
if (busy || pending.current) return;
|
||||
pending.current = true; setWorking(true); setError("");
|
||||
try {
|
||||
const opened = await action();
|
||||
if (opened) { await onLaunched(specialized); }
|
||||
else setError("No document opened. You can try again.");
|
||||
} catch (reason) { setError(String(reason)); }
|
||||
finally { pending.current = false; setWorking(false); }
|
||||
};
|
||||
return <section ref={panel} tabIndex={-1} aria-label="Start" data-start-view={view?.view}
|
||||
style={{ padding: 20, border: "1px solid #435464" }}>
|
||||
<h2>Start</h2>
|
||||
{!ready && <p>Loading Start preferences…</p>}
|
||||
{setup ? <fieldset disabled={!ready || working}><legend>First start setup</legend>
|
||||
<FirstStartGeneralSettings router={router} disabled={!ready || working} onReadyChange={setGeneralReady} onBusyChange={setGeneralBusy} />
|
||||
<label><input type="checkbox" checked={showExamples} onChange={e => setShowExamples(e.target.checked)} /> Show examples</label>
|
||||
<label>Custom folder <input value={customFolder} onChange={e => setCustomFolder(e.target.value)} /></label>
|
||||
<label><input type="checkbox" checked={closeStart} onChange={e => setCloseStart(e.target.checked)} /> Close Start after loading</label>
|
||||
<button type="button" disabled={!generalReady || generalBusy} onClick={finishSetup}>Done</button>
|
||||
</fieldset> : <><p>Create a document or open a file.</p>
|
||||
<fieldset disabled={busy || working || !ready || !view?.active}>
|
||||
<legend>Documents</legend>
|
||||
<button type="button" onClick={() => void launch(onNew)}>Empty File</button>{" "}
|
||||
<button type="button" onClick={() => void launch(onOpen)}>Open File</button>
|
||||
<p>FCStd, STEP, IGES, BREP and STL</p>
|
||||
<div><button type="button" onClick={() => void launch(() => onSpecialized ? onSpecialized("Assembly") : Promise.reject(new Error("Assembly launch unavailable")), true)}>Assembly</button>{" "}
|
||||
<button type="button" onClick={() => void launch(() => onSpecialized ? onSpecialized("Draft") : Promise.reject(new Error("Draft launch unavailable")), true)}>2D Draft</button>{" "}
|
||||
<button type="button" onClick={() => void launch(() => onSpecialized ? onSpecialized("Architecture") : Promise.reject(new Error("Architecture launch unavailable")), true)}>Architecture</button></div>
|
||||
<label>Saved FCStd <select value={stored} onChange={event => setStored(event.target.value)}>
|
||||
<option value="">Select a saved document</option>
|
||||
{savedFiles.map(name => <option key={name}>{name}</option>)}
|
||||
</select></label>{" "}
|
||||
<button type="button" disabled={!stored} onClick={() => void launch(() => onOpenSaved(stored))}>Open saved document</button>
|
||||
{preferences.ShowExamples && <section aria-label="Examples"><h3>Examples</h3>
|
||||
{Object.entries(exampleUrls).sort(([a], [b]) => a.localeCompare(b)).map(([path, url]) => {
|
||||
const name = path.split("/").at(-1)!;
|
||||
return <button key={path} onClick={() => void launch(async () => {
|
||||
const response = await fetch(url); if (!response.ok) throw new Error(`Cannot load ${name}: ${response.status}`);
|
||||
return onOpenFiles([new File([await response.blob()], name)]);
|
||||
})}>{name}</button>;
|
||||
})}</section>}
|
||||
{preferences.CustomFolder.split(";;").filter(Boolean).map(path => <section key={path} aria-label={`Custom folder ${path}`}>
|
||||
<h3>{path}</h3><label>Grant folder access<input type="file" multiple {...{ webkitdirectory: "" }} onChange={event => {
|
||||
const files = Array.from(event.target.files ?? []).filter(file => file.webkitRelativePath.split("/").length === 2);
|
||||
if (!files.length) { setError(`No readable files selected for ${path}. Choose the folder again.`); return; }
|
||||
setFolders(current => ({ ...current, [path]: files })); event.target.value = "";
|
||||
}} /></label>
|
||||
{!folders[path] && <p>Folder access is required for this browser session.</p>}
|
||||
{(folders[path] ?? []).filter(file => !preferences.ShowOnlyFCStd || /\.fcstd$/i.test(file.name))
|
||||
.sort((a, b) => a.name.localeCompare(b.name)).map(file => <button key={file.name} onClick={() => void launch(() => onOpenFiles([file]))}>{file.name}</button>)}
|
||||
</section>)}
|
||||
</fieldset>
|
||||
<button type="button" onClick={() => setSetup(true)}>Open first start setup</button>
|
||||
<StartPreferencesEditor router={router} />
|
||||
</>}
|
||||
{error && <p role="alert">{error}</p>}
|
||||
{(!view || !ready || error) && <button type="button" onClick={() => setRetry(value => value + 1)}>Reload Start</button>}
|
||||
<button type="button" disabled={working} onClick={onClose}>Dismiss Start</button>
|
||||
</section>;
|
||||
}
|
||||
115
web/src/start-preferences.tsx
Normal file
115
web/src/start-preferences.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { WebModuleRouter } from "./module-router.js";
|
||||
import { requestOwnerParameters, START_PARAMETER_PATH, GENERAL_PARAMETER_PATH } from "./view-navigation-preferences.js";
|
||||
import { navigationStyles, defaultNavigationStyle } from "./scene-navigation.js";
|
||||
|
||||
export const startDefaults = { ShowExamples: true, CustomFolder: "", ShowOnlyFCStd: false,
|
||||
FirstStart2024: true, ShowOnStartup: true, closeStart: false, AutoloadModule: "" };
|
||||
export type StartPreferences = typeof startDefaults;
|
||||
export type StartGeneralPreferences = { Language: string; UserSchema: number; NavigationStyle: string; theme: string };
|
||||
export function applyStartTheme(theme: string) {
|
||||
localStorage.setItem("freecad.theme", theme);
|
||||
const root = document.documentElement;
|
||||
root.dataset.theme = theme;
|
||||
root.style.colorScheme = theme === "system" ? "" : theme;
|
||||
window.dispatchEvent(new CustomEvent("freecad-theme-changed", { detail: { theme } }));
|
||||
}
|
||||
export async function startParameters(router: WebModuleRouter, changes?: Partial<StartPreferences> & { LastModule?: string; Language?: string; UserSchema?: number }, general = false) {
|
||||
const path = general ? GENERAL_PARAMETER_PATH : START_PARAMETER_PATH;
|
||||
const request = async (args: Record<string, unknown>) => {
|
||||
const reply = await requestOwnerParameters(router, "PartBox", "start-preferences", { path, ...args });
|
||||
if (reply.kind !== "result") throw new Error(reply.error.message);
|
||||
const result = reply.result as { parameters?: [string, string, any][]; persistenceError?: string };
|
||||
if (result.persistenceError) throw new Error(`Preferences changed but could not be saved: ${result.persistenceError}`);
|
||||
return Object.fromEntries((result.parameters ?? []).map(([, key, value]) => [key, value]));
|
||||
};
|
||||
for (const [name, value] of Object.entries(changes ?? {}))
|
||||
await request({ operation: "set", name, type: typeof value === "boolean" ? "Bool" : typeof value === "number" ? "Int" : "String", value });
|
||||
return { ...startDefaults, ...await request({ operation: "read" }) } as StartPreferences & { LastModule?: string };
|
||||
}
|
||||
|
||||
export async function readStartGeneral(router: WebModuleRouter): Promise<StartGeneralPreferences> {
|
||||
const values = await startParameters(router, undefined, true) as StartPreferences & { Language?: string; UserSchema?: number; NavigationStyle?: string };
|
||||
const view = await requestOwnerParameters(router, "PartBox", "start-preferences", {
|
||||
path: "User parameter:BaseApp/Preferences/View", operation: "read",
|
||||
});
|
||||
if (view.kind !== "result") throw new Error(view.error.message);
|
||||
const parameters = (view.result as { parameters?: [string, string, unknown][] }).parameters ?? [];
|
||||
const navigation = parameters.find(([, key]) => key === "NavigationStyle")?.[2];
|
||||
return { Language: String(values.Language ?? ""), UserSchema: Number(values.UserSchema ?? 0),
|
||||
NavigationStyle: typeof navigation === "string" && navigation ? navigation : defaultNavigationStyle,
|
||||
theme: localStorage.getItem("freecad.theme") ?? "system" };
|
||||
}
|
||||
|
||||
export async function saveStartGeneral(router: WebModuleRouter, value: StartGeneralPreferences) {
|
||||
await startParameters(router, { Language: value.Language, UserSchema: Number(value.UserSchema) }, true);
|
||||
const reply = await requestOwnerParameters(router, "PartBox", "start-preferences", {
|
||||
path: "User parameter:BaseApp/Preferences/View", operation: "set", name: "NavigationStyle",
|
||||
type: "String", value: value.NavigationStyle,
|
||||
});
|
||||
if (reply.kind !== "result") throw new Error(reply.error.message);
|
||||
applyStartTheme(value.theme);
|
||||
window.dispatchEvent(new Event("freecad-start-preferences"));
|
||||
}
|
||||
|
||||
export function FirstStartGeneralSettings({ router, disabled = false, onReadyChange, onBusyChange }: {
|
||||
router: WebModuleRouter; disabled?: boolean; onReadyChange?: (ready: boolean) => void; onBusyChange?: (busy: boolean) => void;
|
||||
}) {
|
||||
const [value, setValue] = useState<StartGeneralPreferences>({ Language: "", UserSchema: 0,
|
||||
NavigationStyle: defaultNavigationStyle, theme: localStorage.getItem("freecad.theme") ?? "system" });
|
||||
const [ready, setReady] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
onReadyChange?.(false);
|
||||
void readStartGeneral(router).then(next => { if (!disposed) { setValue(next); setReady(true); } })
|
||||
.catch(cause => { if (!disposed) { setError(String(cause)); setReady(true); } });
|
||||
return () => { disposed = true; };
|
||||
}, [router, onReadyChange]);
|
||||
useEffect(() => { onReadyChange?.(ready); }, [ready, onReadyChange]);
|
||||
const update = (next: StartGeneralPreferences) => {
|
||||
setValue(next); setBusy(true); onBusyChange?.(true); setError("");
|
||||
void saveStartGeneral(router, next).catch(cause => setError(String(cause))).finally(() => { setBusy(false); onBusyChange?.(false); });
|
||||
};
|
||||
return <fieldset disabled={disabled || busy || !ready}><legend>General settings</legend>
|
||||
<label>Language <select value={value.Language} onChange={e => update({ ...value, Language: e.target.value })}>
|
||||
<option value="">System default</option><option value="English">English</option><option value="中文">中文</option><option value="Deutsch">Deutsch</option><option value="Français">Français</option>
|
||||
</select></label>
|
||||
<label>Units <select value={String(value.UserSchema)} onChange={e => update({ ...value, UserSchema: Number(e.target.value) })}>
|
||||
<option value="0">Standard</option><option value="1">SI1</option><option value="2">SI2</option><option value="5">Imperial decimal</option><option value="7">Building Euro</option>
|
||||
</select></label>
|
||||
<label>Navigation <select value={value.NavigationStyle} onChange={e => update({ ...value, NavigationStyle: e.target.value })}>
|
||||
{navigationStyles.map(style => <option key={style.value} value={style.value}>{style.label}</option>)}
|
||||
</select></label>
|
||||
<label>Theme <select value={value.theme} onChange={e => update({ ...value, theme: e.target.value })}>
|
||||
<option value="system">System</option><option value="light">Light</option><option value="dark">Dark</option>
|
||||
</select></label>
|
||||
{error && <p role="alert">{error}</p>}
|
||||
</fieldset>;
|
||||
}
|
||||
|
||||
export function StartPreferencesEditor({ router }: { router: WebModuleRouter }) {
|
||||
const [value, setValue] = useState(startDefaults);
|
||||
const [error, setError] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
const read = () => { void startParameters(router).then(v => { if (!disposed) setValue(v); }).catch(e => { if (!disposed) setError(String(e)); }); };
|
||||
read(); window.addEventListener("freecad-start-preferences", read);
|
||||
return () => { disposed = true; window.removeEventListener("freecad-start-preferences", read); };
|
||||
}, [router]);
|
||||
const save = async () => {
|
||||
setBusy(true); setError("");
|
||||
try { setValue(await startParameters(router, value)); } catch (e) { setError(String(e)); }
|
||||
finally { setBusy(false); }
|
||||
};
|
||||
return <><fieldset disabled={busy}><legend>Start preferences</legend>
|
||||
{(["ShowExamples", "ShowOnlyFCStd", "ShowOnStartup", "closeStart", "FirstStart2024"] as const).map(key =>
|
||||
<label key={key}><input type="checkbox" checked={value[key]} onChange={e => setValue(v => ({ ...v, [key]: e.target.checked }))} />{key}</label>)}
|
||||
<label>Custom folders (separate with ;;)<input value={value.CustomFolder} onChange={e => setValue(v => ({ ...v, CustomFolder: e.target.value }))} /></label>
|
||||
<label>Workbench after opening<input value={value.AutoloadModule} placeholder="Workbench name or $LastModule" onChange={e => setValue(v => ({ ...v, AutoloadModule: e.target.value }))} /></label>
|
||||
<button type="button" onClick={() => void save()}>Save Start preferences</button>
|
||||
{error && <p role="alert">{error}</p>}
|
||||
</fieldset><FirstStartGeneralSettings router={router} /> </>;
|
||||
}
|
||||
170
web/src/toolbar-preferences.ts
Normal file
170
web/src/toolbar-preferences.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import type { WebModuleRouter } from "./module-router.js";
|
||||
import type { ViewOwner } from "./part-view-facade.js";
|
||||
import { requestOwnerParameters, saved, persist } from "./view-navigation-preferences.js";
|
||||
import { createFailure, createSuccess } from "./facade-envelope.js";
|
||||
import type { StandardCommandResult } from "./web-standard-command-contract.js";
|
||||
|
||||
export const toolbarPath = "User parameter:Tux/PersistentToolbars";
|
||||
export const toolbarVisibilityPath = "User parameter:BaseApp/MainWindow/Toolbars";
|
||||
export const areas = ["Top", "Left", "Right", "Bottom"] as const;
|
||||
export type Area = typeof areas[number];
|
||||
export type Placement = { name: string; area: Area; breakBefore: boolean };
|
||||
const legacyFile = "Std_DlgParameter-PersistentToolbars.FCParam";
|
||||
const file = "Std_DlgParameter-PersistentToolbars.json";
|
||||
const snapshotSchema = "freecad-web/persistent-toolbars/v1";
|
||||
type ToolbarSnapshot = { schema: typeof snapshotSchema; userParameters: string; enabled?: boolean; visibilityParameters?: string };
|
||||
function decodeSnapshot(data: string): ToolbarSnapshot | undefined {
|
||||
const text = atob(data);
|
||||
if (!text.trimStart().startsWith("{")) return undefined; // Legacy User-only FCParam.
|
||||
const snapshot = JSON.parse(text) as ToolbarSnapshot;
|
||||
if (snapshot.schema !== snapshotSchema || typeof snapshot.userParameters !== "string"
|
||||
|| snapshot.enabled !== undefined && typeof snapshot.enabled !== "boolean"
|
||||
|| snapshot.visibilityParameters !== undefined && typeof snapshot.visibilityParameters !== "string")
|
||||
throw new Error("Invalid persistent toolbar preference snapshot");
|
||||
return snapshot;
|
||||
}
|
||||
function enabledParameter(root: StandardCommandResult): boolean | undefined {
|
||||
const value = root.parameters?.find((entry: unknown[]) => entry[1] === "Enabled")?.[2];
|
||||
if (value === undefined) return undefined;
|
||||
return value !== false && value !== 0 && !["false", "0"].includes(String(value).toLowerCase());
|
||||
}
|
||||
const states = new WeakMap<WebModuleRouter, Map<ViewOwner, { ready: boolean; snapshot?: string; tail: Promise<unknown> }>>();
|
||||
const listeners = new Set<() => void>();
|
||||
let latest: string | undefined;
|
||||
const channel = typeof BroadcastChannel === "undefined" ? undefined : new BroadcastChannel("freecad-toolbar-parameters");
|
||||
if (channel) channel.onmessage = event => {
|
||||
if (typeof event.data !== "string") return;
|
||||
latest = event.data; listeners.forEach(fn => fn());
|
||||
};
|
||||
export function onToolbarPreferencesChanged(fn: () => void) { listeners.add(fn); return () => { listeners.delete(fn); }; }
|
||||
// Persist root Enabled and User together; System/add* remain native session presets.
|
||||
export function toolbarParameters(router: WebModuleRouter, owner: ViewOwner, documentId: string,
|
||||
action: (request: (args: Record<string, unknown>) => Promise<StandardCommandResult>) => Promise<unknown>, changed = false) {
|
||||
let owners = states.get(router);
|
||||
if (!owners) { owners = new Map(); states.set(router, owners); }
|
||||
let state = owners.get(owner);
|
||||
if (!state) { state = { ready: false, tail: Promise.resolve() }; owners.set(owner, state); }
|
||||
const current = state;
|
||||
const run = async () => {
|
||||
const request = async (args: Record<string, unknown>) => {
|
||||
const reply = await requestOwnerParameters(router, owner, documentId, args);
|
||||
if (reply.kind !== "result") throw new Error(reply.error.message);
|
||||
return reply.result as StandardCommandResult;
|
||||
};
|
||||
const userPath = `${toolbarPath}/User`;
|
||||
const capture = async () => {
|
||||
const root = await request({ path: toolbarPath, operation: "read" });
|
||||
const exported = await request({ path: userPath, operation: "export" });
|
||||
const visibility = await request({ path: toolbarVisibilityPath, operation: "export" });
|
||||
if (typeof exported.fileData !== "string" || typeof visibility.fileData !== "string")
|
||||
throw new Error("Native toolbar parameter export is missing");
|
||||
// Fields are ASCII (FCParam is already base64), so btoa preserves the full snapshot.
|
||||
return btoa(JSON.stringify({ schema: snapshotSchema, userParameters: exported.fileData,
|
||||
enabled: enabledParameter(root), visibilityParameters: visibility.fileData } satisfies ToolbarSnapshot));
|
||||
};
|
||||
const replace = async (data: string) => {
|
||||
const snapshot = decodeSnapshot(data);
|
||||
const root = await request({ path: userPath, operation: "read" });
|
||||
for (const group of root.groups ?? []) for (const name of ["Saved", ...areas])
|
||||
await request({ path: `${userPath}/${group}`, operation: "remove", name, type: name === "Saved" ? "Bool" : "String" });
|
||||
await request({ path: userPath, operation: "import", fileData: snapshot?.userParameters ?? data });
|
||||
// A legacy User-only snapshot must not reset the native root preference.
|
||||
if (snapshot) await request({ path: toolbarPath,
|
||||
operation: snapshot.enabled === undefined ? "remove" : "set",
|
||||
name: "Enabled", type: "Bool", ...(snapshot.enabled === undefined ? {} : { value: snapshot.enabled }) });
|
||||
if (snapshot?.visibilityParameters !== undefined) {
|
||||
const visibility = await request({ path: toolbarVisibilityPath, operation: "read" });
|
||||
for (const [type, name] of visibility.parameters ?? []) if (type === "Bool")
|
||||
await request({ path: toolbarVisibilityPath, operation: "remove", name, type });
|
||||
await request({ path: toolbarVisibilityPath, operation: "import", fileData: snapshot.visibilityParameters });
|
||||
}
|
||||
};
|
||||
const data = latest ?? (!current.ready ? (await saved(file) ?? await saved(legacyFile)) : undefined);
|
||||
if (data && current.snapshot !== data) {
|
||||
const before = await capture();
|
||||
try { await replace(data); }
|
||||
catch (error) { await replace(before); throw error; }
|
||||
current.snapshot = data; latest = data;
|
||||
}
|
||||
current.ready = true;
|
||||
const before = changed ? await capture() : undefined;
|
||||
try {
|
||||
const result = await action(request);
|
||||
if (changed) {
|
||||
const exported = await capture();
|
||||
if (exported === before) return result;
|
||||
await persist(exported, file);
|
||||
latest = exported; current.snapshot = latest;
|
||||
channel?.postMessage(latest); listeners.forEach(fn => fn());
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (before) await replace(before);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
const pending = current.tail.then(run, run); current.tail = pending.catch(() => {}); return pending;
|
||||
}
|
||||
export async function restoreToolbars(router: WebModuleRouter, documentId: string, workbench: string, defaults: Placement[]): Promise<Placement[]> {
|
||||
return await toolbarParameters(router, "DocumentProbe", documentId, async request => {
|
||||
const root = await request({ path: toolbarPath, operation: "read" });
|
||||
if (enabledParameter(root) === false) return defaults.map(item => ({ ...item }));
|
||||
const read = async (kind: string) => Object.fromEntries((await request({ path: `${toolbarPath}/${kind}/${workbench}Workbench`, operation: "read" })).parameters!.map(([, key, value]) => [key, value]));
|
||||
const user = await read("User");
|
||||
const parameters = user.Saved ? user : await read("System");
|
||||
const result: Placement[] = []; const seen = new Set<string>();
|
||||
if (parameters.Saved) for (const area of areas) {
|
||||
let breakBefore = false;
|
||||
for (const name of String(parameters[area] ?? "").split(",")) {
|
||||
if (name === "Break") { breakBefore = true; continue; }
|
||||
if (name && !seen.has(name)) { result.push({ name, area, breakBefore }); seen.add(name); breakBefore = false; }
|
||||
}
|
||||
}
|
||||
for (const item of defaults) if (!seen.has(item.name)) result.push({ ...item });
|
||||
return result;
|
||||
}) as Placement[];
|
||||
}
|
||||
export async function saveToolbars(router: WebModuleRouter, documentId: string, workbench: string, layout: Placement[]) {
|
||||
return await toolbarParameters(router, "DocumentProbe", documentId, async request => {
|
||||
const root = await request({ path: toolbarPath, operation: "read" });
|
||||
if (enabledParameter(root) === false) return false;
|
||||
const path = `${toolbarPath}/User/${workbench}Workbench`;
|
||||
for (const area of areas) await request({ path, operation: "set", name: area, type: "String",
|
||||
value: layout.filter(item => item.area === area).flatMap(item => item.breakBefore ? ["Break", item.name] : [item.name]).join(",") });
|
||||
await request({ path, operation: "set", name: "Saved", type: "Bool", value: true });
|
||||
return true;
|
||||
}, true);
|
||||
}
|
||||
|
||||
export async function readToolbarVisibility(router: WebModuleRouter, documentId: string): Promise<Record<string, boolean>> {
|
||||
return await toolbarParameters(router, "DocumentProbe", documentId, async request => {
|
||||
const result = await request({ path: toolbarVisibilityPath, operation: "read" });
|
||||
return Object.fromEntries((result.parameters ?? []).filter(([type]) => type === "Bool")
|
||||
.map(([, name, value]) => [name, value !== false && value !== 0 && !["false", "0"].includes(String(value).toLowerCase())]));
|
||||
}) as Record<string, boolean>;
|
||||
}
|
||||
export async function setToolbarVisibility(router: WebModuleRouter, documentId: string, name: string, visible: boolean) {
|
||||
// Visibility belongs to the desktop MainWindow preferences, independent of Tux's layout gate.
|
||||
return toolbarParameters(router, "DocumentProbe", documentId, request =>
|
||||
request({ path: toolbarVisibilityPath, operation: "set", name, type: "Bool", value: visible }), true);
|
||||
}
|
||||
function relatedPath(path: string, root: string) {
|
||||
return path === root || path.startsWith(root + "/") || root.startsWith(path.endsWith(":") ? path : path + "/");
|
||||
}
|
||||
|
||||
/** Native parameter-panel edits and imports use the same authority as toolbar moves. */
|
||||
export function requestToolbarParameters(router: WebModuleRouter, owner: ViewOwner, documentId: string, args: Record<string, unknown>) {
|
||||
const path = String(args.path ?? "User parameter:");
|
||||
const mutates = ["set", "remove", "import"].includes(String(args.operation));
|
||||
const user = `${toolbarPath}/User`;
|
||||
const affectsUser = relatedPath(path, user) || relatedPath(path, toolbarVisibilityPath);
|
||||
return toolbarParameters(router, owner, documentId, request => request(args), mutates && affectsUser)
|
||||
.then(result => {
|
||||
if (mutates && !affectsUser) listeners.forEach(fn => fn());
|
||||
return createSuccess("toolbar-parameters", result);
|
||||
}).catch(error => createFailure("toolbar-parameters", "parameter_update_failed", String(error)));
|
||||
}
|
||||
export function isToolbarParameterPath(path: unknown) {
|
||||
const value = String(path ?? "User parameter:");
|
||||
return relatedPath(value, toolbarPath) || relatedPath(value, toolbarVisibilityPath);
|
||||
}
|
||||
84
web/src/toolbar-visibility-menu.tsx
Normal file
84
web/src/toolbar-visibility-menu.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
/** Desktop toolbar toggle actions, shared by the toolbar button and dock context menus. */
|
||||
export function ToolbarVisibilityMenu({ workspace, names, visibility, disabled, toggle }: {
|
||||
workspace: React.RefObject<HTMLDivElement | null>; names: string[]; visibility: Record<string, boolean>;
|
||||
disabled: boolean; toggle: (name: string, visible: boolean) => void;
|
||||
}) {
|
||||
const [anchor, setAnchor] = useState<{ x: number; y: number } | null>(null);
|
||||
const trigger = useRef<HTMLButtonElement>(null);
|
||||
const menu = useRef<HTMLDivElement>(null);
|
||||
const previousFocus = useRef<HTMLElement | null>(null);
|
||||
const close = (restore = true) => {
|
||||
setAnchor(null);
|
||||
if (restore) {
|
||||
const previous = previousFocus.current;
|
||||
if (previous?.isConnected && previous.getClientRects().length) previous.focus();
|
||||
else trigger.current?.focus();
|
||||
}
|
||||
};
|
||||
const open = (x: number, y: number) => {
|
||||
previousFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : trigger.current;
|
||||
setAnchor({ x, y });
|
||||
};
|
||||
useEffect(() => {
|
||||
const root = workspace.current;
|
||||
if (!root) return;
|
||||
const context = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement;
|
||||
if (!target.closest('.toolbar-dock, .toolbar-placement-controls')) return;
|
||||
event.preventDefault();
|
||||
if (disabled) return;
|
||||
const rect = target.getBoundingClientRect();
|
||||
open(event.clientX || rect.left, event.clientY || rect.bottom);
|
||||
};
|
||||
root.addEventListener('contextmenu', context);
|
||||
return () => root.removeEventListener('contextmenu', context);
|
||||
}, [workspace, disabled]);
|
||||
useLayoutEffect(() => {
|
||||
if (!anchor || !menu.current) return;
|
||||
const element = menu.current;
|
||||
element.style.left = `${Math.max(4, Math.min(anchor.x, window.innerWidth - element.offsetWidth - 4))}px`;
|
||||
element.style.top = `${Math.max(4, Math.min(anchor.y, window.innerHeight - element.offsetHeight - 4))}px`;
|
||||
element.querySelector<HTMLButtonElement>('button')?.focus();
|
||||
}, [anchor]);
|
||||
useEffect(() => {
|
||||
if (!anchor) return;
|
||||
const dismiss = (event: PointerEvent) => {
|
||||
if (!menu.current?.contains(event.target as Node) && !trigger.current?.contains(event.target as Node)) close(false);
|
||||
};
|
||||
const resize = () => close();
|
||||
document.addEventListener('pointerdown', dismiss);
|
||||
window.addEventListener('resize', resize);
|
||||
return () => { document.removeEventListener('pointerdown', dismiss); window.removeEventListener('resize', resize); };
|
||||
}, [anchor]);
|
||||
useEffect(() => { if (disabled) setAnchor(null); }, [disabled]);
|
||||
return <>
|
||||
<button ref={trigger} type="button" disabled={disabled} aria-haspopup="menu" aria-expanded={!!anchor}
|
||||
onClick={() => {
|
||||
if (anchor) { close(); return; }
|
||||
const rect = trigger.current!.getBoundingClientRect(); open(rect.left, rect.bottom);
|
||||
}}>Toolbars</button>
|
||||
{anchor && createPortal(<div ref={menu} role="menu" aria-label="Toolbars" className="toolbar-visibility-menu"
|
||||
style={{ position: 'fixed', left: anchor.x, top: anchor.y, zIndex: 1000 }} onKeyDown={event => {
|
||||
if (event.key === 'Escape' || event.key === 'Tab') {
|
||||
if (event.key === 'Escape') event.preventDefault();
|
||||
event.stopPropagation(); close(); return;
|
||||
}
|
||||
if (!['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) return;
|
||||
event.preventDefault(); event.stopPropagation();
|
||||
const items = [...event.currentTarget.querySelectorAll<HTMLButtonElement>('button')];
|
||||
if (!items.length) return;
|
||||
const index = items.indexOf(document.activeElement as HTMLButtonElement);
|
||||
const next = event.key === 'Home' ? 0 : event.key === 'End' ? items.length - 1
|
||||
: (index + (event.key === 'ArrowUp' ? -1 : 1) + items.length) % items.length;
|
||||
items[next]?.focus();
|
||||
}}>
|
||||
{names.map(name => <button key={name} type="button" role="menuitemcheckbox" aria-checked={visibility[name] !== false}
|
||||
title="Toggles this toolbar" onClick={() => { close(); toggle(name, visibility[name] === false); }}>
|
||||
<span aria-hidden="true">{visibility[name] !== false ? '✓' : ''}</span>{name}
|
||||
</button>)}
|
||||
</div>, document.body)}
|
||||
</>;
|
||||
}
|
||||
Reference in New Issue
Block a user