wireframe-to-3d

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Wireframe-to-3D Conversion

线框转3D转换

Convert 2D orthographic wireframe images to parametric 3D Blender models, exported as glTF 2.0 binary (
.glb
).
将2D正交线框图像转换为参数化3D Blender模型,并导出为glTF 2.0二进制格式(
.glb
)。

Overview

概述

The skill drives a four-stage pipeline:
  1. Analyze wireframe images locally with
    scripts/wireframe_analyzer.py
    (OpenCV → Bezier control points in JSON).
  2. Generate Blender Python code that recreates the contours as parametric Bezier curves.
  3. Execute code in Blender via
    mcp__blender__execute_blender_code
    , converting curves to meshes with PBR materials.
  4. Export as optimized GLB (≤ 15 MB), validating size and topology.
You (Claude) are the orchestrator. The
scripts/
directory contains the only standalone code (
wireframe_analyzer.py
); everything else is patterns you emit and run via MCP.
该技能驱动一个四阶段流水线:
  1. 分析:使用本地的
    scripts/wireframe_analyzer.py
    脚本(基于OpenCV)分析线框图像,输出包含贝塞尔曲线控制点的JSON文件。
  2. 生成:生成Blender Python代码,将轮廓重新创建为参数化贝塞尔曲线。
  3. 执行:通过
    mcp__blender__execute_blender_code
    在Blender中执行代码,将曲线转换为带有PBR材质的网格。
  4. 导出:导出为优化后的GLB文件(≤15 MB),并验证文件大小和拓扑结构。
你(Claude)是该流程的协调器。
scripts/
目录包含唯一的独立代码
wireframe_analyzer.py
;其余所有内容均为你通过MCP生成并运行的代码模式。

Prerequisites — check first

先决条件——先检查

Before any wireframe work, verify the environment:
  1. Blender MCP is reachable. Call
    mcp__blender__get_scene_info
    . If it errors with "Could not connect to Blender", stop and tell the user:
    "Blender's MCP addon isn't running. Start Blender, enable the BlenderMCP addon (port 9876), then re-run."
  2. Python deps for the analyzer. Run:
    python3 -c "import cv2, numpy, scipy" 2>&1
    If it errors, run
    pip install opencv-python numpy scipy Pillow
    (or instruct the user to).
  3. Image input. Confirm the user provided at least one PNG. Reasonable bounds: ≥ 400×400 px, black-on-white or white-on-black line art.
在处理任何线框任务之前,请验证环境:
  1. Blender MCP可访问:调用
    mcp__blender__get_scene_info
    。如果报错“Could not connect to Blender”,请停止操作并告知用户:
    "Blender的MCP插件未运行。请启动Blender,启用BlenderMCP插件(端口9876),然后重新运行。"
  2. 分析器的Python依赖:运行以下命令:
    python3 -c "import cv2, numpy, scipy" 2>&1
    如果报错,请运行
    pip install opencv-python numpy scipy Pillow
    (或指导用户执行此操作)。
  3. 图像输入:确认用户提供了至少一张PNG图像。合理的图像范围:≥400×400像素,黑底白线或白底黑线的线条画。

Decision flow

决策流程

Q1: How many views?

Q1:视图数量?

  • Single view → flat 2D extrusion only (warn the user; depth must be supplied or assumed).
  • Front + side → full 3D reconstruction (silhouette × depth profile).
  • Front + side + back → use back view for symmetry validation.
  • 单视图 → 仅进行平面2D挤出(需提醒用户;必须提供或假设深度值)。
  • 正面+侧面 → 完整3D重建(轮廓×深度剖面)。
  • 正面+侧面+背面 → 使用背面视图进行对称性验证。

Q2: Detail level?

