Novolis CAD Studio 3D — Implementation Plan
Policies that keep the org coherent
name: CAD Studio 3D overview: Deliver Novolis CAD Studio 3D as App → Controls → Libraries with extensive Agent Surfaces so every user-visible action is also an LLM-callable session action (native UI↔agent parity). Evolve Draft Studio, add Cad.SceneBridge, complete 2D+3D CAD drafting/appearance, and stage/render via Avalonia.3D. todos:
- id: m1-bridge-lib
content: "Add Novolis.Cad.SceneBridge: move tessellator, wall/space tessellate, ToSceneDocument/SaveNov3dJson + unit tests; regen Platform map" status: completed
- id: m2-cad-controls
content: "Avalonia.Cad: exportscene, setmaterial/setwallside, Wall+Dimension+extrudeprofile tools, editable property panel — all via session Execute" status: completed
- id: m3-scene-material
content: "Avalonia.3D: setmeshmaterial + render/stage actions; inspector/UI only calls session" status: completed
- id: m4-agent-parity
content: "Agent Surface parity gate: full Cad+Scene action catalogs, UI-through-Execute rule, snapshot/describe/dump, HTTP smoke mirroring UI flows" status: completed
- id: m5-app-shell
content: "Evolve DraftStudio → CadStudio3D: dual Cad+Scene AgentSurface attach, Draft2D/3D/Model/Stage workspaces, in-memory bridge" status: completed
- id: m6-smoke-publish
content: Automated agent+bridge+PNG smoke, agent catalog docs, cadjson.md, NuGet-only verify + publish status: completed isProject: false
Product definition (locked)
Novolis CAD Studio 3D is one app for beginner/intermediate 2D and 3D technical CAD drafting, appearance (sides/materials), limited mesh modelling, staging (lights/cameras), and lit PNG render.
Agent Surface (locked): LLM interaction mirrors user interaction natively. Every mutation or mode change a human can perform in the Studio UI is available as a catalogued session Execute action (Cad and/or Scene). UI chrome is a thin client over the same IAgentHost / session services the LLM uses — not a parallel code path. Perception for the LLM uses the same dumps/snapshots a power user would (plan/model/viewport PNGs, document snapshot, describescene / Cad snapshot fields).
Architecture rule (no exceptions in this plan):
flowchart TB
app[App_CadStudio3D]
cadCtrl[Controls_AvaloniaCad]
sceneCtrl[Controls_Avalonia3D]
cadLib[Libraries_CadPrimitives]
bridge[Libraries_CadSceneBridge]
sceneLib[Libraries_ModelingScene]
mathLib[Libraries_MathGeometry]
agentCore[Libraries_AgentCore_Surface]
app --> cadCtrl
app --> sceneCtrl
app --> agentCore
cadCtrl --> cadLib
cadCtrl --> bridge
cadCtrl --> agentCore
sceneCtrl --> sceneLib
sceneCtrl --> agentCore
bridge --> cadLib
bridge --> sceneLib
bridge --> mathLib
sceneLib --> mathLib- App never contains domain algorithms (tessellate, bake, assign rules); App does attach Agent transports for both Cad and Scene sessions.
- Controls never write
.nov3djson/.cadjsonformats by hand — they call Libraries. - Controls never mutate the document only in UI event handlers — they call
session.Execute(...)(or a shared command helper that does) so agent and UI stay identical. - Libraries never reference Avalonia or app hosts.
- Agent catalogs are exhaustive for product-facing tools: if a toolbar button or property edit exists, an
[AgentAction]/CadSessionActionIds/SceneSessionActionIdsentry exists with the same parameters.
App home (locked): Evolve `novolis-apps/src/DraftStudio` into the product (display name Novolis CAD Studio 3D). SceneLab remains dogfood for Avalonia.3D only.
Document truth (locked): Authoring document stays .cadjson (CadDocument). Stage/render/mesh-edit work on a derived .nov3djson (SceneDocument) produced by the bridge. Round-trip Scene→Cad is out of scope for v1.
Phase 0 — Library foundation: `Novolis.Cad.SceneBridge`
Repo: `novolis-cad`
New packable project: src/Novolis.Cad.SceneBridge/Novolis.Cad.SceneBridge.csproj
PackageId: Novolis.Cad.SceneBridge
Deps: Novolis.Cad.Primitives, Novolis.Modeling.Scene, Novolis.Math.Geometry only.
0.1 Move Avalonia-free tessellation into the library
Today `CadSolidTessellator` already depends only on Cad.Primitives + Math.Geometry but lives under Avalonia.Cad.
- Move type to
Novolis.Cad.SceneBridge.Tessellation.CadSolidTessellator(same API:TryTessellate(CadEntity)→EditableMesh?). - Keep a thin type-forward shim in Avalonia.Cad (
using CadSolidTessellator = Novolis.Cad.SceneBridge...or one-line wrappers) so existingCadModelEvaluator/ phys paths compile without behavior change. - Unit tests in
novolis-cad/tests/Novolis.Cad.Unitfor box/sphere/cylinder/stored mesh (golden vertex counts + AABB).
0.2 Wall / space tessellation (3D CAD → mesh)
Add CadWallTessellator / CadSpaceTessellator in the same package:
- Wall: extrude
Points(or A–B segment) byThickness/Height/Deckusing existing helpers in `CadDocument.cs` /CadVec/OpeningDerivation— produce closedEditableMeshslabs (openings cut whereOpeningDerivationalready defines them). - Space: floor (+ optional ceiling slab) from footprint
Pointsat deck elevation. - Skip unknown kinds (return null); never throw on unsupported entities.
0.3 Bridge API (complete, not a stub)
public static class CadSceneBridge
{
public static SceneDocument ToSceneDocument(CadDocument cad, CadSceneBridgeOptions? options = null);
public static void SaveNov3dJson(CadDocument cad, string path, CadSceneBridgeOptions? options = null);
}Behavior:
SceneDocument.CreateEmpty(); name from Cad document name.- For each tessellatable entity (solids first pass types: box/sphere/cylinder/mesh; then wall/space): create
MeshNode,MeshEditBake.WriteBaked, set transform identity (mesh already world-baked) or preserve Cad transform consistently — pick world-baked verts to match phys exporter mental model. - Materials: if
CadEntity.Materialis set, ensure aMaterialNode(color from material name lookup table or default albedo) and setMeshNode.MaterialId. Wall sides A/B: ifSides.A/B.ShapeIdpresent, create/attach materials named by shape id (color from.cadshapejsonwhen path provided in options; else distinct placeholder colors). - Copy Cad cameras/lights entities when kind is
camera/lightinto SceneCameraNode/LightNodewith best-effort field mapping (position, intensity, kind). - Add default Key/Fill/Rim only when
options.EnsureStudioLightsand scene has zero lights. SceneEvaluator.SaveforSaveNov3dJson.
Tests: Cad fixture with 1 box + 1 wall + 1 material string → SceneDocument with expected node counts; round-trip file load via SceneEvaluator.Load.
0.4 Platform wiring
- Regenerate package map / Platform slnx after adding the packable project (`Generate-Platform-Slnx.ps1`).
- PackageReference only across repos; local iteration via
NovolisUseProjectReferences=true. - Publish
Novolis.Cad.SceneBridgeto GitHub Packages with the rest of novolis-cad (no local feeds).
Phase 1 — Controls: Cad export + material assign + 2D/3D draft tools
Repo: `novolis-avalonia/src/Novolis.Avalonia.Cad`
1.1 Session actions (complete implementations) — UI and LLM share these
Extend `CadSessionActionIds` + CadSessionService.Execute. Every new UI control in this phase calls these actions (no direct CadDocument mutation from click handlers except through Execute).
| ActionId | Behavior | |
|---|---|---|
| `exportscene` | `CadSceneBridge.SaveNov3dJson(doc, path)`; path required (or default beside `.cadjson`) | |
| `bridgescene` | In-memory `ToSceneDocument` result handed to App/Scene session (same as entering Model workspace) | |
| `setmaterial` | `nodeId` + `material` string on entity | |
| `setwallside` | `nodeId` + `side`=`A`\ | `B` + `shapeId` (updates `CadWallSides`) |
| `addwall` | Creates wall entity from `points` JSON or A/B + thickness/height/deck | |
| `extrudeprofile` | Closed polyline `points` + `height` → wall-or-solid entity (v1: create `wall` loop or `box` when rect) | |
| `adddimension` | Store linear dim entity (`kind=dimension`, two points + offset) in CadDocument | |
| `addline` / `addcircle` / `addrect` / `addspline` | Parametric create matching sketch tools (so LLMs need not drive pointer state) | |
| `settool` / `setworkspace` / `setviewmode` / `setsnap` / `setgrid` | Already present — keep as the only way UI switches modes | |
| `exportplanpng` / `exportmodelpng` / `exportpreviewpng` | Already present — primary LLM perception for Cad |
Wire each through BuildActions() with Summary/Params rich enough for LLM tool choice (human-readable summaries, required vs optional params). Enable/disable mirrors UI (e.g. deleteselection disabled with no selection).
1.2 UI Controls (no stub buttons; Execute-only)
- Export Scene… / Bridge to Model →
exportscene/bridgescene. - Property panel (`CadPropertyPanel`): editable Material / Side A/B →
setmaterial/setwallside. - Tools:
CadToolKindWall + Dimension; pointer completion commits viaaddwall/adddimension(LLM can skip pointer and call those directly). - Extrude: UI →
extrudeprofile. - Sketch strip buttons for Line/Circle/Rect/Spline either set tool or expose “place with params” that maps to
add*.
1.3 Tessellator consumers
Update Avalonia.Cad evaluation/phys to call Novolis.Cad.SceneBridge tessellators (remove duplicated geometry code after shim period).
1.4 Scene Controls: material bind + stage/render agent parity
- Add
setmeshmaterial(nodeIdmesh +materialId). - Ensure stage/render UI maps to session (or documented SceneRender session actions):
setactivecamera,matchviewport,addlight,addcamera,setlight,settransform, plus `openshaderender` / `saverenderpng` / `ensurestudiolights` if today those are UI-only — promote them toSceneSessionActionIdsso an LLM can finish the pipeline without clicking Render…. - Property inspector and Render chrome call Execute only.
- Keep
describescene,groundphrase,dumpviewport,dumpwindow,dumpsceneas LLM perception tools (already on Scene session).
Phase 1.5 — Agent Surface parity (extensive, native mirror)
Goal: An LLM using Cad HTTP :18775 and Scene HTTP :18785 (plus TCP/MCP attach) can perform the same product workflows as a human in CadStudio3D without special “agent-only” APIs.
Parity rule (enforced)
- Single write path: Document mutations go through
CadSessionService.Execute/SceneSessionService.Execute. - Catalog completeness: For every product toolbar/menu/property control shipped in Phases 1–2, there is a matching action id in the session catalog with params the LLM can fill without UI state (coordinates, ids, enums as strings).
- Pointer tools have parametric twins: Interactive Wall/Line/Dimension tools remain for humans;
addwall/addline/adddimension/ etc. are the LLM-native equivalents (same resulting entities). - Workspace/mode is agent-visible:
setworkspace, App-level Draft2D/Draft3D/Model/Stage switch exposed as Cad or App session actions (setstudioworkspaceon Cad session or a thin App host action forwarded to both). - Perception: LLM can
snapshot+ export/dump PNGs after each step; Cad snapshot includes selection, workspace, tool, entity counts; Scene keepsdescribescene/ dumps. - Parity gate test: Unit/integration test lists UI-backed action ids (source-generated or hand-maintained allowlist in Controls) and asserts
Actions()returns each id withEnabledsemantics documented. Fail CI if a chrome command invokes a private mutate helper not registered as an action. - MCP / HTTP: CadStudio3D attaches both
AgentSurfaces (same as today DraftStudio + SceneLab). Document the dual-port workflow in app README: Cad for draft/appearance/bridge; Scene for mesh/stage/render. No third protocol.
LLM-native end-to-end script (also the smoke)
Cad: new → setworkspace Cad → addrect → extrudeprofile → setmaterial → exportplanpng
Cad: exportscene / bridgescene
Scene: ensurestudiolights → setactivecamera → matchviewport → saverenderpng → dumpviewportAll steps are Execute calls; no UI required.
Phase 2 — App: Novolis CAD Studio 3D host
Repo: `novolis-apps/src/DraftStudio`
2.1 Product identity
ApplicationTitle/ window title / installer display name → Novolis CAD Studio 3D.- Keep assembly name
DraftStudioor rename toCadStudio3Din the same folder with InternalsVisibleTo/test project updates — choose rename to `CadStudio3D` for clarity; updateDraftStudio.Unit→CadStudio3D.Unit, Inno packaging refs, and any installer scripts that mention Draft Studio.
2.2 Package references
Add (ProjectRef-friendly PackageReferences):
Novolis.Avalonia.3DNovolis.Modeling.SceneNovolis.Cad.SceneBridgeNovolis.Agent.Core/Novolis.Agent.Surfaceas required by Scene attach (same pattern as SceneLab)
2.3 Workspaces (one shell)
Extend host UI beyond Cad’s Cad/Modeling/Preview triad with an App-level mode switcher:
| Workspace | Hosts |
|---|---|
| **Draft 2D** | Existing `CadEditorSurface` plan focus (`CadWorkspace.Cad` + draft viewport) |
| **Draft 3D** | Same surface with model/orbit emphasis (`setviewmode` / Raylib model) |
| **Model** | After export or in-memory bridge: `SceneEditorSurface` for limited mesh ops |
| **Stage / Render** | Same `SceneEditorSurface` + open Render window / Studio lights |
Implementation detail:
- Single
MainWindowownsCadSessionService+SceneSessionService. - Sync rule: On entering Model/Stage, if Cad doc dirty or no scene loaded, run
CadSceneBridge.ToSceneDocumentintoSceneSessionService.ReplaceDocument(in-memory). Export Scene… also writes.nov3djsonfor persistence. - Cad remains source of truth until user explicitly Saves Cad; Scene edits after bridge are mesh-side only (document in status bar which document is active).
2.4 Agent surfaces (extensive attach)
- Attach Cad
AgentSurfaceon:18775/:18776for the full Cad action catalog (draft 2D/3D, appearance, bridge/export, Cad dumps). - Attach Scene
AgentSurfaceon:18785/:18786whenever the Scene session exists (mesh, lights, cameras, render/save, Scene dumps). - App README documents: “LLM uses the same actions as the UI”; include the Phase 1.5 script and port map.
- App does not invent a third protocol; MCP tools forward to these catalogs only.
2.5 Smoke (complete path, automated — agent-first)
Primary smoke is HTTP/session Execute, not UI automation:
- Cad:
new→addrect→extrudeprofile→setmaterial→exportplanpng - Cad:
exportscene(orbridgescene+ SceneReplaceDocumentvia host test hook) - Scene:
ensurestudiolights→ camera/frame actions →saverenderpng/dumpviewport - Assert PNGs exist and byte length > threshold; assert
Actions()contains the parity allowlist
Library-only unit tests remain for CadSceneBridge without transports.
Phase 3 — Hardening and governance
- Update `novolis-governance/docs/cadjson.md`: Cad→
Novolis.Cad.SceneBridge→.nov3djsonpipeline. - Add short Agent parity note under Cad Studio / Avalonia.Cad + Avalonia.3D READMEs: UI↔Execute rule, dual ports, example LLM script.
- Update canvas checklist when phases land.
gpr-health-check/verify-nuget-only/verify-project-ref-mode -SkipBuildbefore claiming done.- Publish order:
Cad.SceneBridge→ Avalonia.Cad / Avalonia.3D → CadStudio3D app.
Explicit non-goals (v1)
- DWG/DXF, paperspace, full dim styles
- Scene→Cad round-trip
- Path-trace as default render
- Sculpt, UV, animation timeline, MoGraph effectors
- Merging Cad and Scene into one file format
- Putting tessellation or bridge code in the App project
- Agent-only “magic” actions that have no UI counterpart (or UI-only mutations with no agent counterpart)
- Embedding an LLM inside Libraries (external LLM calls HTTP/MCP only)
Delivery order (milestones)
- M1 — Bridge library (Phase 0): packable, tested, solids + walls tessellate,
ToSceneDocument/SaveNov3dJson. - M2 — Cad Controls + Cad agent actions (Phase 1.1–1.3): export/bridge, wall/dim/extrude, materials; UI via Execute.
- M3 — Scene material + render/stage agent actions (Phase 1.4).
- M4 — Agent parity gate (Phase 1.5): catalog allowlist test, parametric twins, dual-port docs.
- M5 — App shell (Phase 2): CadStudio3D, dual AgentSurface attach, workspaces, in-memory bridge.
- M6 — Smoke + docs + publish (Phase 2.5 + 3): agent-first E2E script green.
Each milestone ends with dotnet build under ProjectRef and a green unit/smoke test — no UI-only “TODO” actions and no agent-blind UI mutations.