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,363 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sketchup-review-design
|
|
3
|
+
description: |
|
|
4
|
+
Review a SketchUp building model like an architect or general contractor would.
|
|
5
|
+
Use when: (1) model is nearing completion and needs a quality check, (2) user
|
|
6
|
+
asks "does this look right" or "review the design", (3) checking if materials
|
|
7
|
+
are real-world purchasable (Home Depot, lumber yards), (4) verifying basic US
|
|
8
|
+
building code compliance for sheds/outbuildings, (5) looking for geometry
|
|
9
|
+
problems like mesh collisions or z-fighting, (6) generating a punch list of
|
|
10
|
+
issues to fix, (7) comparing model against a reference/goal image using Gemini
|
|
11
|
+
vision. Supports both whole-model review and section-by-section focused review.
|
|
12
|
+
author: Claude Code
|
|
13
|
+
version: 2.0.0
|
|
14
|
+
date: 2026-03-12
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# SketchUp Design Review
|
|
18
|
+
|
|
19
|
+
Review a building model the way an architect or GC would before breaking ground.
|
|
20
|
+
This skill supports two modes: **section-by-section** (preferred) and **whole-model**.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Section-by-Section Review (Preferred)
|
|
25
|
+
|
|
26
|
+
Reviewing the entire model at once produces generic, surface-level feedback.
|
|
27
|
+
Instead, isolate and review one section at a time for detailed, actionable findings.
|
|
28
|
+
|
|
29
|
+
### How to Review Section by Section
|
|
30
|
+
|
|
31
|
+
**Step 1: Identify sections.** List the distinct areas of the model:
|
|
32
|
+
```ruby
|
|
33
|
+
# Get all group name prefixes to identify sections
|
|
34
|
+
groups = model.entities.grep(Sketchup::Group)
|
|
35
|
+
prefixes = groups.map { |g| g.name.split(" ").first(2).join(" ") }.uniq.sort
|
|
36
|
+
prefixes.join("\n")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Typical sections for a building:
|
|
40
|
+
- Foundation/slab
|
|
41
|
+
- Each exterior wall (front, back, left, right)
|
|
42
|
+
- Interior walls
|
|
43
|
+
- Floor framing / deck
|
|
44
|
+
- Roof (rafters, sheathing, ridge)
|
|
45
|
+
- Turrets / towers
|
|
46
|
+
- Porch / entryway
|
|
47
|
+
- Balcony / deck
|
|
48
|
+
- Trim and decorative elements
|
|
49
|
+
- Fixtures (doors, windows, hardware)
|
|
50
|
+
|
|
51
|
+
**Step 2: For each section, do a focused review cycle:**
|
|
52
|
+
|
|
53
|
+
#### 2a. Isolate the section visually
|
|
54
|
+
Hide everything except the section under review + its supporting structure:
|
|
55
|
+
```ruby
|
|
56
|
+
section_prefix = "Balcony" # change per section
|
|
57
|
+
groups = model.entities.grep(Sketchup::Group)
|
|
58
|
+
|
|
59
|
+
# Hide all, show only this section + foundation
|
|
60
|
+
groups.each do |g|
|
|
61
|
+
g.visible = g.name.start_with?(section_prefix) ||
|
|
62
|
+
g.name.include?("Slab") || g.name.include?("Foundation") ||
|
|
63
|
+
g.name.include?("Bot Plate") # show what it connects to
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### 2b. Screenshot the isolated section from multiple angles
|
|
68
|
+
Position the camera close to the section, not the whole model.
|
|
69
|
+
|
|
70
|
+
First, compute the section center and viewing distance via `sketchup_inspect`:
|
|
71
|
+
```ruby
|
|
72
|
+
section_groups = model.entities.grep(Sketchup::Group).select { |g|
|
|
73
|
+
g.name.start_with?(section_prefix)
|
|
74
|
+
}
|
|
75
|
+
if section_groups.any?
|
|
76
|
+
min_x = section_groups.map { |g| g.bounds.min.x }.min
|
|
77
|
+
min_y = section_groups.map { |g| g.bounds.min.y }.min
|
|
78
|
+
min_z = section_groups.map { |g| g.bounds.min.z }.min
|
|
79
|
+
max_x = section_groups.map { |g| g.bounds.max.x }.max
|
|
80
|
+
max_y = section_groups.map { |g| g.bounds.max.y }.max
|
|
81
|
+
max_z = section_groups.map { |g| g.bounds.max.z }.max
|
|
82
|
+
|
|
83
|
+
cx = (min_x + max_x) / 2.0
|
|
84
|
+
cy = (min_y + max_y) / 2.0
|
|
85
|
+
cz = (min_z + max_z) / 2.0
|
|
86
|
+
span = [max_x - min_x, max_y - min_y, max_z - min_z].max
|
|
87
|
+
dist = span * 2.5
|
|
88
|
+
"center=#{cx},#{cy},#{cz} dist=#{dist}"
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then take screenshots from multiple angles using `sketchup_screenshot` with those computed values:
|
|
93
|
+
- **Iso**: eye [cx + dist*0.6, cy - dist*0.6, cz + dist*0.4], target [cx, cy, cz]
|
|
94
|
+
- **Front**: eye [cx, cy - dist, cz], target [cx, cy, cz]
|
|
95
|
+
- **Side**: eye [cx + dist, cy, cz], target [cx, cy, cz]
|
|
96
|
+
|
|
97
|
+
The screenshot tool returns the image directly — just view the result.
|
|
98
|
+
|
|
99
|
+
#### 2c. Compare section against goal image
|
|
100
|
+
Take a `sketchup_screenshot` from an angle that matches the goal image, then compare
|
|
101
|
+
visually. If using the Gemini comparison script, save the screenshot first and pass
|
|
102
|
+
both paths:
|
|
103
|
+
```bash
|
|
104
|
+
./scripts/compare_images.sh "goal_section.jpg" "/tmp/review_section.png"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
If no cropped goal image exists, instruct Gemini to focus on just that area:
|
|
108
|
+
- Modify the compare script prompt to say "Focus ONLY on the [section name] area"
|
|
109
|
+
- Or take a screenshot from an angle that emphasizes the section being reviewed
|
|
110
|
+
|
|
111
|
+
#### 2d. Run programmatic checks on section groups
|
|
112
|
+
```ruby
|
|
113
|
+
section_groups = model.entities.grep(Sketchup::Group).select { |g|
|
|
114
|
+
g.name.start_with?(section_prefix)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
checks = []
|
|
118
|
+
|
|
119
|
+
# Check for oversized lumber
|
|
120
|
+
section_groups.each do |g|
|
|
121
|
+
length = [g.bounds.width, g.bounds.depth, g.bounds.height].max
|
|
122
|
+
checks << "OVERSIZED: #{g.name} (#{length.to_l})" if length > 192
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Check for manifold solids
|
|
126
|
+
section_groups.each do |g|
|
|
127
|
+
checks << "NON-MANIFOLD: #{g.name}" if g.respond_to?(:manifold?) && !g.manifold?
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Check for floating pieces (not touching anything else)
|
|
131
|
+
section_groups.each do |g|
|
|
132
|
+
bb = g.bounds
|
|
133
|
+
touching = section_groups.any? { |other|
|
|
134
|
+
next if other == g
|
|
135
|
+
ob = other.bounds
|
|
136
|
+
!(bb.min.x > ob.max.x + 0.5 || bb.max.x < ob.min.x - 0.5 ||
|
|
137
|
+
bb.min.y > ob.max.y + 0.5 || bb.max.y < ob.min.y - 0.5 ||
|
|
138
|
+
bb.min.z > ob.max.z + 0.5 || bb.max.z < ob.min.z - 0.5)
|
|
139
|
+
}
|
|
140
|
+
checks << "FLOATING: #{g.name} — not touching any other section piece" unless touching
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
checks.empty? ? "All checks passed" : checks.join("\n")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### 2e. Record findings
|
|
147
|
+
After each section review, compile:
|
|
148
|
+
- What's correct
|
|
149
|
+
- What's wrong (with specific fix descriptions)
|
|
150
|
+
- Priority (critical / warning / cosmetic)
|
|
151
|
+
|
|
152
|
+
**Step 3: Restore visibility**
|
|
153
|
+
```ruby
|
|
154
|
+
model.entities.grep(Sketchup::Group).each { |g| g.visible = true }
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Recommended Review Order
|
|
158
|
+
|
|
159
|
+
Review sections in build order (bottom-up), since upper sections depend on lower:
|
|
160
|
+
|
|
161
|
+
1. **Foundation / slab** — correct size, position, Z-offset
|
|
162
|
+
2. **Floor framing** — joists spacing, deck coverage
|
|
163
|
+
3. **Walls** (one at a time) — stud spacing, plates, headers, openings
|
|
164
|
+
4. **Interior walls** — height matches roof line, door openings
|
|
165
|
+
5. **Roof structure** — rafters, ridge, bearing on walls
|
|
166
|
+
6. **Roof sheathing** — covers full area, no gaps
|
|
167
|
+
7. **Exterior features** — porch, balcony, turrets
|
|
168
|
+
8. **Trim and decorative** — fascia, shutters, star, scalloped trim
|
|
169
|
+
9. **Fixtures** — doors, windows, hardware
|
|
170
|
+
|
|
171
|
+
### Section Review Prompt Template
|
|
172
|
+
|
|
173
|
+
When running Gemini comparison for a section, use a focused prompt:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
Focus ONLY on the {SECTION_NAME} in both images. Ignore everything else.
|
|
177
|
+
|
|
178
|
+
For the goal image: describe the {section} in detail — dimensions, lumber sizes,
|
|
179
|
+
joints, spacing, materials, decorative elements.
|
|
180
|
+
|
|
181
|
+
For the model image: describe the same {section} — what matches, what's different.
|
|
182
|
+
|
|
183
|
+
List the top 3 specific changes needed to make the model's {section} match the goal.
|
|
184
|
+
Be precise: "move X from Y to Z", "change 2x4 to 4x4", "add 6 more balusters".
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Whole-Model Review (Quick Pass)
|
|
190
|
+
|
|
191
|
+
Use this for a fast overall health check, not detailed design feedback.
|
|
192
|
+
|
|
193
|
+
### Quick Review Script
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
results = []
|
|
197
|
+
bb = model.bounds
|
|
198
|
+
cx = bb.center.x; cy = bb.center.y; cz = bb.center.z
|
|
199
|
+
|
|
200
|
+
# Dimensions
|
|
201
|
+
sqft = (bb.width * bb.depth) / 144.0
|
|
202
|
+
results << "Footprint: #{sqft.round(1)} sq ft, Height: #{bb.height.to_l}"
|
|
203
|
+
results << (sqft > 120 ? "NOTE: Likely needs building permit" : "May be permit-exempt")
|
|
204
|
+
|
|
205
|
+
# Entity count
|
|
206
|
+
counts = Hash.new(0)
|
|
207
|
+
model.entities.each { |e| counts[e.typename] += 1 }
|
|
208
|
+
results << "Entities: #{counts.map { |t,c| "#{t}:#{c}" }.join(", ")}"
|
|
209
|
+
|
|
210
|
+
# Stray geometry
|
|
211
|
+
strays = model.entities.grep(Sketchup::Edge).select { |e| e.faces.empty? }
|
|
212
|
+
results << (strays.empty? ? "OK: No stray edges" : "WARNING: #{strays.length} stray edges")
|
|
213
|
+
|
|
214
|
+
# Loose top-level faces
|
|
215
|
+
loose = model.entities.grep(Sketchup::Face).length
|
|
216
|
+
results << (loose == 0 ? "OK: No loose faces" : "WARNING: #{loose} loose faces at top level")
|
|
217
|
+
|
|
218
|
+
# Oversized lumber check
|
|
219
|
+
oversized = []
|
|
220
|
+
model.entities.each do |e|
|
|
221
|
+
next unless e.is_a?(Sketchup::Group)
|
|
222
|
+
length = [e.bounds.width, e.bounds.depth, e.bounds.height].max
|
|
223
|
+
oversized << "#{e.name} (#{length.to_l})" if length > 192
|
|
224
|
+
end
|
|
225
|
+
results << (oversized.empty? ? "OK: All lumber fits standard stock" : "WARNING: Oversized: #{oversized.join(", ")}")
|
|
226
|
+
|
|
227
|
+
results.join("\n")
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Multi-Angle Screenshots
|
|
231
|
+
|
|
232
|
+
Use `./scripts/capture_views.sh <design_folder>` for standardized 10-angle capture.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Geometry Validation Checks
|
|
237
|
+
|
|
238
|
+
### Collision Detection
|
|
239
|
+
```ruby
|
|
240
|
+
check_collisions = lambda { |entities|
|
|
241
|
+
items = entities.to_a.select { |e|
|
|
242
|
+
e.is_a?(Sketchup::Group) || e.is_a?(Sketchup::ComponentInstance)
|
|
243
|
+
}
|
|
244
|
+
collisions = []
|
|
245
|
+
items.combination(2).each do |a, b|
|
|
246
|
+
ab = a.bounds; bb_b = b.bounds
|
|
247
|
+
next if ab.min.x > bb_b.max.x || ab.max.x < bb_b.min.x
|
|
248
|
+
next if ab.min.y > bb_b.max.y || ab.max.y < bb_b.min.y
|
|
249
|
+
next if ab.min.z > bb_b.max.z || ab.max.z < bb_b.min.z
|
|
250
|
+
a_name = a.respond_to?(:name) ? a.name : a.definition.name
|
|
251
|
+
b_name = b.respond_to?(:name) ? b.name : b.definition.name
|
|
252
|
+
collisions << "#{a_name} <-> #{b_name}"
|
|
253
|
+
end
|
|
254
|
+
collisions
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Slab-Relative Z-Offsets
|
|
259
|
+
All framing should start at slab top, not Z=0:
|
|
260
|
+
```ruby
|
|
261
|
+
slab = model.entities.to_a.find { |e| e.is_a?(Sketchup::Group) && e.name.include?("Slab") }
|
|
262
|
+
slab_top = slab ? slab.bounds.max.z : 0
|
|
263
|
+
model.entities.grep(Sketchup::Group).each do |g|
|
|
264
|
+
next if g.name.include?("Slab") || g.name.include?("Foundation")
|
|
265
|
+
if g.bounds.min.z < slab_top - 0.5 && g.bounds.min.z >= 0
|
|
266
|
+
puts "WARNING: #{g.name} starts at Z=#{g.bounds.min.z.round(1)}, slab top is Z=#{slab_top}"
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Structural / Framing Review
|
|
274
|
+
|
|
275
|
+
### Stud Spacing (16" OC)
|
|
276
|
+
```ruby
|
|
277
|
+
studs = model.entities.to_a.select { |e|
|
|
278
|
+
e.is_a?(Sketchup::Group) && e.name.include?("Stud") && e.name.start_with?(wall_prefix)
|
|
279
|
+
}
|
|
280
|
+
positions = studs.map { |s| s.bounds.min.x }.sort # or .min.y for Y-axis walls
|
|
281
|
+
positions.each_cons(2) do |a, b|
|
|
282
|
+
spacing = b - a
|
|
283
|
+
puts "WARNING: Stud spacing #{spacing.round(1)}\"" if spacing > 16.5 || spacing < 15.5
|
|
284
|
+
end
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Header Sizing (IRC R602.7)
|
|
288
|
+
| Opening Width | Minimum Header |
|
|
289
|
+
|---------------|----------------|
|
|
290
|
+
| Up to 4 ft | 2x6 |
|
|
291
|
+
| 4–6 ft | 2x8 |
|
|
292
|
+
| 6–8 ft | 2x10 |
|
|
293
|
+
| 8–10 ft | 2x12 |
|
|
294
|
+
|
|
295
|
+
### Double Top Plate Check
|
|
296
|
+
```ruby
|
|
297
|
+
["FW", "BW", "LW", "RW", "DW"].each do |prefix|
|
|
298
|
+
plates = model.entities.to_a.select { |e|
|
|
299
|
+
e.is_a?(Sketchup::Group) && e.name.start_with?("#{prefix} Top Plate")
|
|
300
|
+
}
|
|
301
|
+
puts "WARNING: #{prefix} has #{plates.length} top plate(s), need 2" if plates.length < 2
|
|
302
|
+
end
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Visual Comparison with Gemini Flash
|
|
308
|
+
|
|
309
|
+
### Full Model Comparison
|
|
310
|
+
```bash
|
|
311
|
+
./scripts/compare_images.sh "path/to/goal.jpg" /tmp/current_model.png
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Section-Focused Comparison
|
|
315
|
+
For reviewing a specific section, modify the prompt in the compare script or
|
|
316
|
+
use a focused camera angle that isolates the section being reviewed.
|
|
317
|
+
|
|
318
|
+
**Best practice:** Take a close-up screenshot of the section from the same angle
|
|
319
|
+
as the goal image, then compare just those two crops.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Building Code Quick Reference (IRC)
|
|
324
|
+
|
|
325
|
+
- **Under 120 sq ft**: Often permit-exempt
|
|
326
|
+
- **Over 200 sq ft**: Almost always requires permit
|
|
327
|
+
- **Min ceiling height**: 7ft habitable, no min for storage
|
|
328
|
+
- **Min roof pitch for shingles**: 2:12 (4:12 preferred)
|
|
329
|
+
- **Overhang**: 6-12" typical
|
|
330
|
+
- **Setbacks**: Verify with local zoning (can't check in model)
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Output: Punch List Format
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
=== DESIGN REVIEW: {SECTION NAME} ===
|
|
338
|
+
Model: [model name]
|
|
339
|
+
Date: [date]
|
|
340
|
+
|
|
341
|
+
## CRITICAL (must fix)
|
|
342
|
+
- [ ] [issue]
|
|
343
|
+
|
|
344
|
+
## WARNINGS (should fix)
|
|
345
|
+
- [ ] [issue]
|
|
346
|
+
|
|
347
|
+
## COSMETIC (nice to have)
|
|
348
|
+
- [ ] [issue]
|
|
349
|
+
|
|
350
|
+
## VERIFIED OK
|
|
351
|
+
- [x] [check] ✓
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## References
|
|
357
|
+
- [IRC Framing Inspection Checklist](https://mybuildingpermit.com/sites/default/files/documentation/Framing_0.pdf)
|
|
358
|
+
- [Framing Inspection Checklist: Builder Tips](https://www.southeasterngc.com/framing-inspection-checklist/)
|
|
359
|
+
|
|
360
|
+
## See Also
|
|
361
|
+
- `sketchup-bridge`: MCP tools and Ruby API reference
|
|
362
|
+
- `sketchup-building-framing`: Generating proper framing
|
|
363
|
+
- `sketchup-model-auditing`: Automated geometry checks
|