Q2:细节级别?

  • preview
    — RDP epsilon = 4.0, target ~1–2k tris, < 1 MB GLB.
  • production
    — RDP epsilon = 2.0, target ~5–8k tris, 2–4 MB GLB. Default.
  • high
    — RDP epsilon = 1.0, target ~10–20k tris, may need Decimate to stay under 15 MB.
  • preview
    — RDP epsilon=4.0,目标三角形数量约1–2k,GLB文件<1 MB。
  • production
    — RDP epsilon=2.0,目标三角形数量约5–8k,GLB文件2–4 MB。默认选项。
  • high
    — RDP epsilon=1.0,目标三角形数量约10–20k,可能需要使用Decimate(减面)工具以保持文件大小在15 MB以内。

Q3: Geometry type?

Q3:几何类型?

  • wires
    — frames, arms, hinges. Use
    bevel_depth
    on curves.
  • surfaces
    — lenses, domes. Use lofted profiles or fill caps.
  • hybrid
    — both. Default for glasses-like objects.
  • wires
    — 框架、支架、铰链。对曲线使用
    bevel_depth
    参数。
  • surfaces
    — 镜片、穹顶。使用放样剖面或填充封口。
  • hybrid
    — 同时包含上述两种类型。类似眼镜的物体默认选项。

Q4: Real-world scale?

Q4:真实世界比例?

  • If the user gave dimensions (e.g., "glasses are 140 mm wide"), use them.
  • Otherwise infer from wireframe aspect ratio and assume a sensible default (140 mm width for glasses, 180 mm for helmets, etc.). Confirm with user if not obvious.
  • 如果用户提供了尺寸(例如“眼镜宽140毫米”),请使用该尺寸。
  • 否则根据线框的宽高比推断,并假设一个合理的默认值(眼镜宽140毫米,头盔宽180毫米等)。如果不明确,请与用户确认。

Stage 1 — Run the analyzer

阶段1 — 运行分析器

Run the bundled analyzer once per view:
bash
python3 ${CLAUDE_SKILL_DIR}/scripts/wireframe_analyzer.py <input.png> <output.json>
The script outputs JSON with this shape:
json
{
  "metadata": {"image_size": [W, H], "num_contours": N, "parameters": {...}},
  "contours": [[[x, y], ...], ...],
  "bezier_curves": [[[[P0], [P1], [P2], [P3]], ...], ...]
}
Tuning RDP epsilon (only if defaults fail):
  • Output has too few/jagged contours → lower epsilon to 1.0–1.5.
  • Output has too many noisy points → raise epsilon to 3.0–4.0.
  • Pass via
    --rdp-epsilon
    (or edit the call in the script).
Read the JSON with
Read
. Do not pass huge JSON blobs to Blender — extract what you need first.
为每个视图运行一次捆绑的分析器:
bash
python3 ${CLAUDE_SKILL_DIR}/scripts/wireframe_analyzer.py <input.png> <output.json>
脚本输出的JSON结构如下:
json
{
  "metadata": {"image_size": [W, H], "num_contours": N, "parameters": {...}},
  "contours": [[[x, y], ...], ...],
  "bezier_curves": [[[[P0], [P1], [P2], [P3]], ...], ...]
}
调整RDP epsilon参数(仅在默认值失效时使用):
  • 输出轮廓过少/锯齿状 → 将epsilon降低至1.0–1.5。
  • 输出包含过多噪点 → 将epsilon提高至3.0–4.0。
  • 通过
    --rdp-epsilon
    参数传递(或编辑脚本中的调用)。
使用
Read
读取JSON文件。不要将大型JSON blob传递给Blender — 先提取所需内容。

Stage 2 — Generate Blender code

阶段2 — 生成Blender代码

Build code in small, self-contained chunks (each
execute_blender_code
call gets a fresh Python namespace; only
bpy.data
persists between calls). Always re-import what you need.
小型、独立的代码块构建代码(每次
execute_blender_code
调用都会获得一个全新的Python命名空间;只有
bpy.data
在调用之间持久化)。始终重新导入所需模块。

Pattern: create a Bezier curve from control points

模式:从控制点创建贝塞尔曲线

