makeit4me 1.0.0
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/.claude-plugin/plugin.json +14 -0
- package/.mcp.json +8 -0
- package/index.js +223 -0
- package/package.json +30 -0
- package/skills/sketchup-3d-warehouse-wrangling/SKILL.md +221 -0
- package/skills/sketchup-bridge/SKILL.md +255 -0
- package/skills/sketchup-building-model-geometry/SKILL.md +134 -0
- package/skills/sketchup-model-auditing/SKILL.md +160 -0
- package/skills/sketchup-review-design/SKILL.md +363 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sketchup-bridge
|
|
3
|
+
description: |
|
|
4
|
+
Control SketchUp via the makeit4me MCP server. Use when:
|
|
5
|
+
(1) sending Ruby code to SketchUp, (2) reading or modifying model
|
|
6
|
+
geometry, (3) placing/moving/deleting entities, (4) debugging Ruby eval errors
|
|
7
|
+
like scoping issues, (5) diagnosing visual artifacts like z-fighting or missing
|
|
8
|
+
geometry. Covers all MCP tools, the Ruby API cheat sheet, known gotchas, and
|
|
9
|
+
the screenshot-verify workflow.
|
|
10
|
+
author: Claude Code
|
|
11
|
+
version: 3.0.0
|
|
12
|
+
date: 2026-03-15
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# SketchUp Claude Bridge (MCP)
|
|
16
|
+
|
|
17
|
+
SketchUp is controlled through the **makeit4me** MCP server. All tools target
|
|
18
|
+
the currently active/focused model. Ruby code can be multi-line and readable —
|
|
19
|
+
there is no JSON escaping or shell escaping to worry about.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## MCP Tools
|
|
24
|
+
|
|
25
|
+
### `sketchup_status`
|
|
26
|
+
Health check. Always call this first to confirm SketchUp is open and ready.
|
|
27
|
+
|
|
28
|
+
No parameters. Returns model title, path, and SketchUp version.
|
|
29
|
+
|
|
30
|
+
### `sketchup_models`
|
|
31
|
+
List all open models.
|
|
32
|
+
|
|
33
|
+
No parameters. Returns an array of models with title, path, and active flag.
|
|
34
|
+
|
|
35
|
+
### `sketchup_inspect`
|
|
36
|
+
Evaluate arbitrary Ruby in SketchUp's active model context.
|
|
37
|
+
The variable `model` is pre-bound to `Sketchup.active_model`.
|
|
38
|
+
|
|
39
|
+
**Parameters:** `code` (Ruby string)
|
|
40
|
+
|
|
41
|
+
**Use for reads and non-destructive queries.** For geometry changes, use `sketchup_operation` so they are undoable.
|
|
42
|
+
|
|
43
|
+
Example — count entities:
|
|
44
|
+
```ruby
|
|
45
|
+
model.entities.length
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Example — get model bounds:
|
|
49
|
+
```ruby
|
|
50
|
+
model.bounds.to_s
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `sketchup_operation`
|
|
54
|
+
Execute Ruby wrapped in a named undo operation.
|
|
55
|
+
**Always use this when creating, moving, or deleting geometry** so the user can Ctrl+Z.
|
|
56
|
+
|
|
57
|
+
**Parameters:** `name` (undo label), `code` (Ruby string)
|
|
58
|
+
|
|
59
|
+
Ruby code can span multiple lines freely:
|
|
60
|
+
```ruby
|
|
61
|
+
grp = model.entities.add_group
|
|
62
|
+
grp.name = "Test Box"
|
|
63
|
+
ents = grp.entities
|
|
64
|
+
pts = [
|
|
65
|
+
[0, 0, 0],
|
|
66
|
+
[10.inch, 0, 0],
|
|
67
|
+
[10.inch, 10.inch, 0],
|
|
68
|
+
[0, 10.inch, 0]
|
|
69
|
+
]
|
|
70
|
+
face = ents.add_face(pts)
|
|
71
|
+
face.pushpull(5.inch)
|
|
72
|
+
grp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `sketchup_selection`
|
|
76
|
+
Read or manipulate the active selection.
|
|
77
|
+
|
|
78
|
+
**Parameters:** `action` ("get", "clear", "set", etc.), `entities` (optional, entity references)
|
|
79
|
+
|
|
80
|
+
### `sketchup_screenshot`
|
|
81
|
+
Take a viewport screenshot. Returns the image directly — just view the result, no need to read a temp file.
|
|
82
|
+
|
|
83
|
+
**Parameters:**
|
|
84
|
+
- `eye` — camera position as [x, y, z]
|
|
85
|
+
- `target` — look-at point as [x, y, z]
|
|
86
|
+
- `orthographic` — true for parallel projection (optional)
|
|
87
|
+
- `width`, `height` — image dimensions (optional)
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Geometry Cheat Sheet (SketchUp Ruby API)
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
# Units — always use .inch / .mm / .feet / .m helpers
|
|
95
|
+
10.inch # => 10 inches in SketchUp's internal unit (inches)
|
|
96
|
+
2.4.m # => 2.4 meters
|
|
97
|
+
|
|
98
|
+
# Entities (top-level model geometry)
|
|
99
|
+
model.entities # EntityCollection
|
|
100
|
+
model.entities.add_face(pts) # pts = array of [x,y,z] or Geom::Point3d
|
|
101
|
+
model.entities.add_line(pt1, pt2)
|
|
102
|
+
model.entities.add_box(origin, w, h, d) # returns Array of 6 faces
|
|
103
|
+
model.entities.add_group # returns Sketchup::Group
|
|
104
|
+
|
|
105
|
+
# Faces
|
|
106
|
+
face.pushpull(dist) # AVOID for boxes — direction is non-deterministic (see build-scripts skill)
|
|
107
|
+
face.normal # Geom::Vector3d
|
|
108
|
+
face.vertices.map(&:position)
|
|
109
|
+
|
|
110
|
+
# Transform
|
|
111
|
+
t = Geom::Transformation.translation([dx, dy, dz])
|
|
112
|
+
t = Geom::Transformation.rotation(origin, axis, angle_in_radians)
|
|
113
|
+
model.entities.transform_entities(t, [entity1, entity2])
|
|
114
|
+
|
|
115
|
+
# Groups & Components
|
|
116
|
+
grp = model.entities.add_group
|
|
117
|
+
grp.name = "My Group"
|
|
118
|
+
grp.entities.add_face(...)
|
|
119
|
+
defn = model.definitions.add("MyComponent")
|
|
120
|
+
model.entities.add_instance(defn, Geom::Transformation.new)
|
|
121
|
+
|
|
122
|
+
# Materials
|
|
123
|
+
mat = model.materials.add("Red")
|
|
124
|
+
mat.color = Sketchup::Color.new(255, 0, 0)
|
|
125
|
+
face.material = mat
|
|
126
|
+
|
|
127
|
+
# Layers (Tags in newer SketchUp)
|
|
128
|
+
layer = model.layers.add("Structure")
|
|
129
|
+
entity.layer = layer
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Known Gotchas
|
|
135
|
+
|
|
136
|
+
### 1. `def` Creates Methods on Wrong Scope
|
|
137
|
+
|
|
138
|
+
**Symptom**: `undefined method 'my_helper' for MakeIt4Me:Module`
|
|
139
|
+
|
|
140
|
+
**Cause**: `def` inside eval creates methods on the bridge module, not as local functions.
|
|
141
|
+
|
|
142
|
+
**Fix**: Use lambdas instead:
|
|
143
|
+
```ruby
|
|
144
|
+
# BAD - will error
|
|
145
|
+
def make_box(origin, size)
|
|
146
|
+
# ...
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# GOOD - works in eval context
|
|
150
|
+
make_box = lambda { |origin, size|
|
|
151
|
+
# ...
|
|
152
|
+
}
|
|
153
|
+
make_box.call([0,0,0], 10)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 2. Entity Deletion Race Conditions
|
|
157
|
+
|
|
158
|
+
**Symptom**: `reference to deleted Entity` when iterating and deleting.
|
|
159
|
+
|
|
160
|
+
**Cause**: Deleting entities while iterating invalidates references.
|
|
161
|
+
|
|
162
|
+
**Fix**: Collect to array first, then batch delete:
|
|
163
|
+
```ruby
|
|
164
|
+
# BAD
|
|
165
|
+
entities.each { |e| entities.erase_entities(e) }
|
|
166
|
+
|
|
167
|
+
# GOOD
|
|
168
|
+
entities.erase_entities(entities.to_a)
|
|
169
|
+
|
|
170
|
+
# GOOD - with filter
|
|
171
|
+
to_del = entities.to_a.select { |e| e.is_a?(Sketchup::Face) }
|
|
172
|
+
entities.erase_entities(to_del) if to_del.any?
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 3. Materials Live on Groups, Not Always on Faces
|
|
176
|
+
|
|
177
|
+
**Symptom**: `face.material` returns nil even though the geometry appears textured.
|
|
178
|
+
|
|
179
|
+
**Cause**: SketchUp allows materials on groups/component instances. The group material overrides face materials visually.
|
|
180
|
+
|
|
181
|
+
**Fix**: Check both levels:
|
|
182
|
+
```ruby
|
|
183
|
+
mat = group.material
|
|
184
|
+
mat ||= group.entities.grep(Sketchup::Face).first&.material
|
|
185
|
+
# Apply to group for consistency
|
|
186
|
+
new_group.material = model.materials["Wall Wood"]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 4. Z-Fighting Between Coplanar Faces
|
|
190
|
+
|
|
191
|
+
**Symptom**: Flickering/clipping artifacts where two surfaces overlap.
|
|
192
|
+
|
|
193
|
+
**Cause**: Two faces on the exact same plane. GPU can't determine draw order.
|
|
194
|
+
|
|
195
|
+
**Fix**: Remove redundant coplanar geometry. Diagnose by checking overlaps:
|
|
196
|
+
```ruby
|
|
197
|
+
target_min = [80, 90, 0]
|
|
198
|
+
target_max = [88, 120, 80]
|
|
199
|
+
model.entities.each do |e|
|
|
200
|
+
bb = e.bounds
|
|
201
|
+
if bb.min.x <= target_max[0] && bb.max.x >= target_min[0] &&
|
|
202
|
+
bb.min.y <= target_max[1] && bb.max.y >= target_min[1] &&
|
|
203
|
+
bb.min.z <= target_max[2] && bb.max.z >= target_min[2]
|
|
204
|
+
puts "#{e.typename} #{e.respond_to?(:name) ? e.name : ''} overlaps"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 5. `add_group` from Exploded Entities
|
|
210
|
+
|
|
211
|
+
**Symptom**: `All Entities must have a common parent` when re-grouping exploded entities.
|
|
212
|
+
|
|
213
|
+
**Cause**: `explode` returns entities already parented to the current collection. References may be stale.
|
|
214
|
+
|
|
215
|
+
**Fix**: Explode inside a group context:
|
|
216
|
+
```ruby
|
|
217
|
+
grp = entities.add_group
|
|
218
|
+
inst = grp.entities.add_instance(defn, transform)
|
|
219
|
+
inst.explode # explodes into grp.entities, stays contained
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Error Handling
|
|
225
|
+
|
|
226
|
+
All MCP tools return an error field on failure with the error message and backtrace.
|
|
227
|
+
Always check that the result indicates success before using the returned value.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Workflow Pattern for Claude Code
|
|
232
|
+
|
|
233
|
+
1. **Always start with `sketchup_status`** to confirm SketchUp is running.
|
|
234
|
+
2. **Use `sketchup_inspect` for queries** (reading model state, inspecting geometry).
|
|
235
|
+
3. **Use `sketchup_operation` for all writes** (adding/moving/deleting geometry) — makes changes undoable.
|
|
236
|
+
4. **Use `sketchup_selection` to check** what the user has selected before acting on it.
|
|
237
|
+
5. **Iterate small** — make one change, verify, then continue.
|
|
238
|
+
6. **Screenshot-verify loop** — always visually confirm geometry changes using `sketchup_screenshot`:
|
|
239
|
+
- Set `eye` to a good vantage point for the geometry you changed
|
|
240
|
+
- Set `target` to the center of the area of interest
|
|
241
|
+
- The tool returns the image directly — just view it
|
|
242
|
+
7. If the user says "undo", tell them to press Ctrl+Z in SketchUp (you can't programmatically undo via the bridge).
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Additional Notes
|
|
247
|
+
|
|
248
|
+
- `Sketchup.undo` works via inspect for quick rollbacks during iteration
|
|
249
|
+
- Entity indices change after any add/delete — never cache indices across operations
|
|
250
|
+
- All SketchUp internal units are inches
|
|
251
|
+
- Use `.to_l` for human-readable output (e.g., `84.0.to_l` → `7'`)
|
|
252
|
+
|
|
253
|
+
## See Also
|
|
254
|
+
- `sketchup-building-framing`: Parametric framing with real lumber dimensions
|
|
255
|
+
- `sketchup-3d-warehouse-wrangling`: Placing/trimming 3D Warehouse components
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sketchup-building-model-geometry
|
|
3
|
+
description: |
|
|
4
|
+
Build geometry in SketchUp with visual verification at every step. Use when:
|
|
5
|
+
(1) creating boxes, walls, roofs, or any 3D shapes, (2) using pushpull or
|
|
6
|
+
extrusions that can go the wrong direction, (3) assembling multiple groups
|
|
7
|
+
into a structure, (4) any geometry creation where the result needs to look
|
|
8
|
+
correct from all angles. Enforces multi-angle screenshot verification and
|
|
9
|
+
a final model audit before declaring work complete.
|
|
10
|
+
author: MakeIt4Me
|
|
11
|
+
version: 2.0.0
|
|
12
|
+
date: 2026-03-15
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Building Model Geometry
|
|
16
|
+
|
|
17
|
+
## The Problem
|
|
18
|
+
|
|
19
|
+
When building geometry in SketchUp via the bridge, several things go wrong silently:
|
|
20
|
+
|
|
21
|
+
- **pushpull goes the wrong direction** — SketchUp's `face.pushpull` direction is non-deterministic. It depends on face normal orientation, which varies based on vertex winding order. A wall that should extrude outward extrudes inward. You can't tell from a single screenshot angle.
|
|
22
|
+
- **Groups clip into each other** — Two groups positioned at the same coordinates overlap. Looks fine from one angle, broken from another.
|
|
23
|
+
- **Geometry lands in the wrong spot** — Off-by-one in coordinate math puts a wall 3.5" too far left. Only visible from the side or top.
|
|
24
|
+
- **Faces are reversed** — The back material shows instead of the front. Invisible from most angles.
|
|
25
|
+
|
|
26
|
+
All of these look correct from at least one camera angle. The fix is to never trust a single screenshot.
|
|
27
|
+
|
|
28
|
+
## Rules
|
|
29
|
+
|
|
30
|
+
### 1. Verify after every geometry operation
|
|
31
|
+
|
|
32
|
+
After every `sketchup_operation` call that creates or moves geometry, take a screenshot with `sketchup_screenshot` and visually inspect it before moving on. Do not batch multiple operations and check once at the end.
|
|
33
|
+
|
|
34
|
+
### 2. Use multiple camera angles
|
|
35
|
+
|
|
36
|
+
Never verify from a single angle. After creating geometry, check from at least two of these views using `sketchup_screenshot`:
|
|
37
|
+
|
|
38
|
+
**Front-right isometric** (default good starting point):
|
|
39
|
+
- eye: [200, -150, 120], target: [60, 60, 40]
|
|
40
|
+
|
|
41
|
+
**Back-left isometric** (catches reversed extrusions):
|
|
42
|
+
- eye: [-100, 200, 120], target: [60, 60, 40]
|
|
43
|
+
|
|
44
|
+
**Top-down** (catches X/Y positioning errors):
|
|
45
|
+
- eye: [60, 60, 300], target: [60, 60, 0]
|
|
46
|
+
|
|
47
|
+
**Front elevation** (catches Z errors, height issues):
|
|
48
|
+
- eye: [60, -200, 60], target: [60, 0, 60]
|
|
49
|
+
|
|
50
|
+
**Side elevation** (catches depth/Y errors):
|
|
51
|
+
- eye: [-200, 60, 60], target: [0, 60, 60]
|
|
52
|
+
|
|
53
|
+
Adjust the target point to center on the geometry you just created. The eye distance should be far enough to see the full context.
|
|
54
|
+
|
|
55
|
+
**Tip:** Use `orthographic: true` for elevation views. It removes perspective distortion, making it easier to see which parts are in front of or behind others and to verify alignment.
|
|
56
|
+
|
|
57
|
+
### 3. Fix pushpull direction immediately
|
|
58
|
+
|
|
59
|
+
If a pushpull went the wrong way:
|
|
60
|
+
1. Undo it (`Sketchup.undo` via `sketchup_inspect`)
|
|
61
|
+
2. Reverse the face normal first, then pushpull again
|
|
62
|
+
3. Or use negative distance: `face.pushpull(-distance)`
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
# Check which way the normal points before pushpull
|
|
66
|
+
normal = face.normal
|
|
67
|
+
# If normal.z < 0 for a floor face, reverse it first
|
|
68
|
+
face.reverse! if normal.z < 0
|
|
69
|
+
face.pushpull(height)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 4. Name every group
|
|
73
|
+
|
|
74
|
+
Always name groups immediately after creation. This makes auditing and debugging possible:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
grp = model.entities.add_group
|
|
78
|
+
grp.name = "Front Wall"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 5. Check bounding boxes after placement
|
|
82
|
+
|
|
83
|
+
After positioning a group, verify its bounds make sense:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
bb = grp.bounds
|
|
87
|
+
"#{grp.name}: min=#{bb.min}, max=#{bb.max}, size=#{bb.width.to_l} x #{bb.height.to_l} x #{bb.depth.to_l}"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 6. Run model audit when you think you're done
|
|
91
|
+
|
|
92
|
+
Before telling the user the geometry is complete, run the `sketchup-model-auditing` checks:
|
|
93
|
+
|
|
94
|
+
- Check all groups are manifold solids (`group.manifold?`)
|
|
95
|
+
- Check for floating pieces (groups not resting on anything)
|
|
96
|
+
- Check for mesh intersections between groups
|
|
97
|
+
- Take a final multi-angle screenshot set
|
|
98
|
+
|
|
99
|
+
Do not declare work complete until the audit passes or you've explained any remaining issues to the user.
|
|
100
|
+
|
|
101
|
+
## Screenshot Workflow
|
|
102
|
+
|
|
103
|
+
Use `sketchup_screenshot` after every geometry change. The tool returns the image directly — just view the result.
|
|
104
|
+
|
|
105
|
+
**Angle 1: front-right iso**
|
|
106
|
+
- eye: [200, -150, 120], target: [60, 60, 40]
|
|
107
|
+
|
|
108
|
+
**Angle 2: back-left iso**
|
|
109
|
+
- eye: [-100, 200, 120], target: [60, 60, 40]
|
|
110
|
+
|
|
111
|
+
Then check:
|
|
112
|
+
- Does the new geometry appear where expected?
|
|
113
|
+
- Is the extrusion direction correct?
|
|
114
|
+
- Are there any visible clipping or overlap issues?
|
|
115
|
+
- Do proportions look right relative to existing geometry?
|
|
116
|
+
|
|
117
|
+
## Common Pitfalls
|
|
118
|
+
|
|
119
|
+
### pushpull is not deterministic
|
|
120
|
+
The direction depends on face normal, which depends on vertex order. Always check the result visually. If it went wrong, undo and try `face.pushpull(-distance)` or `face.reverse!` first.
|
|
121
|
+
|
|
122
|
+
### add_face vertex winding matters
|
|
123
|
+
Counter-clockwise vertices (viewed from outside) produce an outward-facing normal. Clockwise produces inward. When building walls, think about which side is "outside."
|
|
124
|
+
|
|
125
|
+
### Coordinate system
|
|
126
|
+
SketchUp uses inches internally. X = width (left/right), Y = depth (front/back), Z = height (up/down). The origin (0,0,0) is the front-left corner at ground level.
|
|
127
|
+
|
|
128
|
+
### Group transforms
|
|
129
|
+
When you add geometry inside a group, coordinates are relative to the group's local origin. If you need world coordinates, use `group.transformation` to convert.
|
|
130
|
+
|
|
131
|
+
## See Also
|
|
132
|
+
- `sketchup-bridge`: Core MCP tools and Ruby API reference
|
|
133
|
+
- `sketchup-model-auditing`: Detailed geometry and structural auditing
|
|
134
|
+
- `sketchup-review-design`: Architectural review process
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sketchup-model-auditing
|
|
3
|
+
description: |
|
|
4
|
+
Programmatic geometry and structural auditing for SketchUp models via Ruby API.
|
|
5
|
+
Use when: (1) checking if framing groups are manifold solids, (2) detecting
|
|
6
|
+
floating/unsupported pieces, (3) finding mesh intersections between framing members,
|
|
7
|
+
(4) verifying sloped wall plates sit on stud tops, (5) AABB intersection checks
|
|
8
|
+
produce false positives on sloped geometry (rafters, roofs). Covers Solid Inspector 2
|
|
9
|
+
API integration, slope-aware collision detection, and structural connectivity checks.
|
|
10
|
+
author: Claude Code
|
|
11
|
+
version: 1.0.0
|
|
12
|
+
date: 2026-03-11
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# SketchUp Model Auditing
|
|
16
|
+
|
|
17
|
+
## Problem
|
|
18
|
+
SketchUp's built-in tools (Solid Inspector², CleanUp³) only check individual geometry
|
|
19
|
+
validity (manifold, stray edges, reversed faces). They cannot detect:
|
|
20
|
+
- Pieces floating in space with nothing supporting them
|
|
21
|
+
- Mesh intersections between separate groups (studs through rafters, headers through kings)
|
|
22
|
+
- Structural connectivity issues (plates not seated on studs)
|
|
23
|
+
|
|
24
|
+
Additionally, naive axis-aligned bounding box (AABB) intersection checks produce massive
|
|
25
|
+
false positives on sloped geometry like rafters.
|
|
26
|
+
|
|
27
|
+
## Context / Trigger Conditions
|
|
28
|
+
- After generating framing via build scripts
|
|
29
|
+
- When the visual screenshot shows floating or intersecting pieces
|
|
30
|
+
- When Solid Inspector says "all good" but the model clearly has problems
|
|
31
|
+
- When AABB checks report hundreds of "intersections" that aren't real
|
|
32
|
+
|
|
33
|
+
## Solution
|
|
34
|
+
|
|
35
|
+
### 1. Solid Inspector 2 API (geometry-only checks)
|
|
36
|
+
```ruby
|
|
37
|
+
# Check manifold
|
|
38
|
+
group.manifold?
|
|
39
|
+
|
|
40
|
+
# Shell API for reversed/internal faces
|
|
41
|
+
shell = TT::Plugins::SolidInspector2::Shell.new(group)
|
|
42
|
+
shell.reversed_faces # => array of faces
|
|
43
|
+
shell.internal_faces # => array of faces
|
|
44
|
+
|
|
45
|
+
# Note: ErrorFinder.find_errors expects different args than you'd think
|
|
46
|
+
# Use Shell + manifold? instead for programmatic checks
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**What SI2 catches:** holes, stray edges, reversed faces, internal faces, non-manifold
|
|
50
|
+
**What SI2 misses:** floating pieces, spatial collisions, structural connectivity
|
|
51
|
+
|
|
52
|
+
### 2. Floating Piece Detection
|
|
53
|
+
```ruby
|
|
54
|
+
named_groups.each do |g|
|
|
55
|
+
bb = g.bounds
|
|
56
|
+
bottom_z = bb.min.z
|
|
57
|
+
next if bottom_z <= slab_top_z # on the slab
|
|
58
|
+
|
|
59
|
+
has_support = named_groups.any? { |other|
|
|
60
|
+
next false if other == g
|
|
61
|
+
ob = other.bounds
|
|
62
|
+
# Use 1.5" tolerance for narrow contact areas (stud top to plate bottom)
|
|
63
|
+
z_touch = (ob.max.z - bottom_z).abs < 1.5
|
|
64
|
+
x_overlap = ob.min.x < (bb.max.x + 1) && ob.max.x > (bb.min.x - 1)
|
|
65
|
+
y_overlap = ob.min.y < (bb.max.y + 1) && ob.max.y > (bb.min.y - 1)
|
|
66
|
+
z_touch && x_overlap && y_overlap
|
|
67
|
+
}
|
|
68
|
+
# flag if no support found
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Key insight:** Expand the XY overlap tolerance by ~1" for narrow members (studs/plates
|
|
73
|
+
on the same wall have very small contact areas that strict AABB misses).
|
|
74
|
+
|
|
75
|
+
### 3. Slope-Aware Intersection Detection (critical)
|
|
76
|
+
|
|
77
|
+
**The problem:** A rafter spanning Y=-7..138 has an AABB Z range of 83..115. Every stud
|
|
78
|
+
with a top Z above 83 will "intersect" the rafter's bounding box, even though the actual
|
|
79
|
+
rafter geometry at that stud's Y position is well above the stud.
|
|
80
|
+
|
|
81
|
+
**The fix:** Calculate the actual roof bottom Z at each entity's Y position:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# Derive slope from actual rafter geometry
|
|
85
|
+
rafters = groups.select { |g| g.name =~ /Rafter/i }
|
|
86
|
+
r = rafters.first
|
|
87
|
+
verts = r.entities.grep(Sketchup::Edge).flat_map { |e|
|
|
88
|
+
[e.start.position, e.end.position]
|
|
89
|
+
}.uniq { |p| [p.x.round(2), p.y.round(2), p.z.round(2)] }
|
|
90
|
+
|
|
91
|
+
sorted = verts.sort_by(&:y)
|
|
92
|
+
front_bottom_z = sorted.first(2).map(&:z).min
|
|
93
|
+
back_bottom_z = sorted.last(2).map(&:z).min
|
|
94
|
+
front_y = sorted.first(2).map(&:y).min
|
|
95
|
+
back_y = sorted.last(2).map(&:y).max
|
|
96
|
+
|
|
97
|
+
# Lambda to get roof bottom Z at any Y
|
|
98
|
+
run = back_y - front_y
|
|
99
|
+
rise = front_bottom_z - back_bottom_z
|
|
100
|
+
roof_bottom_z = lambda { |y|
|
|
101
|
+
t = (y - front_y) / run
|
|
102
|
+
front_bottom_z - t * rise
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Now check each stud against the ACTUAL roof Z at its position
|
|
106
|
+
studs.each do |s|
|
|
107
|
+
center_y = (s.bounds.min.y + s.bounds.max.y) / 2.0
|
|
108
|
+
roof_z = roof_bottom_z.call(center_y)
|
|
109
|
+
if s.bounds.max.z > roof_z + 0.5 # 0.5" tolerance
|
|
110
|
+
# REAL intersection — flag it
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Result:** Went from 196 false-positive "intersections" to 0 with this approach.
|
|
116
|
+
|
|
117
|
+
### 4. Header-King Stud Overlap Check
|
|
118
|
+
Headers should span between king studs, not through them. Check with volumetric overlap:
|
|
119
|
+
```ruby
|
|
120
|
+
headers.each do |h|
|
|
121
|
+
kings.each do |k|
|
|
122
|
+
ox = [0, [h.bounds.max.x, k.bounds.max.x].min - [h.bounds.min.x, k.bounds.min.x].max].max
|
|
123
|
+
oy = [0, [h.bounds.max.y, k.bounds.max.y].min - [h.bounds.min.y, k.bounds.min.y].max].max
|
|
124
|
+
oz = [0, [h.bounds.max.z, k.bounds.max.z].min - [h.bounds.min.z, k.bounds.min.z].max].max
|
|
125
|
+
vol = ox * oy * oz
|
|
126
|
+
# Flag if vol > 1.0 cubic inch
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 5. Sloped Wall Plate Connectivity
|
|
132
|
+
On sloped walls, top plates must sit on stud tops (gap < 0.25"):
|
|
133
|
+
```ruby
|
|
134
|
+
plates.each do |p|
|
|
135
|
+
stud_y = p.bounds.max.y.round(0) # plate sits on the lower (max-Y) stud
|
|
136
|
+
stud_top = studs_by_y[stud_y]
|
|
137
|
+
gap = (p.bounds.min.z - stud_top).abs
|
|
138
|
+
# Flag if gap > 0.25"
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Common bug:** Plates positioned one plate-thickness (1.5") too high. Caused by
|
|
143
|
+
calculating plate Z as `stud_top + plate_height` instead of just `stud_top`.
|
|
144
|
+
|
|
145
|
+
## Verification
|
|
146
|
+
Run the complete audit by sending the audit script via `sketchup_inspect`. Since MCP
|
|
147
|
+
tools accept multi-line Ruby directly, you can paste the full audit script into the
|
|
148
|
+
`code` parameter. Expected output: `ALL CHECKS PASSED` with 7/7 checks green.
|
|
149
|
+
|
|
150
|
+
## Notes
|
|
151
|
+
- 3D Warehouse components (doors, shingles) will always fail `manifold?` — warn, don't fail
|
|
152
|
+
- The floating check needs expanded XY tolerance for narrow framing members
|
|
153
|
+
- Corner plate overlaps (where two walls meet) are expected — don't flag those
|
|
154
|
+
- Entity indices change after any add/delete — never cache across operations
|
|
155
|
+
- This audit is complementary to the visual screenshot-verify loop, not a replacement
|
|
156
|
+
|
|
157
|
+
## See Also
|
|
158
|
+
- `sketchup-bridge`: MCP tools and Ruby API reference
|
|
159
|
+
- `sketchup-building-framing`: Parametric framing generation
|
|
160
|
+
- `sketchup-review-design`: Visual/architectural review process
|