This repository contains Ruby scripts for SketchUp that generate simple cabinetry models from code.
- Rows – Create and manage cabinet rows, reflow widths, and keep reveals uniform across fronts.
lib/cabinet.rb– library functions for creating cabinets. The generator builds a frameless carcass from two sides, a top (solid panel or pair of stringers), a bottom, and a back, and can optionally add shelves. Cabinets may also be partitioned with fixed panels so each section can have its own doors or drawers. Panels can be inset usingtop_inset,bottom_inset, andback_insetoptions. Usetop_type: :stringersandtop_stringer_width(default100.mm) to model stringers instead of a full top panel. Door overlays usedoor_revealfor the default gap to cabinet edges,top_revealandbottom_revealcan override the edge clearances, anddoor_gapcontrols spacing between adjacent fronts.- Panels are now created as SketchUp components rather than groups. A default
Birch Plywoodmaterial is applied to cabinet parts, while doors default toMDF. Five-piece doors can specify separate materials for the frame (defaultMaple) and center panel, which defaults to the door material. When missing from the model, the generator definesMDFas a solid color (RGB 164,143,122),Mapleas a solid color (RGB 224,200,160), andBirch Plywoodas a solid color (RGB 222,206,170). examples/frameless_two_shelf_cabinet.rb– sample script that creates a frameless cabinet with two shelves.examples/shaker_door_cabinet.rb– demonstrates a rail-and-stile door with an 18° bevel.examples/drawer_cabinet.rb– shows how to add drawers to a cabinet, mix drawers with doors, and adjust drawer clearances.examples/partitioned_cabinet.rb– demonstrates dividing a cabinet interior with fixed partitions.examples/slab_door_fronts.rb– inserts three base cabinets showcasing the:doors_left,:doors_right, and:doors_doubleslab door modes.- Settings & Defaults (mm) – locate shipped defaults, user overrides, and reset steps.
Copy the library and sample code into SketchUp's Ruby console or load them as scripts to build cabinet geometry automatically.
Place aicabinets.rb and the aicabinets/ folder inside SketchUp's Plugins directory to load AI Cabinets as an extension. The registrar exposes metadata so the Extension Manager lists "AI Cabinets" with version information from aicabinets/version.rb. The loader registers UI commands and toolbars without creating geometry at load time, keeping startup cost minimal while exposing extension entry points.
To verify Ruby syntax locally, run:
ruby -c aicabinets.rb && find aicabinets -type f -name '*.rb' -print0 | xargs -0 -n1 ruby -cInstall RuboCop and run the same command that CI executes:
gem install rubocop
rubocop --parallel --display-cop-namesThe Lint GitHub Actions workflow provisions Ruby 3.2 with ruby/setup-ruby@v1 and fails the build when RuboCop reports offenses.
TestUp is SketchUp's Minitest runner. To execute the AI Cabinets smoke tests:
- Install the TestUp 2 extension alongside AI Cabinets.
- In Extensions → TestUp → Preferences → Test Suites, add the absolute path to
tests/AI Cabinets/. - Open the TestUp window and run the AI Cabinets suite or the
TC_Smokecase.
The suite currently verifies the extension namespace loads, the helper utilities reset the active model cleanly, and shared tolerances work for geometry assertions.
UI dialog tests also stream DevTools console errors back to TestUp. The TC_DialogConsoleErrors case opens the Insert dialog, performs a basic interaction, and asserts that no console.error, window.onerror, or unhandled Promise rejection occurred; dedicated fixtures exercise failure scenarios. Warnings are recorded for review but do not fail the suite.
Use the npm helpers to deploy the extension to SketchUp 2026 and run TestUp CI locally:
-
Deploy the extension to the SketchUp 2026 Plugins folder:
npm run deploy:sketchup
-
Run the full TestUp suite (auto-deploys first):
npm run testup:all
Results are written to
dist/testup-all-results.json, and each run logsputsoutput todist/testup-all-ruby-console.log(with errors indist/testup-all-ruby-errors.log). Pass--output "C:\\dev\\aicabinets\\dist\\all_results.json"to redirect the JSON if another tool monitors a different path. -
Run a subset defined by a TestUp YAML config (auto-deploys first):
npm run testup:config -- --config "C:\\dev\\aicabinets\\tests\\class_only.yml"Or set
TESTUP_CONFIGand omit the flag:set TESTUP_CONFIG=C:\\dev\\aicabinets\\tests\\class_only.yml npm run testup:config
-
Generate a single-class TestUp YAML on the fly and run it (auto-deploys first):
npm run testup:class -- --class TC_Smoke --output "C:\\dev\\aicabinets\\dist\\class_results.json"Or set
TESTUP_CLASS/TESTUP_CONFIGalternatives:set TESTUP_CLASS=TC_Smoke npm run testup:class
Each class run also writes the SketchUp Ruby Console stream to
dist/testup-<ClassName>-ruby-console.log(and a matching-ruby-errors.log). When runningTC_Smoke, verify the log contains the[AICabinets TestUp] Ruby console capture markerline emitted by the smoke test to confirm output fromputsstatements is captured. -
Execute a custom Ruby script within SketchUp, close the active model without saving, and exit (auto-deploys first):
npm run sketchup:run -- --script "C:\\dev\\aicabinets\\script\\test_script.rb"Or set
STARTUP_RUBYand omit the flag:set STARTUP_RUBY=C:\\dev\\aicabinets\\script\\test_script.rb npm run sketchup:run
Each invocation saves the Ruby console output and errors to
dist/sketchup-run-<ScriptName>-<timestamp>-ruby-console.loganddist/sketchup-run-<ScriptName>-<timestamp>-ruby-errors.log, making it easy to reviewputsoutput without reopening SketchUp.
Defaults assume SketchUp 2026 on Windows with SketchUp.exe in %ProgramFiles%\SketchUp\SketchUp 2026\SketchUp\SketchUp.exe and Plugins at %APPDATA%\SketchUp\SketchUp 2026\SketchUp\Plugins. Override paths with environment variables or per-command flags:
SKETCHUP_EXEor--exe <path>SKETCHUP_PLUGINS_DIRor--plugins <path>SKETCHUP_VERSIONto adjust the default SketchUp version used for the paths above (defaults to2026)AI_CABINETS_TESTSor--tests <path>TESTUP_CONFIGor--config <path>(fortestup:config)TESTUP_CLASSor--class <TestUpClass>with optional--output <results.json>(fortestup:class)--output <results.json>when runningtestup:all(defaults todist/testup-all-results.json)STARTUP_RUBYor--script <path>(forsketchup:run)
Pass --debug after the command to echo resolved paths and the full SketchUp invocation.
To print the effective defaults (shipped JSON merged with user overrides), run:
ruby -I. script/print_effective_defaults.rbReset persisted overrides during manual testing with:
ruby -I. script/reset_overrides.rbTo package the extension for manual installation, run:
zip -r aicabinets-$(ruby -e "load 'aicabinets/version.rb'; puts AICabinets::VERSION").rbz aicabinets.rb aicabinets/When a tag matching v* is pushed, the Package RBZ workflow zips aicabinets.rb and aicabinets/ into dist/aicabinets-<VERSION>.rbz, uploads it as a workflow artifact, and can optionally publish the file on the tag’s GitHub Release.
AI Cabinets ships read-only defaults at aicabinets/data/defaults.json within the installed extension folder. The extension creates aicabinets/user/overrides.json after your first successful Insert or Edit action to persist user-chosen values. Both files live alongside aicabinets.rb in the extension’s support directory.
Effective settings are produced by merging the shipped defaults with the user overrides, applying override values last so they take precedence when keys overlap.
When defaults load, a sanitizer ensures every cabinet definition includes a partitions container with a resolved orientation, a bays array sized to count + 1, and per-bay defaults (for example, shelf_count and door_mode). Nested subpartitions inherit the perpendicular orientation, clone sane defaults when missing, and keep unknown keys intact so legacy models migrate deterministically.
All serialized length values are stored in millimeters and use the _mm suffix in JSON. No other unit system is written to disk.
To reset the extension to the shipped defaults, delete aicabinets/user/overrides.json. The extension recreates the overrides file the next time it needs to save user changes.
- Open Extension Manager from the Extensions menu or its toolbar icon.
- Click Install Extension…, choose the downloaded
aicabinets-<VERSION>.rbz, and confirm SketchUp’s warning for third-party packages. SketchUp Help: Manually Installing Extensions - If SketchUp 2026 blocks the load because the extension is Unsigned, open Extension Manager → Settings (gear) and review the Loading Policy, selecting the mode that matches your security posture. SketchUp documents the three modes—Identified Extensions Only, Approve Unidentified Extensions, and Unrestricted—in its Loading Policy Preferences guide. Changes may require restarting SketchUp before the unsigned extension loads.
- Only install RBZ files from sources you trust. Unrestricted allows all extensions and is the least secure option.
- Enable the extension in Extension Manager if it did not auto-activate after installation.
After installing the extension, launch its placeholder action from Extensions → AI Cabinets → Insert Base Cabinet… or by showing the AI Cabinets toolbar. Both entry points share the same command, which currently opens a simple placeholder message while the full cabinet insertion dialog is under development.
See Per-bay shelves & doors for a task-focused walk-through of the bay chips, Fronts & Shelves editor, Sub-partitions, Insert/Edit scope, and the double-door guardrail.
Generated base cabinets accept a partitions payload to divide the interior into bays.
partition_modedrives the top-level orientation (verticalorhorizontal). Whenpartition_modeisnone, the sanitizer forcespartitions.countto0andbaysto a single entry.modedetermines how partitions are created:noneomits partitions.evenspacescountpartitions so the resultingcount + 1bays have nearly equal clear widths.
positionsplaces partitions at explicit offsets measured in millimeters from the cabinet’s left outside face to each partition’s left face.baysstores per-bay settings such asshelf_countanddoor_mode. Its length always matchescount + 1, and missing or partial entries are filled from the active defaults.- Shelves and door fronts are generated per leaf bay. Parent bays that declare
subpartitionsskip shelf/front geometry so nested bays can provide their own counts and door modes. Each door leaf uses the bay’s clear width, subtracts edge/top/bottom reveals, and for doubles splits the remaining width around the configured center gap. - Each bay may define a
subpartitionscontainer with its owncount,orientation, and nestedbays. The sanitizer forces nested orientations to stay perpendicular to the parent partitions and fills missing bays tocount + 1. - Partitions use the carcass panel thickness (or an explicit
panel_thickness_mmvalue when provided) and span the interior height (top of the bottom panel to the underside of the top or stringers) and depth (front face to the back panel). - Invalid or overlapping requests are ignored, and the generator logs warnings when positions are clamped to the cabinet interior or discarded because they violate minimum bay widths.
The Insert/Edit HtmlDialog surfaces a bay selector (chips), shelf stepper, and door mode segmented control for each bay. Selecting None persists door_mode: null, while the Ruby helper blocks “Double” whenever the bay’s clear width cannot accommodate two leaves. Quality-of-life actions apply settings to every bay or mirror the left half to the right, skipping destinations that would violate the double-door constraints.
The Insert/Edit dialog ships with a predictable focus order and polite announcements so keyboard and assistive technology users receive the same feedback as mouse users.
- Native radios live inside
<fieldset>/<legend>groups for the partition mode selector, scope toggle, and bay editor switcher. Each option uses an explicit<label for="…">pairing so screen readers expose the full option text. - Segmented controls (bay chips, bay editor switcher, and door mode) expose a single tab stop per group. Arrow keys (and Home/End) wrap across options, immediately commit the new selection, and leave focus on the active segment so Tab/Shift+Tab can continue the dialog order. Disabled choices are skipped automatically, and
aria-selected/nativearia-checkedstates stay in sync with the visual highlight. - A single visually hidden live region (
#sr-updates) relays status changes. The JavaScriptLiveAnnouncerhelper coalesces messages (200 ms debounce) and sanitizes text before settingtextContentto avoid spamming assistive tech. - Inactive controls are removed from the tab order with
HTMLElement.inertwhen Chromium supports it, or a fallback that togglesaria-hiddenand saves/restores prior tabindex values. - Validation errors reuse the same live region:
FormController#setFieldErrorsetsaria-invalid, updates the field’s inline message, and announces{Label}: {Error}when the text changes.