python
import bpy
python
import bpy

Identify by stable name; bpy.data persists between calls.

使用稳定名称标识;bpy.data在调用之间持久化。

name = 'GEO-lens-right'
curve_data = bpy.data.curves.new(name=name, type='CURVE') curve_data.dimensions = '3D' curve_data.resolution_u = 16 # tessellation resolution curve_data.bevel_depth = 0.001 # 1 mm wire thickness (adjust for surfaces) curve_data.use_fill_caps = True
obj = bpy.data.objects.new(name, curve_data) bpy.context.collection.objects.link(obj)
name = 'GEO-lens-right'
curve_data = bpy.data.curves.new(name=name, type='CURVE') curve_data.dimensions = '3D' curve_data.resolution_u = 16 # 细分分辨率 curve_data.bevel_depth = 0.001 # 1毫米线框厚度(针对曲面可调整) curve_data.use_fill_caps = True
obj = bpy.data.objects.new(name, curve_data) bpy.context.collection.objects.link(obj)

Control points come from the analyzer JSON (px → mm scaling done client-side).

控制点来自分析器的JSON文件(像素→毫米的缩放在客户端完成)。

control_points = [(0.0, 0.0, 0.0), (0.5, 1.0, 0.0), (1.5, 1.0, 0.0), (2.0, 0.0, 0.0)]
spline = curve_data.splines.new(type='BEZIER') spline.bezier_points.add(len(control_points) - 1) for i, (x, y, z) in enumerate(control_points): pt = spline.bezier_points[i] pt.co = (x, y, z) pt.handle_left_type = 'ALIGNED' # C¹ smooth pt.handle_right_type = 'ALIGNED'
print(f"created:{name}") # signal back via stdout

**Pixel → world conversion** (do this in the code you generate, before sending to Blender):
norm_x = px_x / img_width norm_y = 1.0 - (px_y / img_height) # flip Y; image origin is top-left x_world = (norm_x - 0.5) * world_width_mm / 1000.0 # to metres y_world = (norm_y - 0.5) * world_height_mm / 1000.0
undefined
control_points = [(0.0, 0.0, 0.0), (0.5, 1.0, 0.0), (1.5, 1.0, 0.0), (2.0, 0.0, 0.0)]
spline = curve_data.splines.new(type='BEZIER') spline.bezier_points.add(len(control_points) - 1) for i, (x, y, z) in enumerate(control_points): pt = spline.bezier_points[i] pt.co = (x, y, z) pt.handle_left_type = 'ALIGNED' # C¹平滑 pt.handle_right_type = 'ALIGNED'
print(f"created:{name}") # 通过标准输出返回信号

**像素→世界坐标转换**(在生成的代码中执行此操作,再发送到Blender):
norm_x = px_x / img_width norm_y = 1.0 - (px_y / img_height) # 翻转Y轴;图像原点为左上角 x_world = (norm_x - 0.5) * world_width_mm / 1000.0 # 转换为米 y_world = (norm_y - 0.5) * world_height_mm / 1000.0
undefined

Pattern: convert curves to mesh + cleanup

模式:将曲线转换为网格并清理

python
import bpy

name = 'GEO-lens-right'
obj = bpy.data.objects[name]

bpy.context.view_layer.objects.active = obj
bpy.ops.object.convert(target='MESH')

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.remove_doubles(threshold=0.0001)
bpy.ops.mesh.normals_make_consistent(inside=False)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.shade_smooth()

mesh = obj.data
print(f"mesh:{name} verts:{len(mesh.vertices)} polys:{len(mesh.polygons)}")
python
import bpy

name = 'GEO-lens-right'
obj = bpy.data.objects[name]

bpy.context.view_layer.objects.active = obj
bpy.ops.object.convert(target='MESH')

bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.remove_doubles(threshold=0.0001)
bpy.ops.mesh.normals_make_consistent(inside=False)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.shade_smooth()

