--- name: assembly-constraints description: Create and manage CadQuery assemblies with parts, sub-assemblies, and constraint-based positioning. --- # Assembly Constraints Skill ## Description Create and manage CadQuery assemblies with parts, sub-assemblies, and constraint-based positioning. ## Instructions You are a CadQuery assembly expert. Help users build modular, multi-part designs with proper constraints. ### Assembly Structure ```python import cadquery as cq def build(): """Assembly description.""" assy = cq.Assembly(name="assembly_name") # Add parts assy.add(part1, name="part1", color=cq.Color(0.7, 0.7, 0.7)) assy.add(part2, name="part2", color=cq.Color(0.2, 0.6, 0.9)) # Define constraints assy.constrain("part1?face_tag", "part2?face_tag", "Plane") # Solve assy.solve() return assy result = build() ``` ### Critical Rules 1. **Name all parts** - `assy.add(part, name="unique_name")` for navigation 2. **Tag mate faces** - Use `.tag("name")` on faces in part definitions 3. **Solve after constraints** - Always call `assy.solve()` before export 4. **Use config for shared params** - Import from `config.py` ### Constraint Types | Type | Use Case | Example | |------|----------|---------| | **Plane** | Faces touch and align | Bolt head on plate | | **Axis** | Centerlines align | Shaft in hole | | **Point** | Centers coincide | Ball joint | | **PointInPlane** | Point slides on surface | Slider | | **Fixed** | Lock in place | Base plate | ### Constraint Syntax ```python # Using tagged faces (preferred) assy.constrain("part1?top", "part2?bottom", "Plane") # Using face selectors assy.constrain("part1@faces@>Z", "part2@faces@Z", "part2@faces@Z").tag("top") part.faces("X").tag("side_right") part.faces("Y").tag("front") part.faces("Z` | Highest Z face (top) | | `X` | Highest X face (right) | | `Y` | Highest Y face (front) | | `Z").tag("top") plate.faces("Z").tag("top") return standoff def build(): assy = cq.Assembly(name="bracket_assy") # Add base plate assy.add(make_plate(), name="plate", color=cq.Color(0.8, 0.8, 0.8)) # Add 4 standoffs positions = [(-40, -20), (40, -20), (-40, 20), (40, 20)] for i, (x, y) in enumerate(positions): assy.add( make_standoff(), name=f"standoff_{i}", color=cq.Color(0.3, 0.3, 0.3), loc=cq.Location((x, y, 5)) # On top of plate ) assy.constrain(f"standoff_{i}?base", "plate?top", "Point") assy.solve() return assy result = build() ``` ### Troubleshooting | Problem | Solution | |---------|----------| | Solver fails | Set initial `loc=` positions closer to final | | Parts overlap | Check constraint directions (Axis param) | | Face not found | Verify tag exists, check selector syntax | | Import error | Check file path, use absolute if needed | --- ## Selection Context in Assemblies (viewer-integrated environments only) In hosts with a 3D viewer integration, clicking a part sends selection context (part name, bounding box). Without a viewer the user refers to parts by their assembly `name=` — the workflow below is identical either way: ### Modifying Selected Assembly Parts 1. **Identify by name** - Match selection to named part in assembly 2. **Modify the part builder** - Change the function that creates that part 3. **Keep other parts** - Don't alter unrelated geometry ### Example: User selects "standoff_0" and says "make this taller" ```python # BEFORE def make_standoff(height=20): standoff = cq.Workplane("XY").cylinder(height, 5) # ... # User selects standoff_0 (height ~20mm) # User says: "make this 30mm tall" # AFTER - only height parameter changed def make_standoff(height=30): # Changed from 20 to 30 standoff = cq.Workplane("XY").cylinder(height, 5) # ... ``` ### Removing Parts from Assembly ```python # BEFORE assy.add(make_plate(), name="plate") assy.add(make_standoff(), name="standoff_0") # User selected this assy.add(make_standoff(), name="standoff_1") # User says: "delete this" (standoff_0 selected) # AFTER - remove the selected part and its constraints assy.add(make_plate(), name="plate") # standoff_0 removed entirely assy.add(make_standoff(), name="standoff_1") ```