AgentSkillsCN

threedee-parametric

在threedee设计环境中创建和修改3D参数化模型。当:(1) 创建新的3D对象、几何体或网格,(2) 修改尺寸、颜色、材料等参数,(3) 添加基本体(盒子、球体、圆柱体、环面结),(4) 设置灯光或相机,(5) 导出模型到STL/OBJ/glTF,(6) 在该项目中使用Three.js代码时使用。在涉及3D建模、类CAD操作、参数化设计或网格创建的请求时触发。

SKILL.md
--- frontmatter
name: threedee-parametric
description: Create and modify 3D parametric models in the threedee design environment. Use when: (1) creating new 3D objects, geometries, or meshes, (2) modifying parameters like dimensions, colors, materials, (3) adding primitives (boxes, spheres, cylinders, torus knots), (4) setting up lighting or camera, (5) exporting models to STL/OBJ/glTF, (6) working with Three.js code in this project. Triggers on requests involving 3D modeling, CAD-like operations, parametric design, or mesh creation.

threedee Parametric Design

A live-updating 3D viewport for parametric modeling with AI assistance. Edit code in VS Code, see results instantly in the browser.

Project Structure

code
threedee/
├── src/
│   ├── main.js          # Main scene, camera, lighting, model loader
│   └── exporters.js     # STL/OBJ/glTF export utilities
├── user/
│   ├── models/          # User's parametric model definitions (auto-discovered!)
│   └── exports/         # Generated mesh files (STL, OBJ, glTF)
└── index.html           # Viewport UI with model selector

Creating New Models (Recommended Workflow)

Models are automatically discovered! Simply create a new .js file in user/models/ and it will appear in the UI dropdown after a page refresh.

Step 1: Create a Model File

Create a new file in user/models/ with a descriptive kebab-case name (e.g., my-bracket.js). The filename becomes the model ID and is converted to Title Case in the UI.

Step 2: Export Required Functions

Every model file must export a createMesh() function:

javascript
// user/models/my-bracket.js
import * as THREE from 'three';

export const parameters = {
  width: 40,
  height: 20,
  thickness: 5,
  color: 0x4a90d9,
};

export function createMesh(userParams = {}) {
  const params = { ...parameters, ...userParams };
  
  const geometry = new THREE.BoxGeometry(
    params.width, 
    params.height, 
    params.thickness
  );
  const material = new THREE.MeshStandardMaterial({ color: params.color });
  const mesh = new THREE.Mesh(geometry, material);
  
  mesh.castShadow = true;
  mesh.receiveShadow = true;
  mesh.position.y = params.height / 2; // Sit on ground plane
  
  return mesh;
}

Step 3: Save and View

  1. Save the file
  2. The browser will hot-reload (or refresh if needed)
  3. Select your new model from the MODEL dropdown in the top-left
  4. Export when ready using the EXPORT MODEL panel

Quick Reference

Basic Primitives

javascript
const geometry = new THREE.BoxGeometry(2, 1, 3);           // width, height, depth
const geometry = new THREE.SphereGeometry(1, 32, 32);      // radius, segments
const geometry = new THREE.CylinderGeometry(1, 1, 2, 32);  // topR, bottomR, height
const geometry = new THREE.TorusGeometry(1, 0.4, 16, 100); // radius, tube, segments
const geometry = new THREE.TorusKnotGeometry(1, 0.35, 128, 32);

Common Modifications

Change object color:

javascript
material.color.setHex(0x22d3ee);  // cyan accent

Position object:

javascript
mesh.position.y = 1.5;  // lift above ground
mesh.position.set(0, 1.5, 0);  // x, y, z

Scale object:

javascript
mesh.scale.set(2, 1, 1);  // stretch in x

Rotate object:

javascript
mesh.rotation.y = Math.PI / 4;  // 45 degrees

Parametric Pattern

For reusable models, always merge with default parameters to handle partial updates correctly:

javascript
export const parameters = {
  width: 40,
  height: 20,
  depth: 40,
  color: 0x4a90d9,
};

export function createGeometry(userParams = {}) {
  const params = { ...parameters, ...userParams };
  return new THREE.BoxGeometry(params.width, params.height, params.depth);
}

export function createMesh(userParams = {}) {
  const params = { ...parameters, ...userParams };
  const geometry = createGeometry(params);
  const material = new THREE.MeshStandardMaterial({ color: params.color });
  return new THREE.Mesh(geometry, material);
}

Geometry Merging Best Practices

When combining different geometry types (e.g., mixing BoxGeometry with ExtrudeGeometry), they often have incompatible index buffers which causes mergeGeometries to fail.

Always convert to non-indexed before merging to ensure compatibility:

javascript
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';

// ... create geometries ...

// Convert all to non-indexed before merging to avoid attribute/index conflicts
const compatibleGeometries = [geo1, geo2, geo3].map(g => g.toNonIndexed());
const merged = mergeGeometries(compatibleGeometries);