mesh = obj.data
print(f"mesh:{name} verts:{len(mesh.vertices)} polys:{len(mesh.polygons)}")

Pattern: PBR material (Principled BSDF — the only shader glTF exports cleanly)

模式:PBR材质(Principled BSDF — 唯一能被glTF干净导出的着色器)

python
import bpy

mat = bpy.data.materials.get('MAT-frame-metal') or bpy.data.materials.new('MAT-frame-metal')
mat.use_nodes = True
bsdf = mat.node_tree.nodes['Principled BSDF']
bsdf.inputs['Base Color'].default_value = (0.08, 0.08, 0.10, 1.0)
bsdf.inputs['Metallic'].default_value = 1.0
bsdf.inputs['Roughness'].default_value = 0.25

obj = bpy.data.objects['GEO-frame']
if obj.data.materials:
    obj.data.materials[0] = mat
else:
    obj.data.materials.append(mat)
print('material:assigned')
Material presets (use these unless the user specifies):
  • MAT-frame-metal
    (0.08, 0.08, 0.10)
    base, metallic=1.0, roughness=0.25 (brushed steel)
  • MAT-lens-mirror
    (0.05, 0.08, 0.15)
    base, metallic=0.8, roughness=0.05, IOR=1.5 (mirror glass)
  • MAT-pad-silicone
    (0.65, 0.63, 0.60)
    base, metallic=0.0, roughness=0.7 (matte silicone)
python
import bpy

mat = bpy.data.materials.get('MAT-frame-metal') or bpy.data.materials.new('MAT-frame-metal')
mat.use_nodes = True
bsdf = mat.node_tree.nodes['Principled BSDF']
bsdf.inputs['Base Color'].default_value = (0.08, 0.08, 0.10, 1.0)
bsdf.inputs['Metallic'].default_value = 1.0
bsdf.inputs['Roughness'].default_value = 0.25

obj = bpy.data.objects['GEO-frame']
if obj.data.materials:
    obj.data.materials[0] = mat
else:
    obj.data.materials.append(mat)
print('material:assigned')
材质预设(除非用户指定,否则使用这些预设):
  • MAT-frame-metal
    — 基础色
    (0.08, 0.08, 0.10)
    ,金属度=1.0,粗糙度=0.25(拉丝钢)
  • MAT-lens-mirror
    — 基础色
    (0.05, 0.08, 0.15)
    ,金属度=0.8,粗糙度=0.05,IOR=1.5(镜面玻璃)
  • MAT-pad-silicone
    — 基础色
    (0.65, 0.63, 0.60)
    ,金属度=0.0,粗糙度=0.7(哑光硅胶)

Pattern: export to GLB

模式:导出为GLB

python
import bpy, os

filepath = '/tmp/wireframe_output.glb'
bpy.ops.export_scene.gltf(
    filepath=filepath,
    export_format='GLB',
    export_materials='EXPORT',
    export_uv=True,
    export_normals=True,
    export_animations=False,
    export_yup=True,
)
size_mb = os.path.getsize(filepath) / (1024 * 1024)
print(f"export:{filepath} size_mb:{size_mb:.2f}")
If
size_mb > 15
: apply Decimate and re-export (see error recovery).
python
import bpy, os

filepath = '/tmp/wireframe_output.glb'
bpy.ops.export_scene.gltf(
    filepath=filepath,
    export_format='GLB',
    export_materials='EXPORT',
    export_uv=True,
    export_normals=True,
    export_animations=False,
    export_yup=True,
)
size_mb = os.path.getsize(filepath) / (1024 * 1024)
print(f"export:{filepath} size_mb:{size_mb:.2f}")
如果
size_mb > 15
:应用Decimate(减面)工具并重新导出(参见错误恢复部分)。

Stage 3 — Validate

阶段3 — 验证

