Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c288257
Rename Img-to-3D Studio v2 to Img-to-3D Generator; adopt the multi-im…
lucaslyl Jul 23, 2026
5c98d8b
img with canvas sucess
lucaslyl Jul 24, 2026
c9442ac
Fix img-to-3D generation determinism, part placement and geometry con…
lucaslyl Jul 27, 2026
eea4365
modulraize code following theming ui and catalog folder structure
lucaslyl Jul 27, 2026
7fb7668
Merge remote-tracking branch 'origin/main' into eco-525-img-to-3d-studio
lucaslyl Jul 28, 2026
9af0f0a
Img-to-3D: bone primitive, deterministic face/assembly passes, comple…
lucaslyl Jul 28, 2026
efbac02
Img-to-3D: de-float orphan parts deterministically + fix refine round…
lucaslyl Jul 28, 2026
654540e
add examples
lucaslyl Jul 29, 2026
615fb99
fix lint
lucaslyl Jul 29, 2026
53d6ee8
optimize
lucaslyl Jul 29, 2026
e4e1a46
screenshot
lucaslyl Jul 29, 2026
a73836a
update instance dir
lucaslyl Jul 29, 2026
b177b42
update realm index
lucaslyl Jul 29, 2026
54283b2
Merge remote-tracking branch 'origin/main' into eco-525-img-to-3d-studio
lucaslyl Jul 29, 2026
6a75bc5
create listing & update spec
lucaslyl Jul 29, 2026
325c391
give unique id
lucaslyl Jul 30, 2026
bd37ca2
Align the round-2 SculptedModel instance with the realm's serialized …
lucaslyl Jul 30, 2026
14d4ed4
Merge remote-tracking branch 'origin/main' into eco-525-img-to-3d-studio
lucaslyl Aug 4, 2026
ee41f09
Complete Img-to-3D listing packaging for a working remix
lucaslyl Aug 4, 2026
592d688
Document every spec and finish the Img-to-3D listing packaging
lucaslyl Aug 4, 2026
666a16c
Merge remote-tracking branch 'origin/main' into eco-525-img-to-3d-studio
lucaslyl Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"data": {
"type": "card",
"attributes": {
"references": {
"images": []
},
"llmModel": null,
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"meta": {
"adoptsFrom": {
"module": "../img-to-3d-studio",
"name": "ImgTo3dStudio"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"data": {
"type": "card",
"attributes": {
"references": {
"images": [
{
"url": "https://static.vecteezy.com/system/resources/thumbnails/027/182/338/small/trailler-truck-isolated-on-a-transparent-background-png.png",
"sourceMode": "url"
}
]
},
"llmModel": null,
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"meta": {
"adoptsFrom": {
"module": "../img-to-3d-studio",
"name": "ImgTo3dStudio"
}
},
"relationships": {
"selectedCreation": {
"links": {
"self": "../img-to-3d/282993ec-cba6-4130-bc27-582b997caf06/rounds/SculptedModel/feb20557-8e81-4aef-b70f-c842a1584b43"
}
},
"latestCreation": {
"links": {
"self": "../img-to-3d/282993ec-cba6-4130-bc27-582b997caf06/rounds/SculptedModel/feb20557-8e81-4aef-b70f-c842a1584b43"
}
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions 4376bf-img-to-3d-generator/Skill/refine-sculpt-skill.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"data": {
"meta": {
"adoptsFrom": {
"name": "Skill",
"module": "https://cardstack.com/base/skill"
}
},
"type": "card",
"attributes": {
"cardTitle": "Sculpt Refiner Skill",
"cardDescription": "Conversational refinement for an Img-to-3D model in the AI Assistant. Compares the model's render against its reference photo, suggests fixes, and applies the ones the user approves via the Refine Model command.",
"cardInfo": {
"name": null,
"notes": null,
"summary": null,
"cardThumbnailURL": null
},
"commands": [
{
"codeRef": {
"name": "default",
"module": "../commands/refine-sculpt-command"
},
"requiresApproval": true
}
],
"instructions": "You are refining a 3D model inside the Boxel AI Assistant. A Sculpted Model card is attached — it shows the REFERENCE photo the model was built from and the model's current RENDER (a screenshot). You can SEE both images; use your own vision to compare them.\n\nHOW YOU WORK:\n- Your FIRST reply is a diagnosis, not a command. LOOK at the reference vs the current render and list, as short bullet rows, the SPECIFIC differences you can see — what is wrong, missing, mis-coloured, the wrong size, or in the wrong place. Then ask ONE question: which of these would you like me to fix (or shall I fix them all, one at a time)?\n- CHECK GLAZING AND OPENINGS DELIBERATELY: windows, a windshield, side/rear glass, a sunroof, a screen, a lens, a grille opening. These are flat and low-contrast, so they are the parts most often left out of the build — if the reference clearly shows a window and the render has none, that is a real 'missing part', call it out.\n- Do NOT dump the spec or JSON into the chat, and do NOT critique endlessly — list only concrete, visible differences (aim for the 2–5 that matter most), positively framed.\n\nAPPLYING A CHANGE:\n- When the user agrees on a change, CALL THE \"Refine Model\" COMMAND — once per change. Give it a single, precise, self-contained instruction naming the part and the exact change, e.g. \"recolor the shorts to bright red #d81f2a\", \"make both ears 30% bigger\", \"move the bill to the front centre of the head\", \"add a windshield as a recessed glass pane across the front of the cab\", \"remove the duplicate nose\".\n- The user reviews and approves each command before it runs, so propose ONE change at a time and wait — do not batch several edits into one call, and do not paste geometry numbers; the command works out the spec edit itself.\n- Each call edits the model IN PLACE (it does not spawn a new copy), and the studio shows the update. After it applies, reply with ONE short sentence saying what you changed, then ask if they want the next fix.\n\nCALLING \"Refine Model\":\n- sculptedModelId: ALWAYS the id of the attached Sculpted Model card — that is the CURRENT round shown in the studio viewport. Each edit is applied in place to that same current round's file; never target a different or older round.\n- instruction: the single plain-language change described above. Recolor / resize / move / reorient / add / remove a part — all fine; describe it clearly enough that someone who cannot see the reference could apply it.\n\nWHAT NOT TO DO:\n- Do not try to rewrite code or emit three.js — you only describe the change; the command edits the model's spec and rebuilds it.\n- Do not invent detail the reference does not show. If the reference and render already match on something, leave it alone."
},
"relationships": {
"cardInfo.theme": {
"links": {
"self": null
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"data": {
"type": "card",
"attributes": {
"readMe": "# dropHairlineParts\n\n## Summary\n\nA pruning pass in the img-to-3d structure-repair chain. It detects hairline solids — geometry so thin it renders as a wire hanging in space (a mould seam, panel gap, or printed line modelled as a tiny tube). The gate is deliberately name-blind and purely dimensional: tubes with radius under 0.02, tori with tube under 0.008, and boxes/roundedBoxes/cylinders with two extents under 0.015 world units are deleted. Removal cascades: any child parented to a dropped part is dropped too, since a surviving child would inherit root coordinates and fly off on its own.\n\n## Import\n\n```ts\nimport { dropHairlineParts } from '../util/spec-passes/prune';\n```\n\n## Usage\n\nRuns first in `runStructurePasses`, before any other inventory or placement pass. It receives the parsed spec, mutates `parsed.components` in place by filtering out condemned parts, and returns log lines describing each drop. The effect is that surface marks stop rendering as dark lines crossing the model.\n",
"ref": {
"module": "../util/spec-passes/prune",
"name": "dropHairlineParts"
},
"specType": null,
"containedExamples": [],
"cardTitle": "dropHairlineParts",
"cardDescription": null,
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"meta": {
"adoptsFrom": {
"module": "@cardstack/base/spec",
"name": "Spec"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"data": {
"type": "card",
"attributes": {
"readMe": "# selectRecipes\n\n## Summary\n\nTurns an analysis document into the recipe text for the spec prompt: it maps the names returned by `selectRecipeNames(analysis)` to their blocks in the `RECIPES` table (revolved, organic, character, flatGraphic, wrapDecal, boxy, grip, roundedShell, artwork, architectural, vehicle, machineDetail, finishes) and joins them with blank lines. These are craft rules only some objects need — each costs a couple of thousand tokens of model attention, so an object only receives the ones its own plan triggers; an object triggering none yields an empty string.\n\n## Import\n\n```ts\nimport { selectRecipes } from '../prompts/recipes';\n```\n\n## Usage\n\nCalled by `buildSpecSystemPrompt` in `prompts/spec.gts`: a non-empty result becomes the second system-prompt block (\"Build directives for THIS object\"), while an empty result leaves the invariant contract block alone.",
"ref": {
"module": "../prompts/recipes",
"name": "selectRecipes"
},
"specType": null,
"containedExamples": [],
"cardTitle": "selectRecipes",
"cardDescription": null,
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"meta": {
"adoptsFrom": {
"module": "@cardstack/base/spec",
"name": "Spec"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"data": {
"type": "card",
"attributes": {
"readMe": "# flagUnrealizedParts\n\n## Summary\n\nA report-only pass in the img-to-3d structure-repair chain — the mirror of `dropUnplannedParts`. It detects plan entries that NO component realizes: a part the reference plainly has (a bottle's front label, say) that the build simply never authored. It also reports declared materials no component uses, which is often the only trace of a part that was planned, given paint, and never built. Plan parts with approach `revolved` are exempt when a lathe exists to have absorbed them. Geometry cannot be invented from a part name, so nothing is repaired.\n\n## Import\n\n```ts\nimport { flagUnrealizedParts } from '../util/spec-passes/prune';\n```\n\n## Usage\n\nRuns in `runStructurePasses` after the pruning drops; the studio's `generate()` also calls it directly (components/studio-isolated.gts) to decide whether the vision completeness audit is worth its round-trip. Receives (parsed, analysis), mutates nothing, returns log lines.\n",
"ref": {
"module": "../util/spec-passes/prune",
"name": "flagUnrealizedParts"
},
"specType": null,
"containedExamples": [],
"cardTitle": "flagUnrealizedParts",
"cardDescription": null,
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"meta": {
"adoptsFrom": {
"module": "@cardstack/base/spec",
"name": "Spec"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"data": {
"type": "card",
"attributes": {
"readMe": "# enforceAttachments\n\n## Summary\n\nAn attachment pass in the img-to-3d structure chain. The analysis writes prose joint lines (\"balcony railing rests-on balcony platform\") that the spec prompt calls hard joints, but nothing checked them numerically — contact at a corner satisfies the interpreter while a roof sits nowhere near centred. Working purely in the spec's own coordinates (no camera model), it REPAIRS the two unambiguous constraints — `centered-above` and `rests-on` seat the subject group on top of its target with a 0.03 overlap, recentring only when it is already roughly centred — and reports `inset-into`, `flush-top`, and `attached-left/right/front/back` violations. It refuses inverted lines (a support never rests on the body it carries; a subject the spec placed below its target is left alone).\n\n## Import\n\n```ts\nimport { enforceAttachments } from '../util/spec-passes/attachments';\n```\n\n## Usage\n\nRuns in `runStructurePasses` after `repairMirroredAttachments`, before `groundSupports`. Receives (parsed, analysis); mutates positions in place; returns log lines.\n",
"ref": {
"module": "../util/spec-passes/attachments",
"name": "enforceAttachments"
},
"specType": null,
"containedExamples": [],
"cardTitle": "enforceAttachments",
"cardDescription": null,
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"meta": {
"adoptsFrom": {
"module": "@cardstack/base/spec",
"name": "Spec"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"data": {
"type": "card",
"attributes": {
"readMe": "# clampToEnvelope\n\n## Summary\n\nA placement pass in the img-to-3d pipeline for revolved objects. The traced silhouette is the object's true outer boundary, and a capsule, ring, or collar wider than the neck — or a part that slid off-axis — pokes past the outline and reads as floating. Given the world-space envelope (half-width per height, `{ y, half }[]` from the traced lathe profile), it lowers a part whose centre floats above the silhouette top onto the top edge, scales an on-axis over-wide solid down to the local half-width (X, and Z too when `revolved`), and slides an off-axis part toward the axis. Lathes, tubes, extrusions and meshes are skipped; decal radii belong to `fitCurvedDecals`.\n\n## Import\n\n```ts\nimport { clampToEnvelope } from '../util/spec-passes/placement';\n```\n\n## Usage\n\nNot in `runStructurePasses` — the studio's `applyParsedSpec` calls `clampToEnvelope(parsed, this.tracedEnvelope, true)` only when silhouette tracing succeeded. Mutates positions/scales in place; returns log lines.\n",
"ref": {
"module": "../util/spec-passes/placement",
"name": "clampToEnvelope"
},
"specType": null,
"containedExamples": [],
"cardTitle": "clampToEnvelope",
"cardDescription": null,
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"meta": {
"adoptsFrom": {
"module": "@cardstack/base/spec",
"name": "Spec"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"data": {
"type": "card",
"attributes": {
"readMe": "# flagSilhouetteNotch\n\n## Summary\n\nA report-only shape pass in the img-to-3d structure chain — the silhouette-level counterpart of the buried-part check: there a part exists but cannot be seen; here the silhouette says a part should exist and none does. It samples the model's height profile in 24 steps along its longest horizontal axis (a vehicle's length), restricted to the central slab so thin flank parts cannot paper over the gap, and looks for a LONG dip — at least 15% of the span, under 65% of the tallest height — with taller structure on BOTH sides. A dip at either end is a nose or tail; a flanked valley is a body part nobody built. Which part is missing is a question about the photograph, so nothing is repaired.\n\n## Import\n\n```ts\nimport { flagSilhouetteNotch } from '../util/spec-passes/shape';\n```\n\n## Usage\n\nRuns late in `runStructurePasses`, among the flag passes. Receives the parsed spec, mutates nothing, returns at most one descriptive log line.\n",
"ref": {
"module": "../util/spec-passes/shape",
"name": "flagSilhouetteNotch"
},
"specType": null,
"containedExamples": [],
"cardTitle": "flagSilhouetteNotch",
"cardDescription": null,
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"meta": {
"adoptsFrom": {
"module": "@cardstack/base/spec",
"name": "Spec"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"data": {
"type": "card",
"attributes": {
"readMe": "# composeComparison\n\n## Summary\n\nPacks the reference photo beside one or more labeled render angles into a single JPEG contact sheet (returned as a data URL), so the refine model compares them in one glance — it reviews exactly one image per round but sees the build from every side, because 3D errors hide from single angles. Each image is drawn at a shared height (384px when there are multiple renders, 512px for one) with a label strip on top: `REFERENCE`, then `RENDER` or the angle labels (`RENDER @ REF ANGLE`, `RENDER FRONT`, `RENDER SIDE`, `RENDER 3/4` when `firstIsReferenceAngle` is set).\n\n## Import\n\n```ts\nimport { composeComparison } from '../util/comparison-sheet';\n```\n\n## Usage\n\nThe studio (`components/studio-isolated.gts`) composes the sheet from the reference data URL and captured viewport renders before each refine and completeness-audit vision call; the returned data URL goes into the request's image content.",
"ref": {
"module": "../util/comparison-sheet",
"name": "composeComparison"
},
"specType": null,
"containedExamples": [],
"cardTitle": "composeComparison",
"cardDescription": null,
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"meta": {
"adoptsFrom": {
"module": "@cardstack/base/spec",
"name": "Spec"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"data": {
"type": "card",
"attributes": {
"readMe": "# isRecessed\n\n## Summary\n\nA predicate shared by the img-to-3d placement passes. The pipeline's default assumption is that every feature PROTRUDES — `resolveBuriedParts` pushes buried parts back out — so a feature designed to sit INTO its host needs an opt-out. A component counts as recessed when it flags `inset: true` or when its nodeId, partRef, or note matches `RECESS_NAME` (recess, sunken, inset, intake, vent, grille, interior, cavity, well, window, windshield, and variants). It reads the component only; nothing is mutated.\n\n## Import\n\n```ts\nimport { isRecessed } from '../util/spec-passes/placement';\n```\n\n## Usage\n\nCalled inside the placement module itself: `resolveBuriedParts` uses it to exempt recessed features from being ejected out of their host, and `seatRecesses` uses it (plus its `attachTo`/enclosing-host search) to pick which parts get sunk into a surface. Receives a component, returns a boolean.\n",
"ref": {
"module": "../util/spec-passes/placement",
"name": "isRecessed"
},
"specType": null,
"containedExamples": [],
"cardTitle": "isRecessed",
"cardDescription": null,
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"meta": {
"adoptsFrom": {
"module": "@cardstack/base/spec",
"name": "Spec"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"data": {
"type": "card",
"attributes": {
"readMe": "# reconcileProportions\n\n## Summary\n\nMeasured proportion reconciliation in the img-to-3d pipeline — the heart of \"LLM names structure, math writes numbers\". The draft build is rendered and measured in the viewer (real world-space boxes), each `partRef` group is compared against its analysis bbox target (normalized to the object's union bounds), and size and placement corrections are written back deterministically — zero vision calls. Widths are skipped when the camera is over 30° off front; vertical MOVES are withheld above 25° elevation and for split groups that share no host and do not touch (sizes still apply). Per-part scale is clamped so the FINAL scale stays within 0.4–2.5×, preventing compounding rounds from driving a part degenerate. Tubes and decal sizes are never touched.\n\n## Import\n\n```ts\nimport { reconcileProportions } from '../util/spec-passes/proportions';\n```\n\n## Usage\n\nCalled by the studio's `reconcileDraftProportions` after `renderDraftAndMeasure`, not in `runStructurePasses`. Receives (parsed, analysis, measured, skipRefs); returns `{ logs, residual }` — the mean deviation the auto-verify loop gates on.\n",
"ref": {
"module": "../util/spec-passes/proportions",
"name": "reconcileProportions"
},
"specType": null,
"containedExamples": [],
"cardTitle": "reconcileProportions",
"cardDescription": null,
"cardInfo": {
"name": null,
"summary": null,
"cardThumbnailURL": null,
"notes": null
}
},
"meta": {
"adoptsFrom": {
"module": "@cardstack/base/spec",
"name": "Spec"
}
}
}
}
Loading
Loading