parapoly-runtime 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -35,6 +35,25 @@ node node_modules/parapoly-runtime/cli/code3d-build.js ./my-project
|
|
|
35
35
|
| `[workspace-dir]` | 项目目录路径(含 package.json),默认当前目录 |
|
|
36
36
|
| `--no-bin` | 输出纯文本 .code3d.js(默认输出 gzip 的 .code3d.js.bin) |
|
|
37
37
|
|
|
38
|
+
## Install AI Instructions
|
|
39
|
+
|
|
40
|
+
安装 Code3D AI 编程辅助指令到当前项目(让 GitHub Copilot 等 AI 了解 Code3D API 规范):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
code3d-install-instructions
|
|
44
|
+
|
|
45
|
+
# 或通过 node 直接运行
|
|
46
|
+
node node_modules/parapoly-runtime/cli/code3d-install-instructions.js
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
或指定目标目录:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
code3d-install-instructions ./my-project
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
安装后会在项目中生成 `.github/instructions/` 目录,AI 编程助手会自动读取其中的规范。
|
|
56
|
+
|
|
38
57
|
## Uninstall
|
|
39
58
|
|
|
40
59
|
```bash
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* code3d-install-instructions CLI — 安装 Code3D AI 辅助编程指令文件
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* code3d-install-instructions [target-dir]
|
|
7
|
+
*
|
|
8
|
+
* 参数:
|
|
9
|
+
* target-dir 目标项目目录,默认为当前目录
|
|
10
|
+
*
|
|
11
|
+
* 功能:
|
|
12
|
+
* 将 Code3D 的 AI instructions 文件安装到目标项目的 .github/instructions/ 目录。
|
|
13
|
+
* 这些文件让 GitHub Copilot 等 AI 编程助手了解 Code3D 项目规范和 API,
|
|
14
|
+
* 从而在编写 .code3d.js 文件时提供更准确的代码建议。
|
|
15
|
+
*
|
|
16
|
+
* 安装的文件:
|
|
17
|
+
* .github/instructions/code3d-project-workspace.instructions.md
|
|
18
|
+
* .github/instructions/code3d.instructions.md
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require("fs");
|
|
22
|
+
const path = require("path");
|
|
23
|
+
|
|
24
|
+
const targetDir = path.resolve(process.argv[2] || ".");
|
|
25
|
+
const instructionsSource = path.join(__dirname, "..", "instructions");
|
|
26
|
+
const instructionsTarget = path.join(targetDir, ".github", "instructions");
|
|
27
|
+
|
|
28
|
+
// Validate target is a project directory
|
|
29
|
+
if (!fs.existsSync(path.join(targetDir, "package.json"))) {
|
|
30
|
+
console.error(`❌ 目标目录不是一个有效的项目(未找到 package.json): ${targetDir}`);
|
|
31
|
+
console.error(" 用法: code3d-install-instructions [project-dir]");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Validate source
|
|
36
|
+
if (!fs.existsSync(instructionsSource)) {
|
|
37
|
+
console.error("❌ instructions/ 目录未找到,npm 包可能不完整。");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Get instruction files
|
|
42
|
+
const files = fs.readdirSync(instructionsSource).filter(f => f.endsWith(".instructions.md"));
|
|
43
|
+
if (files.length === 0) {
|
|
44
|
+
console.error("❌ 未找到 .instructions.md 文件。");
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Create target directory and copy
|
|
49
|
+
fs.mkdirSync(instructionsTarget, { recursive: true });
|
|
50
|
+
|
|
51
|
+
console.log(`\n📋 安装 Code3D AI instructions 到: ${path.relative(process.cwd(), instructionsTarget) || instructionsTarget}\n`);
|
|
52
|
+
|
|
53
|
+
let installed = 0;
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
const src = path.join(instructionsSource, file);
|
|
56
|
+
const dest = path.join(instructionsTarget, file);
|
|
57
|
+
const exists = fs.existsSync(dest);
|
|
58
|
+
fs.copyFileSync(src, dest);
|
|
59
|
+
console.log(` ${exists ? "🔄" : "✅"} ${file}${exists ? " (已覆盖)" : ""}`);
|
|
60
|
+
installed++;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(`\n✅ 已安装 ${installed} 个 AI instruction 文件。`);
|
|
64
|
+
console.log(" GitHub Copilot 等 AI 编程助手将在编写 .code3d.js 时自动参考这些规范。\n");
|
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/code3d-workspace-config.json,**/package.json,**/*.code3d.js,**/.parapoly/**,**/*.part3d"
|
|
3
|
+
description: "Code3D / 国基3D / 国基CAD 项目工作区规范:项目结构、模块编写、编译流程、多色组装等。适用于所有 code3d 3D 建模项目。"
|
|
4
|
+
---
|
|
5
|
+
# Code3D Project Workspace — AI 辅助 3D 建模指南
|
|
6
|
+
|
|
7
|
+
当用户要求构建多文件 3D 项目时,按本规范组织代码和编译。
|
|
8
|
+
|
|
9
|
+
## 项目结构
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
my-project/
|
|
13
|
+
├── package.json # 项目名称/版本/入口(必须)
|
|
14
|
+
├── code3d-workspace-config.json # 编译配置(可选)
|
|
15
|
+
├── .gitignore # 忽略 dist 输出目录
|
|
16
|
+
├── main.code3d.js # 入口文件
|
|
17
|
+
├── .parapoly/ # 运行时工具(由模板提供)
|
|
18
|
+
│ └── parapoly-runtime/ # parapoly-runtime npm 包
|
|
19
|
+
│ ├── parapoly_runtime.js
|
|
20
|
+
│ ├── parapoly_runtime.d.ts
|
|
21
|
+
│ ├── cli/
|
|
22
|
+
│ │ └── code3d-build.js # 编译 CLI
|
|
23
|
+
│ ├── package.json
|
|
24
|
+
│ └── README.md
|
|
25
|
+
├── parts/ # 子模块
|
|
26
|
+
│ ├── wall.code3d.js
|
|
27
|
+
│ └── details/
|
|
28
|
+
│ └── window.code3d.js
|
|
29
|
+
├── assets/ # .part3d 资源文件
|
|
30
|
+
│ └── model.part3d
|
|
31
|
+
└── dist/ # 编译输出(只生成一个文件)
|
|
32
|
+
└── project-name.code3d.js.bin
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 配置文件
|
|
36
|
+
|
|
37
|
+
### package.json(必须)
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"name": "project-name",
|
|
42
|
+
"version": "1.0.0",
|
|
43
|
+
"main": "main.code3d.js"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### code3d-workspace-config.json(可选,编译设置)
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"build": {
|
|
52
|
+
"output": "dist",
|
|
53
|
+
"bin": true
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 模块编写规范
|
|
59
|
+
|
|
60
|
+
每个 `.code3d.js` 必须:
|
|
61
|
+
1. 定义 `let main = function(params) { ... }`
|
|
62
|
+
2. 返回 `PartBlock3dDocument` 实例
|
|
63
|
+
3. 通过 `params` 接收参数(可选)
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
let main = function(params) {
|
|
67
|
+
let block3d_doc = new PartBlock3dDocument()
|
|
68
|
+
let width = params?.width || 10
|
|
69
|
+
let height = params?.height || 8
|
|
70
|
+
|
|
71
|
+
// 构建几何
|
|
72
|
+
block3d_doc.box("union", width, height, 0.5, "#d4a574")
|
|
73
|
+
|
|
74
|
+
// 引用子模块
|
|
75
|
+
let window = use("./details/window.code3d.js", { size: 2 })
|
|
76
|
+
block3d_doc.add_part(window, "union", "window1")
|
|
77
|
+
block3d_doc.translate(3, 5, 0.3)
|
|
78
|
+
|
|
79
|
+
return block3d_doc
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## use() 函数
|
|
84
|
+
|
|
85
|
+
### 语法
|
|
86
|
+
```javascript
|
|
87
|
+
let result = use(path, params)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- **path**: 模块路径(字符串)
|
|
91
|
+
- **params**: 可选参数对象
|
|
92
|
+
- **返回值**: `PartBlock3dDocument` 实例
|
|
93
|
+
|
|
94
|
+
### 路径格式
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
// 相对路径
|
|
98
|
+
let wall = use("./parts/wall.code3d.js", { width: 10 })
|
|
99
|
+
|
|
100
|
+
// 绝对路径
|
|
101
|
+
let shell = use("E:/models/part.part3d", { recompile: true })
|
|
102
|
+
|
|
103
|
+
// HTTP/HTTPS 远程文件
|
|
104
|
+
let gun = use("https://poly.keepwork.com/parapoly-projects/block3d/1003_水枪.code3d.js")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### .part3d 参数
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
use("./model.part3d") // 使用预编译数据
|
|
111
|
+
use("./model.part3d", { recompile: true }) // 强制重新编译
|
|
112
|
+
use("./model.part3d", { color: "#ff0000" }) // 覆盖颜色
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## add_part() — 组合子文档
|
|
116
|
+
|
|
117
|
+
将 `use()` 返回的文档组合到当前文档:
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
let child = use("./child.code3d.js", { size: 5 })
|
|
121
|
+
block3d_doc.add_part(child, "union", "child_name")
|
|
122
|
+
block3d_doc.translate(x, y, z) // 定位
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
参数:`add_part(doc, boolOp, nodeName, color?)`
|
|
126
|
+
- `doc`: use() 返回的 PartBlock3dDocument
|
|
127
|
+
- `boolOp`: `"union"` | `"difference"` | `"intersection"`
|
|
128
|
+
- `nodeName`: 节点名(字符串,在当前文档内唯一)
|
|
129
|
+
- `color`: 可选覆盖颜色
|
|
130
|
+
|
|
131
|
+
## 编译
|
|
132
|
+
|
|
133
|
+
### 命令
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# 在项目目录运行(默认输出 .code3d.js.bin)
|
|
137
|
+
node .parapoly/parapoly-runtime/cli/code3d-build.js
|
|
138
|
+
|
|
139
|
+
# 指定项目目录
|
|
140
|
+
node .parapoly/parapoly-runtime/cli/code3d-build.js ./my-project
|
|
141
|
+
|
|
142
|
+
# 强制输出纯文本 .code3d.js
|
|
143
|
+
node .parapoly/parapoly-runtime/cli/code3d-build.js --no-bin
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 编译流程
|
|
147
|
+
|
|
148
|
+
1. 读取 `package.json` 和 `code3d-workspace-config.json`
|
|
149
|
+
2. 收集所有 `.code3d.js` 和 `.part3d` 文件
|
|
150
|
+
3. **语法检查**:验证 main 函数、return 语句、JS 语法
|
|
151
|
+
4. 分析 `use()` 依赖,构建依赖图,检测循环依赖
|
|
152
|
+
5. 下载远程文件,读取绝对路径文件
|
|
153
|
+
6. 拓扑排序,生成单文件 bundle
|
|
154
|
+
7. 可选输出 `.bin`(gzip 压缩,压缩率约 85-90%)
|
|
155
|
+
|
|
156
|
+
### 编译错误定位
|
|
157
|
+
|
|
158
|
+
编译和运行时错误会显示文件名和行号:
|
|
159
|
+
```
|
|
160
|
+
✗ ./parts/wall.code3d.js (行 12): Unexpected token '}'
|
|
161
|
+
[./fence.code3d.js (行 14, 列 5)] xxxx is not defined
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## 设计拆分原则
|
|
165
|
+
|
|
166
|
+
将复杂模型拆分为多文件时遵循:
|
|
167
|
+
|
|
168
|
+
1. **按功能拆分**:每个独立零件/组件一个文件
|
|
169
|
+
2. **参数化复用**:相同形状不同尺寸通过 params 传递
|
|
170
|
+
3. **层级嵌套**:入口 → 组件 → 子部件 → 细节(最多 3-4 级)
|
|
171
|
+
4. **文件命名**:用功能描述命名,如 `wall.code3d.js`、`window.code3d.js`
|
|
172
|
+
|
|
173
|
+
### 拆分示例:房屋场景
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
main.code3d.js ← 场景入口:组合所有部件
|
|
177
|
+
├── house.code3d.js ← 房子:墙+门+屋顶
|
|
178
|
+
│ ├── parts/wall.code3d.js ← 墙体(含窗户)
|
|
179
|
+
│ │ └── parts/details/window.code3d.js
|
|
180
|
+
│ ├── parts/door.code3d.js
|
|
181
|
+
│ └── parts/roof.code3d.js
|
|
182
|
+
├── garden.code3d.js ← 花园:树木+地面
|
|
183
|
+
├── fence.code3d.js ← 围栏(参数化长度)
|
|
184
|
+
└── chimney.code3d.js ← 烟囱
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## 注意事项
|
|
188
|
+
|
|
189
|
+
- `use()` 是同步的,文件在执行前已预加载
|
|
190
|
+
- 相同 path+params 的 `use()` 调用会被缓存
|
|
191
|
+
- 注释行中的 `use()` 不会被编译器收集
|
|
192
|
+
- `.part3d` 的 UTF-8 BOM 会被自动剥离
|
|
193
|
+
- 远程文件编译时下载嵌入,运行时无需网络
|
|
194
|
+
- 编辑器打开 `.bin` 文件会自动解压加载
|
|
195
|
+
|
|
196
|
+
## 子模块构建方向与组装
|
|
197
|
+
|
|
198
|
+
子模块应在自身坐标系中沿正方向构建,由调用方通过 `rotate` + `translate` 定位到正确位置。
|
|
199
|
+
|
|
200
|
+
### 关键原则
|
|
201
|
+
|
|
202
|
+
1. **子模块沿正轴构建**:手柄、支架等长条形部件从 x=0 向 +X 延伸,由父模块旋转到实际朝向
|
|
203
|
+
2. **凹/凸方向注意 Y 轴**:碗/锅等形状,凹面(开口)应朝 +Y(上方)。椭球切割时保留下半球得到碗形
|
|
204
|
+
3. **rotate 在 translate 之前**:先旋转确定朝向,再平移到位
|
|
205
|
+
4. **边缘重叠避免缝隙**:torus 做锅沿/轮圈时,应与主体有 0.1~0.2 的重叠,避免视觉上的缝隙
|
|
206
|
+
|
|
207
|
+
### 示例:平底锅手柄组装
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
// 手柄模块沿 +X 构建(x=0→x=18)
|
|
211
|
+
// 父模块旋转 180° 使其朝 -X,再平移到锅体边缘
|
|
212
|
+
let handle = use("./parts/handle.code3d.js", { length: 18 })
|
|
213
|
+
block3d_doc.add_part(handle, "union", "handle")
|
|
214
|
+
block3d_doc.rotate("y", 180) // 翻转到 -X 方向
|
|
215
|
+
block3d_doc.translate(-13, 5.5, 0) // 贴合锅体左侧边缘
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 示例:碗/锅形(椭球切割)
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
// 椭球切割:保留下半球 → 凹面朝上
|
|
222
|
+
block3d_doc.ellipsoid("union", r, depth, r, color)
|
|
223
|
+
// 用 box 切掉上半部分(box 中心放在上方)
|
|
224
|
+
block3d_doc.box("difference", d+2, d+2, d+2, color)
|
|
225
|
+
block3d_doc.translate(0, depth, 0) // box 覆盖 y=0 以上区域
|
|
226
|
+
// 整体上移使锅底在 y=0
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## 多色组装模式
|
|
230
|
+
|
|
231
|
+
当一个模型需要多个颜色时,必须使用 `booleanEnabled=false` 外层包裹。
|
|
232
|
+
|
|
233
|
+
### 原理
|
|
234
|
+
|
|
235
|
+
- `booleanEnabled=true`:子节点合并为一个实体,**组颜色覆盖子节点颜色**
|
|
236
|
+
- `booleanEnabled=false`:子节点保持独立,**各自保留自己的颜色**
|
|
237
|
+
|
|
238
|
+
### 子模块多色模板
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
let main = function(params) {
|
|
242
|
+
let block3d_doc = new PartBlock3dDocument()
|
|
243
|
+
|
|
244
|
+
// 外层:booleanEnabled=false 保留各部件颜色
|
|
245
|
+
block3d_doc.push_node("union", "assembly", "#主色", false)
|
|
246
|
+
|
|
247
|
+
// 部件A(红色)— 内部 boolean 合并
|
|
248
|
+
block3d_doc.push_node("union", "", "#ff0000", true)
|
|
249
|
+
block3d_doc.box("union", 10, 5, 10, "#ff0000")
|
|
250
|
+
block3d_doc.pop_node()
|
|
251
|
+
block3d_doc.translate(0, 0, 0)
|
|
252
|
+
|
|
253
|
+
// 部件B(蓝色)
|
|
254
|
+
block3d_doc.push_node("union", "", "#0000ff", true)
|
|
255
|
+
block3d_doc.sphere("union", 3, "#0000ff")
|
|
256
|
+
block3d_doc.pop_node()
|
|
257
|
+
block3d_doc.translate(0, 5, 0)
|
|
258
|
+
|
|
259
|
+
block3d_doc.pop_node()
|
|
260
|
+
return block3d_doc
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 关键规则
|
|
265
|
+
|
|
266
|
+
1. **子模块必须有 `booleanEnabled=false` 外层** — 这是 `add_part` 识别多色的信号
|
|
267
|
+
2. **入口文件不需要外层包裹** — 根文档本身不会被 `run_node`
|
|
268
|
+
3. **需要 boolean 运算的部件放在 `booleanEnabled=true` 的内层 group**
|
|
269
|
+
4. **每个颜色一个 `push_node(..., true)` group** — 同色部件可合并到一个 group
|
|
270
|
+
|
|
271
|
+
### 禁止:嵌套 booleanEnabled=true group 做差集
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
// ❌ 错误 — 嵌套的 difference group transform 可能不生效
|
|
275
|
+
block3d_doc.push_node("union", "", "#333", true)
|
|
276
|
+
block3d_doc.box("union", 10, 10, 10, "#333")
|
|
277
|
+
block3d_doc.push_node("difference", "", "#333", true) // 嵌套!
|
|
278
|
+
block3d_doc.sphere("union", 4, "#333")
|
|
279
|
+
block3d_doc.pop_node()
|
|
280
|
+
block3d_doc.pop_node()
|
|
281
|
+
|
|
282
|
+
// ✅ 正确 — 同一 group 内直接用 difference 原语
|
|
283
|
+
block3d_doc.push_node("union", "", "#333", true)
|
|
284
|
+
block3d_doc.box("union", 10, 10, 10, "#333")
|
|
285
|
+
block3d_doc.sphere("difference", 4, "#333")
|
|
286
|
+
block3d_doc.translate(0, 0, 0) // 定位减法体
|
|
287
|
+
block3d_doc.pop_node()
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### difference 必须与被减实体在同一 booleanEnabled=true 组
|
|
291
|
+
|
|
292
|
+
在 `booleanEnabled=false` 外层下,每个内层 `booleanEnabled=true` group 是独立执行的。
|
|
293
|
+
**difference 只在自己所在的 group 内生效**。如果把 difference 放在单独的 inner group 里,它不会从相邻 group 中减去材料。
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
// ❌ 错误 — 挂孔在单独的 inner group 里,difference 不会执行
|
|
297
|
+
block3d_doc.push_node("union", "parts", "#333", false)
|
|
298
|
+
block3d_doc.push_node("union", "", "#333", true) // group 1: 手柄
|
|
299
|
+
block3d_doc.box("union", 16, 1.2, 2.8, "#333")
|
|
300
|
+
block3d_doc.pop_node()
|
|
301
|
+
block3d_doc.translate(8, 0, 0)
|
|
302
|
+
block3d_doc.push_node("union", "", "#333", true) // group 2: 挂孔
|
|
303
|
+
block3d_doc.cylinder("union", 2.2, 1.2, "#333")
|
|
304
|
+
block3d_doc.cylinder("difference", 1.0, 1.4, "#333") // 不会生效!
|
|
305
|
+
block3d_doc.pop_node()
|
|
306
|
+
block3d_doc.translate(15, 0, 0)
|
|
307
|
+
block3d_doc.pop_node()
|
|
308
|
+
|
|
309
|
+
// ✅ 正确 — 所有需要布尔运算的几何体放在同一个 group
|
|
310
|
+
block3d_doc.push_node("union", "parts", "#333", false)
|
|
311
|
+
block3d_doc.push_node("union", "", "#333", true) // 全部放一起
|
|
312
|
+
block3d_doc.box("union", 16, 1.2, 2.8, "#333")
|
|
313
|
+
block3d_doc.translate(8, 0, 0)
|
|
314
|
+
block3d_doc.cylinder("union", 2.2, 1.2, "#333")
|
|
315
|
+
block3d_doc.translate(15, 0, 0)
|
|
316
|
+
block3d_doc.cylinder("difference", 1.0, 1.4, "#333")
|
|
317
|
+
block3d_doc.translate(15, 0, 0)
|
|
318
|
+
block3d_doc.pop_node()
|
|
319
|
+
block3d_doc.pop_node()
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### 单色子模块也必须有 booleanEnabled=false 外层
|
|
323
|
+
|
|
324
|
+
即使子模块只有一种颜色,也**必须**使用 `booleanEnabled=false` 外层。
|
|
325
|
+
否则 `add_part` 可能无法正确处理颜色,导致部件显示为白色。
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
// ❌ 错误 — 直接用 booleanEnabled=true 作为顶层
|
|
329
|
+
block3d_doc.push_node("union", "part", "#333", true)
|
|
330
|
+
block3d_doc.box("union", 4, 1.2, 3.5, "#333")
|
|
331
|
+
block3d_doc.pop_node()
|
|
332
|
+
|
|
333
|
+
// ✅ 正确 — 即使单色也包裹 false 外层
|
|
334
|
+
block3d_doc.push_node("union", "part", "#333", false)
|
|
335
|
+
block3d_doc.push_node("union", "", "#333", true)
|
|
336
|
+
block3d_doc.box("union", 4, 1.2, 3.5, "#333")
|
|
337
|
+
block3d_doc.pop_node()
|
|
338
|
+
block3d_doc.pop_node()
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### 部件之间必须有几何重叠
|
|
342
|
+
|
|
343
|
+
`add_part` 组装时,各部件之间必须有 **至少 0.5~1cm** 的几何重叠,否则会出现视觉缝隙。
|
|
344
|
+
同一个 `booleanEnabled=true` 组内的 union 原语也必须重叠才能合并为一个整体。
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
// ❌ 错误 — box 和 cylinder 不重叠(box 到 x=2, cylinder 从 x=2.25 开始)
|
|
348
|
+
block3d_doc.box("union", 4, 1.2, 3.5, "#333") // x: -2 ~ +2
|
|
349
|
+
block3d_doc.cylinder("union", 1.75, 1.2, "#333")
|
|
350
|
+
block3d_doc.translate(4, 0, 0) // center at x=4, edge at x=2.25
|
|
351
|
+
|
|
352
|
+
// ✅ 正确 — cylinder 中心在 box 边缘处,产生充分重叠
|
|
353
|
+
block3d_doc.box("union", 4, 1.2, 3.5, "#333") // x: -2 ~ +2
|
|
354
|
+
block3d_doc.cylinder("union", 1.75, 1.2, "#333")
|
|
355
|
+
block3d_doc.translate(2, 0, 0) // center at x=2, edge at x=0.25, 与 box 重叠 1.75
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### 手柄与锅体 translate 定位要点
|
|
359
|
+
|
|
360
|
+
- 计算锅体在手柄高度处的**实际半径**(椭球/锥体随高度变化)
|
|
361
|
+
- 手柄 translate 的 X 值应使连接段**嵌入锅壁 1~2cm**
|
|
362
|
+
- 手柄 Y 值应与**锅沿高度**对齐(不是锅体中心)
|
|
363
|
+
- torus 锅沿位置要与椭球切割后的**实际顶边对齐**(注意切割 box 的尺寸影响顶边位置)
|
|
364
|
+
|
|
365
|
+
## 空心壳体构造
|
|
366
|
+
|
|
367
|
+
### 椭球挖空模式
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
block3d_doc.push_node("union", "", "#333", true)
|
|
371
|
+
// 外壳椭球
|
|
372
|
+
block3d_doc.ellipsoid("union", r, ry_outer, r, "#333")
|
|
373
|
+
// 切顶(保留下半碗形)
|
|
374
|
+
block3d_doc.box("difference", d+2, d+2, d+2, "#333")
|
|
375
|
+
block3d_doc.translate(0, ry_outer, 0)
|
|
376
|
+
// 内腔椭球(直接 difference,不要嵌套 group)
|
|
377
|
+
block3d_doc.ellipsoid("difference", r - wall, ry_inner, r - wall, "#333")
|
|
378
|
+
block3d_doc.translate(0, -wall, 0)
|
|
379
|
+
block3d_doc.pop_node()
|
|
380
|
+
block3d_doc.translate(0, ry_outer, 0) // 整体上移
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### 内腔尺寸规则(防止穿底)
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
外壳 Y 半径: ry_outer = depth + 1
|
|
387
|
+
内腔 Y 半径: ry_inner = depth + 0.2 (≤ ry_outer - 0.8)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**核心约束**:`ry_inner` 必须比 `ry_outer` 小至少 0.8,否则内腔底部会穿透外壳。
|
|
391
|
+
|
|
392
|
+
计算验证:
|
|
393
|
+
- 外壳底部 y_bottom = -ry_outer
|
|
394
|
+
- 内腔中心在 y = -wall,底部 = -wall - ry_inner
|
|
395
|
+
- 底部壁厚 = (-wall - ry_inner) - (-ry_outer) = ry_outer - wall - ry_inner
|
|
396
|
+
- 例: ry_outer=5.5, wall=0.3, ry_inner=4.7 → 底部壁厚 = 5.5 - 0.3 - 4.7 = 0.5cm ✓
|
|
397
|
+
|
|
398
|
+
## 编译配置详细说明
|
|
399
|
+
|
|
400
|
+
```json
|
|
401
|
+
{
|
|
402
|
+
"name": "project-name", // 项目名称,也是默认输出文件名
|
|
403
|
+
"version": "1.0.0",
|
|
404
|
+
"main": "main.code3d.js", // 入口文件
|
|
405
|
+
"build": {
|
|
406
|
+
"output": "dist", // 输出文件夹,默认 "dist"
|
|
407
|
+
"filename": "custom", // 可选,覆盖输出文件名(不含扩展名)
|
|
408
|
+
"bin": false // 可选,false 输出 .js;默认 true 输出 .js.bin
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
输出文件名规则:
|
|
414
|
+
- 基础名 = `build.filename` || `name`
|
|
415
|
+
- 自动检测是否已含 `.code3d`,避免重复
|
|
416
|
+
- bin 模式: `{基础名}.code3d.js.bin`(gzip 压缩)
|
|
417
|
+
- js 模式: `{基础名}.code3d.js`(纯文本)
|
|
418
|
+
|
|
419
|
+
## 编译工具位置
|
|
420
|
+
|
|
421
|
+
```
|
|
422
|
+
packages/parapoly-runtime/cli/code3d-build.js # Node.js CLI 源码
|
|
423
|
+
packages/parapoly-runtime/src/builder/code3dProjectBuilder.ts # 浏览器/共享模块
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## 项目模板位置
|
|
427
|
+
|
|
428
|
+
```
|
|
429
|
+
packages/parapoly-ai-to-3d/code3d-workspace-template/ # 项目模板(新建项目的基础)
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
模板内容:
|
|
433
|
+
- `package.json` — 项目名称/版本/入口
|
|
434
|
+
- `code3d-workspace-config.json` — 编译配置
|
|
435
|
+
- `main.code3d.js` — 空入口文件
|
|
436
|
+
- `.gitignore` — 忽略 dist/
|
|
437
|
+
- `.parapoly/parapoly-runtime/` — 编译工具(由 parapoly-runtime 的 `npm run build` 自动同步)
|
|
438
|
+
- `README.md` — 使用说明
|
|
439
|
+
|
|
440
|
+
## 新建 code3d 项目
|
|
441
|
+
|
|
442
|
+
新建项目时,必须将模板的 `.parapoly/` 目录拷贝到新项目中,确保项目可独立编译。
|
|
443
|
+
|
|
444
|
+
步骤:
|
|
445
|
+
1. 创建项目目录
|
|
446
|
+
2. 拷贝 `code3d-workspace-template/.parapoly/` 到新项目的 `.parapoly/`
|
|
447
|
+
3. 创建 `package.json`(修改 name)
|
|
448
|
+
4. 创建 `code3d-workspace-config.json`(编译配置)
|
|
449
|
+
5. 创建 `main.code3d.js` 入口文件
|
|
450
|
+
6. 创建 `.gitignore`(忽略 dist/)
|
|
451
|
+
|
|
452
|
+
`.parapoly/` 目录包含:
|
|
453
|
+
- `parapoly-runtime/parapoly_runtime.js` — UMD 运行时库
|
|
454
|
+
- `parapoly-runtime/parapoly_runtime.d.ts` — 类型声明
|
|
455
|
+
- `parapoly-runtime/cli/code3d-build.js` — 编译 CLI 工具
|
|
456
|
+
- `parapoly-runtime/package.json`
|
|
457
|
+
- `parapoly-runtime/README.md`
|
|
458
|
+
|
|
459
|
+
编译命令:
|
|
460
|
+
```bash
|
|
461
|
+
node .parapoly/parapoly-runtime/cli/code3d-build.js
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
如果已全局 link(`npm link`),也可直接:
|
|
465
|
+
```bash
|
|
466
|
+
code3d-build
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## 示例项目位置
|
|
470
|
+
|
|
471
|
+
```
|
|
472
|
+
packages/parapoly-ai-to-3d/code3d-project-workspace-example/
|
|
473
|
+
packages/parapoly-ai-to-3d/project-templates/平底锅/
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## 参考范例库
|
|
477
|
+
|
|
478
|
+
创建国基3D项目时,应搜索并参考以下目录中的 `.code3d.js` 和 `.part3d` 示例代码,学习建模手法和模式:
|
|
479
|
+
|
|
480
|
+
```
|
|
481
|
+
packages/parapoly-editor/public/parapoly-projects/
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
该目录包含大量已验证的 code3d 模型,涵盖各种形状、组装方式和参数化技巧。创建新模型时优先在此目录中搜索相似物体的实现作为参考。
|
|
485
|
+
|
|
486
|
+
### 从 .part3d 文件提取设计参数
|
|
487
|
+
|
|
488
|
+
.part3d 文件中保存了完整的参数化建模历史,可从中提取精确尺寸用于 code3d 项目:
|
|
489
|
+
|
|
490
|
+
**1. 草图轮廓 → CSG 尺寸**
|
|
491
|
+
|
|
492
|
+
从 `SkGeomLineComponent` 的 `start_point` / `end_point` 提取截面轮廓坐标:
|
|
493
|
+
```
|
|
494
|
+
点 (10, 0) → (10, 30) → (20, 30) → (20, 10) → (50, 10) → (50, 0) → (10, 0)
|
|
495
|
+
```
|
|
496
|
+
表示:内径=20(r=10), 颈外径=40(r=20), 颈高=20(y:10→30), 盘外径=100(r=50), 盘厚=10(y:0→10)
|
|
497
|
+
|
|
498
|
+
**2. 旋转体 → cylinder / cone**
|
|
499
|
+
|
|
500
|
+
`FeatureRevolveComponent` 的 `value: 360` + `ax2: [[0,0,0],[0,1,0]]` 表示绕 Y 轴 360° 旋转。
|
|
501
|
+
在 code3d 中用同轴的 `cylinder` 组合等效:
|
|
502
|
+
```javascript
|
|
503
|
+
// 盘体(等效 r=50 段)
|
|
504
|
+
block3d_doc.cylinder("union", disc_r, disc_h, color)
|
|
505
|
+
// 颈部(等效 r=20 段)
|
|
506
|
+
block3d_doc.cylinder("union", neck_r, neck_h, color)
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
**3. 拉伸圆 → 螺栓孔**
|
|
510
|
+
|
|
511
|
+
`FeatureExtrudeComponent` 中 `value: 20` + `direction: [0,1,0]` 表示沿 Y 拉伸 20mm。
|
|
512
|
+
草图中多个 `SkGeomCircleComponent`(半径 5)在分布圆上均匀排列 → 在 code3d 中用循环:
|
|
513
|
+
```javascript
|
|
514
|
+
for (let i = 0; i < bolt_n; i++) {
|
|
515
|
+
let angle = (360 / bolt_n) * i
|
|
516
|
+
let rad = angle * Math.PI / 180
|
|
517
|
+
let x = Math.cos(rad) * pcd_r
|
|
518
|
+
let z = Math.sin(rad) * pcd_r
|
|
519
|
+
block3d_doc.cylinder("difference", bolt_r, depth, color)
|
|
520
|
+
block3d_doc.translate(x, y_center, z)
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
**4. 布尔差集 → difference**
|
|
525
|
+
|
|
526
|
+
`FeatureNodeBoolean` 将拉伸体从旋转体中减去。在 code3d 中直接用 `"difference"` 布尔操作。
|
|
527
|
+
|
|
528
|
+
### 建模模式映射表
|
|
529
|
+
|
|
530
|
+
| Part3D 特征 | code3d 等效 |
|
|
531
|
+
|---|---|
|
|
532
|
+
| 草图 + 旋转 360° | `cylinder` / `cone`(同轴叠加) |
|
|
533
|
+
| 草图 + 拉伸 | `box` / `cylinder`(沿方向) |
|
|
534
|
+
| 布尔差集 | 原语 `"difference"` 操作 |
|
|
535
|
+
| 均布圆上打孔 | `for` 循环 + 三角函数定位 |
|
|
536
|
+
| 阶梯截面旋转 | 多个不同半径的 `cylinder` union |
|
|
537
|
+
| 倒角/圆角 | code3d 暂不支持,可用 torus 近似边缘 |
|
|
538
|
+
|
|
539
|
+
## Part3D 编辑器与 .part3d 文件
|
|
540
|
+
|
|
541
|
+
### Part3D 编辑器概述
|
|
542
|
+
|
|
543
|
+
Part3D 是国基CAD 的参数化实体建模编辑器,采用特征树(Feature Tree)驱动的建模方式,类似 SolidWorks/FreeCAD 的 Part Design 工作台。
|
|
544
|
+
|
|
545
|
+
编辑器页面:`packages/parapoly-editor/pages/part3d.tsx`
|
|
546
|
+
核心代码:`packages/parapoly-editor/src/editors/part3d/`
|
|
547
|
+
运行时依赖:`packages/parapoly-runtime`
|
|
548
|
+
|
|
549
|
+
### .part3d 文件格式
|
|
550
|
+
|
|
551
|
+
- 文件后缀:`.part3d`
|
|
552
|
+
- 格式:JSON(Sprite 序列化树)
|
|
553
|
+
- 包含:FeatureContainer → 特征节点序列(草图、拉伸、倒角等)
|
|
554
|
+
- 每个特征节点记录完整的参数化操作历史,可重新编译生成几何体
|
|
555
|
+
|
|
556
|
+
### 特征节点类型
|
|
557
|
+
|
|
558
|
+
**基础体:**
|
|
559
|
+
- `FeatureNodeSetBox` — 长方体
|
|
560
|
+
- `FeatureNodeSetSphere` — 球体
|
|
561
|
+
- `FeatureNodeSetCylinder` — 圆柱体
|
|
562
|
+
- `FeatureNodeSetCone` — 圆锥体
|
|
563
|
+
- `FeatureNodeSetTorus` — 环形体
|
|
564
|
+
- `FeatureNodeSetEllipsoid` — 椭球体
|
|
565
|
+
- `FeatureNodeSetPrism` — 棱柱
|
|
566
|
+
- `FeatureNodeSetStep` — STEP 导入体
|
|
567
|
+
|
|
568
|
+
**草图+造型:**
|
|
569
|
+
- `SkContainerNode` — 草图容器(2D 约束草图)
|
|
570
|
+
- `FeatureNodeExtrude` — 拉伸
|
|
571
|
+
- `FeatureNodeRevolve` — 旋转
|
|
572
|
+
- `FeatureNodeSweep` — 扫掠
|
|
573
|
+
- `FeatureNodeHelix` — 螺旋
|
|
574
|
+
|
|
575
|
+
**修饰:**
|
|
576
|
+
- `FeatureNodeChamfer` — 倒角
|
|
577
|
+
- `FeatureNodeFillet` — 圆角
|
|
578
|
+
- `FeatureNodeShell` — 抽壳
|
|
579
|
+
- `FeatureNodeDraft` — 拔模
|
|
580
|
+
|
|
581
|
+
**变换与布尔:**
|
|
582
|
+
- `FeatureNodeMirror` — 镜像
|
|
583
|
+
- `FeatureNodeBoolean` — 布尔运算
|
|
584
|
+
- `FeatureNodeTransform` — 变换
|
|
585
|
+
- `FeatureNodeDelete` — 删除体
|
|
586
|
+
|
|
587
|
+
### 草图工具
|
|
588
|
+
|
|
589
|
+
草图(SkContainerNode)支持的绘制和约束工具:
|
|
590
|
+
|
|
591
|
+
**绘制:** 点、线段、圆弧、圆、B样条、矩形、多边形、腰形槽
|
|
592
|
+
**约束:** 重合、点在线上、水平、垂直、平行、垂直、相切、等长、对称、锁定
|
|
593
|
+
**尺寸:** 距离X/Y/自由、半径、直径、角度
|
|
594
|
+
**编辑:** 裁剪、分割、圆角、倒角、对称复制
|
|
595
|
+
**导入导出:** DXF、SVG
|
|
596
|
+
|
|
597
|
+
### .part3d 示例位置
|
|
598
|
+
|
|
599
|
+
```
|
|
600
|
+
packages/parapoly-editor/public/parapoly-projects/part3d/
|
|
601
|
+
├── 塑胶积木模型合集/
|
|
602
|
+
├── 小方机器人/ ← 多零件装配示例
|
|
603
|
+
│ ├── 小方上壳.part3d
|
|
604
|
+
│ ├── 小方下壳.part3d
|
|
605
|
+
│ ├── 小方中框.part3d
|
|
606
|
+
│ └── ...
|
|
607
|
+
└── 机械模型合集/
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### 在 code3d 项目中引用 .part3d
|
|
611
|
+
|
|
612
|
+
国基3D 项目可以通过 `use()` 引用 `.part3d` 文件作为子零件:
|
|
613
|
+
|
|
614
|
+
```javascript
|
|
615
|
+
// 引用本地 .part3d(使用预编译数据)
|
|
616
|
+
let part = use("./assets/零件.part3d")
|
|
617
|
+
block3d_doc.add_part(part, "union", "part_name")
|
|
618
|
+
|
|
619
|
+
// 强制重新编译(修改 .part3d 后需要)
|
|
620
|
+
let part2 = use("./assets/零件.part3d", { recompile: true })
|
|
621
|
+
|
|
622
|
+
// 覆盖颜色
|
|
623
|
+
let part3 = use("./assets/零件.part3d", { recompile: true, color: "#4488ff" })
|
|
624
|
+
|
|
625
|
+
// 绝对路径引用
|
|
626
|
+
let shell = use("E:/models/part3d/外壳.part3d", { recompile: true })
|
|
627
|
+
|
|
628
|
+
// 远程 .part3d
|
|
629
|
+
let remote = use("https://poly.keepwork.com/parapoly-projects/part3d/机械模型合集/04%20油标尺.part3d")
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### code3d 引用 .part3d 的示例项目
|
|
633
|
+
|
|
634
|
+
```
|
|
635
|
+
packages/parapoly-ai-to-3d/code3d-project-workspace-example/ ← 含 .part3d 引用注释示例
|
|
636
|
+
packages/parapoly-ai-to-3d/code3d-project-workspace-example2/ ← 小方机器人多零件装配
|
|
637
|
+
```
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/*.code3d.js"
|
|
3
|
+
---
|
|
4
|
+
# ParaPoly Code3D Modeling Guide
|
|
5
|
+
|
|
6
|
+
Generate `.code3d.js` scripts that create 3D models using the ParaPoly engine's `PartBlock3dDocument` API via constructive solid geometry (CSG) and transformation operations.
|
|
7
|
+
|
|
8
|
+
## Output Format
|
|
9
|
+
|
|
10
|
+
Every `.code3d.js` file MUST follow this structure:
|
|
11
|
+
|
|
12
|
+
```javascript
|
|
13
|
+
var object1, object2; // declare all node variable names used in push_node
|
|
14
|
+
|
|
15
|
+
let main = function(){
|
|
16
|
+
let block3d_doc = new PartBlock3dDocument()
|
|
17
|
+
// ... build geometry here ...
|
|
18
|
+
return block3d_doc
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Key rules:
|
|
23
|
+
- The file MUST define a `main` function that returns a `PartBlock3dDocument`
|
|
24
|
+
- All node names used in `push_node` must be declared as `var` at the top
|
|
25
|
+
- The function uses `let main = function(){...}` syntax (not `function main()`)
|
|
26
|
+
- Colors are CSS hex strings like `"#ff0000"`
|
|
27
|
+
|
|
28
|
+
## API Reference
|
|
29
|
+
|
|
30
|
+
### Primitive Shapes
|
|
31
|
+
|
|
32
|
+
All primitives take a boolean operation type as first parameter: `"union"`, `"difference"`, or `"intersection"`.
|
|
33
|
+
The last parameter is always a color string (typically `"#ffc658"`).
|
|
34
|
+
|
|
35
|
+
| Method | Parameters | Description |
|
|
36
|
+
|--------|-----------|-------------|
|
|
37
|
+
| `box(op, width_x, height_y, depth_z, color)` | Dimensions along each axis | Rectangular box |
|
|
38
|
+
| `sphere(op, radius, color)` | Single radius | Sphere |
|
|
39
|
+
| `cylinder(op, radius, height, color)` | Radius and height (along Y) | Cylinder |
|
|
40
|
+
| `cone(op, top_radius, bottom_radius, height, color)` | Two radii and height | Truncated cone/cone |
|
|
41
|
+
| `torus(op, outer_radius, inner_radius, color)` | Two radii | Torus/donut |
|
|
42
|
+
| `prism(op, edges, radius, height, color)` | Edge count, radius, height | Regular polygon prism |
|
|
43
|
+
| `ellipsoid(op, radius_x, radius_y, radius_z, color)` | Three radii | Ellipsoid |
|
|
44
|
+
| `wedge(op, x, y, z, color)` | Three dimensions | Wedge shape |
|
|
45
|
+
| `trapezoid(op, top_width, bottom_width, height, depth, color)` | Trapezoid profile extruded | Trapezoid |
|
|
46
|
+
|
|
47
|
+
### Boolean Operations
|
|
48
|
+
|
|
49
|
+
- `"union"` — Add/combine shapes together
|
|
50
|
+
- `"difference"` — Subtract/cut shape from parent
|
|
51
|
+
- `"intersection"` — Keep only overlapping region
|
|
52
|
+
|
|
53
|
+
### Grouping (push_node / pop_node)
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
block3d_doc.push_node(op, name, color, booleanEnabled)
|
|
57
|
+
// child shapes and operations go here
|
|
58
|
+
block3d_doc.pop_node()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Parameters:
|
|
62
|
+
- `op`: `"union"` | `"difference"` | `"intersection"` — how this group combines with siblings
|
|
63
|
+
- `name`: String identifier (must match a `var` declared at top), e.g. `"object1"`
|
|
64
|
+
- `color`: Hex color for this group/part, e.g. `"#ff0000"`
|
|
65
|
+
- `booleanEnabled`: `true` to execute boolean ops within the group
|
|
66
|
+
|
|
67
|
+
### Transformations
|
|
68
|
+
|
|
69
|
+
Transformations apply to the LAST created primitive or the LAST popped group:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
block3d_doc.translate(x, y, z) // Position the preceding shape/group
|
|
73
|
+
block3d_doc.rotate(axis, degrees) // axis: "x", "y", or "z"
|
|
74
|
+
block3d_doc.scale(x, y, z) // Scale factors
|
|
75
|
+
block3d_doc.mirror(op, plane, color) // plane: "x", "y", or "z"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Critical Positioning Rules**:
|
|
79
|
+
|
|
80
|
+
1. Every shape is created at its **parent group's origin** (0,0,0).
|
|
81
|
+
2. `translate(x, y, z)` moves the **immediately preceding shape or popped group** to position (x, y, z).
|
|
82
|
+
3. `rotate(axis, degrees)` rotates the **immediately preceding shape or popped group**.
|
|
83
|
+
4. Each shape's translate is independent — they do NOT accumulate.
|
|
84
|
+
5. If NO translate follows a shape/group, it stays at origin.
|
|
85
|
+
6. To rotate an entire group, place `rotate()` AFTER `pop_node()`, not inside the group.
|
|
86
|
+
|
|
87
|
+
### Edge Operations
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
block3d_doc.fillet(radius, planeType) // Round edges. planeType: "x","y","z","xy","xz","yz","xyz"
|
|
91
|
+
block3d_doc.chamfer(size, planeType) // Bevel edges
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`fillet()` and `chamfer()` follow the same rule — they apply to the **immediately preceding shape or popped group**.
|
|
95
|
+
|
|
96
|
+
### Color & Boolean in Groups
|
|
97
|
+
|
|
98
|
+
`booleanEnabled` (4th parameter of `push_node`) controls whether child shapes are merged via boolean operations:
|
|
99
|
+
|
|
100
|
+
- `booleanEnabled=true`: All children are merged into a single solid. The **group color overrides child colors**.
|
|
101
|
+
- `booleanEnabled=false`: Children stay as separate parts. Each sub-group **preserves its own color**.
|
|
102
|
+
|
|
103
|
+
**Multi-color assembly pattern** — use an outer group with `booleanEnabled=false` containing inner groups each with their own color:
|
|
104
|
+
```javascript
|
|
105
|
+
block3d_doc.push_node("union", "assembly", "#aaa", false) // outer: no merge, colors preserved
|
|
106
|
+
block3d_doc.push_node("union", '', "#ff0000", true) // red part (merged internally)
|
|
107
|
+
block3d_doc.box("union", 10, 5, 10, "#ff0000")
|
|
108
|
+
block3d_doc.pop_node()
|
|
109
|
+
block3d_doc.push_node("union", '', "#0000ff", true) // blue part (merged internally)
|
|
110
|
+
block3d_doc.sphere("union", 3, "#0000ff")
|
|
111
|
+
block3d_doc.pop_node()
|
|
112
|
+
block3d_doc.translate(0, 5, 0)
|
|
113
|
+
block3d_doc.pop_node()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Sketching & Extrude
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
block3d_doc.start_sketch(name, plane) // plane: "xy", "xz", "yz"
|
|
120
|
+
block3d_doc.line_segment(x1, y1, z1, x2, y2, z2)
|
|
121
|
+
block3d_doc.circle(x, y, z, radius)
|
|
122
|
+
block3d_doc.arc(x, y, z, radius, startAngle, endAngle, isDegree)
|
|
123
|
+
block3d_doc.end_sketch()
|
|
124
|
+
block3d_doc.extrude(op, length, color, bSolid, transform_matrix)
|
|
125
|
+
block3d_doc.revolve(op, angle, axis, color, bSolid, transform_matrix)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Coordinate System
|
|
129
|
+
|
|
130
|
+
- **Default unit: millimeters (mm)** — all dimensions, radii, and positions are in mm
|
|
131
|
+
- **Y-axis is UP**, X is left-right, Z is front-back
|
|
132
|
+
- Origin (0,0,0) is the center of the model
|
|
133
|
+
|
|
134
|
+
**All shapes are CENTERED at their translate position:**
|
|
135
|
+
- `box(op, W, H, D)` at translate(x, y, z) → extends from (x-W/2, y-H/2, z-D/2) to (x+W/2, y+H/2, z+D/2)
|
|
136
|
+
- `cylinder(op, R, H)` at translate(x, y, z) → extends from y-H/2 to y+H/2
|
|
137
|
+
- `cone(op, topR, bottomR, H)` at translate(x, y, z) → topR at y+H/2, bottomR at y-H/2
|
|
138
|
+
- `sphere(op, R)` at translate(x, y, z) → centered at (x, y, z)
|
|
139
|
+
|
|
140
|
+
**Stacking formula**: To place leg under tabletop: `leg_y = -tabletop_H/2 - leg_H/2`
|
|
141
|
+
|
|
142
|
+
## Key Patterns
|
|
143
|
+
|
|
144
|
+
### Hollow Object (Cup/Vase)
|
|
145
|
+
```javascript
|
|
146
|
+
block3d_doc.cone("union", 3.2, 2.5, 9, "#ffc658") // outer wall
|
|
147
|
+
block3d_doc.cone("difference", 3, 2.3, 9, "#ffc658") // inner hollow
|
|
148
|
+
block3d_doc.translate(0, 0.3, 0) // offset up → thick bottom
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Handle Attached to Curved Body
|
|
152
|
+
Use the **same shape as the body** for the difference cut:
|
|
153
|
+
```javascript
|
|
154
|
+
block3d_doc.push_node("union", '', "#fff", true)
|
|
155
|
+
block3d_doc.torus("union", 1.5, 0.35, "#ffc658")
|
|
156
|
+
block3d_doc.rotate("x", 90)
|
|
157
|
+
block3d_doc.pop_node()
|
|
158
|
+
block3d_doc.translate(3.5, 0, 0)
|
|
159
|
+
block3d_doc.cone("difference", 3.2, 2.5, 9, "#ffc658") // matches cup body shape exactly
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Table (Tabletop + Explicit Legs)
|
|
163
|
+
```javascript
|
|
164
|
+
block3d_doc.box("union", 20, 1.5, 12, "#ffc658") // tabletop at origin
|
|
165
|
+
// legs: cylinder height 10, centered at y=-5, top at y=0 meets tabletop
|
|
166
|
+
block3d_doc.cylinder("union", 0.8, 10, "#ffc658")
|
|
167
|
+
block3d_doc.translate(8, (-5), 4.5)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Common Pitfalls
|
|
171
|
+
|
|
172
|
+
1. **Boolean order matters**: `union` after `difference` fills the hole back in.
|
|
173
|
+
2. **Don't cap hollow objects**: No solid disk at the mouth of a cup.
|
|
174
|
+
3. **Match cut shapes to body**: Use cone to cut against cone, cylinder against cylinder — not sphere.
|
|
175
|
+
4. **Centered geometry**: Shapes don't start at base. A cylinder(r, 10) at origin spans y=-5 to y=+5.
|
|
176
|
+
5. **rotate/translate placement**: Must be placed AFTER the target shape or `pop_node()`. Placing inside a group only affects the last shape in the group, not the whole group.
|
|
177
|
+
6. **booleanEnabled and colors**: If parent group has `booleanEnabled=true`, all children merge into one color. Use `false` on the outer group when children need distinct colors.
|
|
178
|
+
7. **fillet scope**: `fillet()` rounds the immediately preceding shape/group. Place it right after `pop_node()` or the target primitive.
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "parapoly-runtime",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "ParaPoly 3D CAD Runtime — CSG modeling, sketch, and project builder",
|
|
5
5
|
"main": "./parapoly_runtime.js",
|
|
6
6
|
"types": "./parapoly_runtime.d.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"code3d-build": "./cli/code3d-build.js"
|
|
8
|
+
"code3d-build": "./cli/code3d-build.js",
|
|
9
|
+
"code3d-install-instructions": "./cli/code3d-install-instructions.js"
|
|
9
10
|
},
|
|
10
11
|
"keywords": [
|
|
11
12
|
"3d",
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
"parapoly_runtime.js",
|
|
20
21
|
"parapoly_runtime.d.ts",
|
|
21
22
|
"cli/",
|
|
23
|
+
"instructions/",
|
|
22
24
|
"README.md"
|
|
23
25
|
]
|
|
24
26
|
}
|