After the full pipeline, validate before declaring success:
  1. mcp__blender__get_scene_info
    — confirm expected objects exist.
  2. For paired parts (left/right lens), call
    mcp__blender__get_object_info
    on each and compare bounding box widths. Tolerance: 1 mm.
  3. Triangle count: get via
    get_object_info
    . If a part exceeds budget, plan Decimate.
  4. File size: must be ≤ 15 MB hard cap, ideally ≤ 8 MB.
完成整个流水线后,在宣布成功之前进行验证:
  1. mcp__blender__get_scene_info
    — 确认预期对象存在。
  2. 对于成对部件(左右镜片),调用
    mcp__blender__get_object_info
    获取每个部件的信息,并比较边界框宽度。容差:1毫米。
  3. 三角形数量:通过
    get_object_info
    获取。如果某个部件超出预算,计划使用Decimate工具。
  4. 文件大小:必须≤15 MB硬上限,理想情况下≤8 MB。

Error recovery

错误恢复

SymptomLikely causeFix
Code execution error: ...
from MCP
Bad Python in generated codeRe-emit code in smaller chunks; trace the line from the error message
Could not connect to Blender
Addon not runningTell the user to start Blender + addon
Timeout waiting for Blender response
Code chunk too large or slowBreak into smaller
execute_blender_code
calls
Variables undefined across callsEach call gets a fresh namespaceRe-import modules; refer to objects by
bpy.data.objects['name']
Analyzer outputs 0 contoursImage too low contrastRe-run with
--gaussian-kernel 7 --canny-t1 30
Asymmetric lensesOriginal drawing asymmetric, or contour detection inconsistentWarn the user; do not auto-mirror unless asked
GLB too largeHigh poly count or embedded texturesApply
DECIMATE
modifier with ratio 0.6–0.8; re-export
Mesh has holesCurve resolution too lowRaise
curve_data.resolution_u
to 24 or 32; reconvert
Material missing in GLBUsed non-Principled-BSDF nodesRebuild material using only Principled BSDF
症状可能原因修复方法
MCP返回
Code execution error: ...
生成的代码存在Python语法错误将代码拆分为更小的块重新生成;根据错误消息定位问题行
Could not connect to Blender
插件未运行告知用户启动Blender和插件
Timeout waiting for Blender response
代码块过大或运行缓慢将代码拆分为更小的
execute_blender_code
调用
调用之间变量未定义每次调用都会获得全新的命名空间重新导入模块;通过
bpy.data.objects['name']
引用对象
分析器输出0个轮廓图像对比度太低使用
--gaussian-kernel 7 --canny-t1 30
参数重新运行
镜片不对称原始图纸不对称,或轮廓检测不一致提醒用户;除非用户要求,否则不要自动镜像
GLB文件过大多边形数量过高或嵌入纹理应用
DECIMATE
修改器,比例设为0.6–0.8;重新导出
网格存在孔洞曲线分辨率太低
curve_data.resolution_u
提高到24或32;重新转换
GLB中缺少材质使用了非Principled-BSDF节点仅使用Principled BSDF重建材质

Decimate code pattern (when GLB > 15 MB)

Decimate(减面)代码模式(当GLB>15 MB时)

python
import bpy

obj = bpy.data.objects['GEO-frame']
bpy.context.view_layer.objects.active = obj

mod = obj.modifiers.new(name='Decimate', type='DECIMATE')
mod.ratio = 0.7
mod.use_collapse_degenerate = True
bpy.ops.object.modifier_apply(modifier=mod.name)
print(f"decimated:{obj.name} verts:{len(obj.data.vertices)}")
python
import bpy

obj = bpy.data.objects['GEO-frame']
bpy.context.view_layer.objects.active = obj

mod = obj.modifiers.new(name='Decimate', type='DECIMATE')
mod.ratio = 0.7
mod.use_collapse_degenerate = True
bpy.ops.object.modifier_apply(modifier=mod.name)
print(f"decimated:{obj.name} verts:{len(obj.data.vertices)}")

Output to user

向用户输出结果

