Loading...
Loading...
Convert 2D orthographic wireframe PNG drawings to 3D Blender models exported as glTF/GLB. Use this skill whenever the user provides wireframe images (technical drawings, line drawings, orthographic views, side/front/back panels) and wants to generate a 3D model, mesh, or .glb file. Triggers on phrases like "convert this wireframe to 3D", "make a 3D model from these drawings", "build a model from this wireframe", "generate GLB from these views", or any image-to-3D-mesh request involving line drawings. Make sure to use this skill even if the user does not explicitly say "wireframe" — also covers "orthographic views", "technical drawings", "line drawings of objects", "front and side views". Requires the Blender MCP addon to be running (port 9876) and Python with opencv-python, numpy, scipy installed.
npx skill4agent add roble3/cc-blender-skill wireframe-to-3d.glbscripts/wireframe_analyzer.pymcp__blender__execute_blender_codescripts/wireframe_analyzer.pymcp__blender__get_scene_info"Blender's MCP addon isn't running. Start Blender, enable the BlenderMCP addon (port 9876), then re-run."
python3 -c "import cv2, numpy, scipy" 2>&1pip install opencv-python numpy scipy Pillowpreviewproductionhighwiresbevel_depthsurfaceshybridpython3 ${CLAUDE_SKILL_DIR}/scripts/wireframe_analyzer.py <input.png> <output.json>{
"metadata": {"image_size": [W, H], "num_contours": N, "parameters": {...}},
"contours": [[[x, y], ...], ...],
"bezier_curves": [[[[P0], [P1], [P2], [P3]], ...], ...]
}--rdp-epsilonReadexecute_blender_codebpy.dataimport bpy
# Identify by stable name; bpy.data persists between calls.
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)
# Control points come from the analyzer JSON (px → mm scaling done client-side).
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 stdoutnorm_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.0import 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)}")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)MAT-lens-mirror(0.05, 0.08, 0.15)MAT-pad-silicone(0.65, 0.63, 0.60)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 > 15mcp__blender__get_scene_infomcp__blender__get_object_infoget_object_info| Symptom | Likely cause | Fix |
|---|---|---|
| Bad Python in generated code | Re-emit code in smaller chunks; trace the line from the error message |
| Addon not running | Tell the user to start Blender + addon |
| Code chunk too large or slow | Break into smaller |
| Variables undefined across calls | Each call gets a fresh namespace | Re-import modules; refer to objects by |
| Analyzer outputs 0 contours | Image too low contrast | Re-run with |
| Asymmetric lenses | Original drawing asymmetric, or contour detection inconsistent | Warn the user; do not auto-mirror unless asked |
| GLB too large | High poly count or embedded textures | Apply |
| Mesh has holes | Curve resolution too low | Raise |
| Material missing in GLB | Used non-Principled-BSDF nodes | Rebuild material using only Principled BSDF |
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)}")✓ Exported(2.4 MB)/tmp/wireframe_output.glb
Triangles: 5 200 (3 parts: GEO-frame, GEO-lens-right, GEO-lens-left)
Materials: MAT-frame-metal, MAT-lens-mirror
Warnings: none
references/algorithms.mdreferences/blender-patterns.mdreferences/best-practices.mdforeach_set.binview_type=auto-detectdetail_level=productiongeometry_type=hybridworld_width_mm=auto