// Optional: Weld vertices afterwards if smooth shading is needed
// import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
// const smooth = mergeVertices(merged);

Exporting Models

Export the active 3D model to various formats using the browser console or programmatically:

Single Format Exports

javascript
// STL (3D printing)
threedeeExport.stl(mesh, "my-part")         // Binary (recommended)
threedeeExport.stlAscii(mesh, "my-part")   // ASCII text format

// General interchange
threedeeExport.obj(mesh, "my-part")        // OBJ with UVs and normals

// Web and AR
threedeeExport.gltf(mesh, "my-part")       // glTF (JSON)
threedeeExport.glb(mesh, "my-part")        // GLB (binary, compact)

// CAD interchange
threedeeExport.step(mesh, "my-part")       // STEP Faceted B-Rep

Batch Export (All Formats)

javascript
// Export to STL, OBJ, glTF, and STEP simultaneously
await threedeeExport.all(mesh, "my-part")

// Export to specific formats
await threedeeExport.all(mesh, "my-part", ['stl', 'step', 'obj'])

Export Notes

  • Files are automatically timestamped: my-part_2025-12-30_23-02-15.stl
  • Browser downloads are triggered automatically
  • Console logs confirm successful exports
  • The mesh variable is always available in the console and accessible via window.mesh

Lighting Adjustment

javascript
keyLight.intensity = 1.5;           // brighter
keyLight.position.set(10, 15, 10);  // move light
rimLight.color.setHex(0xff6600);    // orange rim

Advanced Modeling & Slicer Compatibility

When creating complex procedural shapes (like gyroids or lattices) intended for 3D printing, follow these best practices to ensure "watertight" models and high-quality exports.

Marching Cubes Best Practices

If using MarchingCubes, configure it for high resolution and clean boundaries:

  1. Polygon Budget: Always set a high maxPolyCount (e.g., 1,000,000) for high-resolution meshes to prevent model sections from being "cut off" due to buffer limits.
  2. Watertight Boundaries: Use padding in your scalar field. Never let the isosurface (boundary) sit exactly on the domain edge. Sample a slightly larger domain than the actual object size to ensure the Marching Cubes algorithm can "close" the mesh.
  3. Boundary Smoothing: When clipping shapes (e.g., using Math.max for a cylinder boundary), use a multiplier (e.g., * 5 or * 10) for the boundary distance to create a smooth gradient that the interpolator can follow.
  4. Vertex Welding: Always use mergeVertices from BufferGeometryUtils after mc.update() to weld coincident vertices. This fixes "cracks" and "open edges" that slicers often report.
javascript
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';

// ... inside createMesh ...
const mc = new MarchingCubes(resolution, material, false, false, 1000000);
// ... fill field ...
mc.update();

// Weld and recompute normals
const welded = mergeVertices(mc.geometry, 1e-5);
welded.computeVertexNormals();
mc.geometry.dispose();
mc.geometry = welded;

Ensuring Export Quality

The src/exporters.js utilities include an automated preparation step for STL exports that handles:

  • Coordinate system normalization (baking world transforms).
  • Vertex welding and normal correction.
  • Removal of degenerate (zero-area) triangles.

Workflow

Creating vs Iterating

IMPORTANT: When asked to improve, refine, or iterate on an existing model:

  • DO: Edit the existing model file directly (e.g., modify user/models/tree.js)
  • DON'T: Create new files like tree-v2.js, tree-improved.js, realistic-tree.js

Only create a new file when the user explicitly asks for a new, separate model.

Standard Workflow

  1. User requests a new 3D object → Create a new file in user/models/
  2. User requests improvements/changes → Edit the existing model file
  3. Save — the viewport hot-reloads instantly
  4. The user's selected model persists across page refreshes
  5. Export when ready via the EXPORT MODEL panel

Iteration Examples

User says: "Make the tree more realistic" Action: Edit user/models/tree.js to improve the existing code

User says: "Add more branches and better textures" Action: Edit user/models/tree.js to add the requested features

User says: "Create a pine tree (separate from the oak tree)" Action: Create a new file user/models/pine-tree.js

Console Commands

Models can also be loaded programmatically:

javascript
// Load a model by ID (filename without .js)
await loadModel('shelf-bracket');

// List all available models
console.log(modelList);

// Access current mesh
window.mesh

Important Notes

  • The viewport runs at http://localhost:3002 (or next available port)
  • Models are auto-discovered from user/models/*.js — no manual registration needed!
  • Selected model persists across page refreshes via localStorage
  • Model filenames use kebab-case (e.g., my-widget.js) and display as Title Case in the UI
  • Y-axis is up (standard Three.js convention)
  • Objects should be positioned with y > 0 to sit above the grid
  • The mesh variable is exported and accessible in console via window.mesh
  • All changes hot-reload instantly—no refresh needed
  • When iterating: edit the existing file, don't create new versions

References

For advanced geometry and CSG operations, see references/geometry.md.