When done, report:
  • Output path of the GLB file
  • File size in MB (vs 15 MB cap)
  • Triangle count per part (vs 30 000 cap)
  • Material slots assigned
  • Any warnings (asymmetry, decimation applied, fallbacks used)
Example:
✓ Exported
/tmp/wireframe_output.glb
(2.4 MB)
Triangles: 5 200 (3 parts: GEO-frame, GEO-lens-right, GEO-lens-left)
Materials: MAT-frame-metal, MAT-lens-mirror
Warnings: none
完成后,报告以下内容:
  • GLB文件的输出路径
  • 文件大小(MB)(与15 MB上限对比)
  • 每个部件的三角形数量(与30000上限对比)
  • 已分配的材质插槽
  • 任何警告(不对称、已应用减面、使用了回退方案)
示例:
✓ 已导出
/tmp/wireframe_output.glb
(2.4 MB)
三角形数量:5200(3个部件:GEO-frame、GEO-lens-right、GEO-lens-left)
材质:MAT-frame-metal、MAT-lens-mirror
警告:无

When to load deeper references

何时加载深度参考资料

The body above covers the 80% case. For the long tail, load these on demand:
  • references/algorithms.md
    — image-processing pipeline theory (Canny, RDP, least-squares Bezier fitting), 2D-to-3D reconstruction principles, ISO 128 orthographic standards. Load when the analyzer output looks wrong and you need to tune parameters.
  • references/blender-patterns.md
    — exhaustive Blender Python patterns (lofting, surface revolution, custom modifier stacks). Load when the user requests non-standard geometry (curved surfaces, complex bridges, articulated parts).
  • references/best-practices.md
    — performance optimization (
    foreach_set
    , batch ops, context caching), naming conventions (Blender Studio standards), modifier stack ordering. Load when builds are slow or output topology is poor.
以上内容覆盖了80%的常见场景。对于长尾场景,按需加载以下资料:
  • references/algorithms.md
    — 图像处理流水线理论(Canny边缘检测、RDP算法、最小二乘法贝塞尔拟合)、2D转3D重建原理、ISO 128正交标准。当分析器输出异常且需要调整参数时加载。
  • references/blender-patterns.md
    — 详尽的Blender Python代码模式(放样、曲面旋转、自定义修改器堆栈)。当用户要求非标准几何形状(曲面、复杂桥接、铰接部件)时加载。
  • references/best-practices.md
    — 性能优化(
    foreach_set
    、批量操作、上下文缓存)、命名规范(Blender Studio标准)、修改器堆栈顺序。当构建速度慢或输出拓扑结构不佳时加载。

Constraints

约束条件

  • Blender ≥ 4.0 (5.x preferred). The Principled BSDF node and glTF exporter are stable across these versions.
  • glTF embedded only (no
    .bin
    + textures sidecar; no KTX2/Draco compression — Three.js needs extra loaders we haven't vendored).
  • PNG textures only (max 1024×1024). Prefer flat PBR colours; textures only when essential.
  • No bone animations in the GLB. Idle motion is driven in JS by the consumer site.
  • Blender ≥4.0(推荐5.x版本)。Principled BSDF节点和glTF导出器在这些版本中稳定。
  • 仅支持嵌入式glTF(不支持
    .bin
    +纹理副文件;不支持KTX2/Draco压缩——Three.js需要额外的加载器,我们未提供)。
  • 仅支持PNG纹理(最大1024×1024像素)。优先使用纯色PBR材质;仅在必要时使用纹理。
  • GLB中不包含骨骼动画。静态模型的动画由消费站点的JS驱动。

Tip

提示

If the user just says "convert this wireframe", default to:
view_type=auto-detect
,
detail_level=production
,
geometry_type=hybrid
,
world_width_mm=auto
. Only ask for clarification if multiple interpretations are plausible.
如果用户仅说“转换这个线框”,默认使用:
view_type=自动检测
detail_level=production
geometry_type=hybrid
world_width_mm=自动
。只有当存在多种合理解释时,才需要向用户确认。