diff --git a/CLAUDE.md b/CLAUDE.md index e9b9ae3..fe83b26 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,7 +19,7 @@ ifcurl serve --host 0.0.0.0 --port 9000 `ifcurl` implements the `ifc://` URL scheme — a way to address a specific view of an IFC model stored in a Git repository. A single URL encodes the git source, an optional IfcOpenShell selector, and an optional camera viewpoint. -Full spec: `SPECIFICATION.md`. Full development plan: `PLAN.md`. +Full spec: `SPECIFICATION.md`. Development tasks tracked in beads (`bd ready`). ## URL structure diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index 1ed2fb8..0000000 --- a/PLAN.md +++ /dev/null @@ -1,285 +0,0 @@ -# IFC URL — Development Plan - -## Overview - -IFC URL requires work across four distinct areas: - -1. A Python core library and CLI tool -2. A preview service with Gitea integration -3. Desktop app integration (Bonsai, IFC Viewer) -4. The federation extension to `IFCDOCUMENTREFERENCE` - -The Python core is the foundation for all other work. The Gitea integration -and desktop app work can proceed in parallel once the core exists. - -### Language ownership - -| Component | Language | Rationale | -|---|---|---| -| URL parsing, git fetch, selector execution, rendering | Python | Built on IfcOpenShell | -| Bonsai plugin | Python | Bonsai is a Blender/Python addon | -| CLI tool | Python | Wraps the core library | -| Preview service | Python | Wraps the core library | -| Gitea plugin | Go | Gitea is written in Go; the plugin only calls an HTTP endpoint | -| Web viewer integration | JavaScript | Future, not in current scope | - -The Python core is a strong candidate for eventual contribution to -IfcOpenShell, but this is not a prerequisite for any phase. - ---- - -## Phase 1 — Python core and CLI tool - -Build a Python library that resolves an `ifc://` URL and produces a rendered -PNG. This validates the spec against real IFC files and is the foundation for -all subsequent work. - -### Library responsibilities - -- Parse an `ifc://` URL into its components -- Infer git transport from URL structure (SSH, HTTPS, local) -- Fetch the IFC file from the repository at the specified ref -- Execute the IfcOpenShell selector against the loaded model -- Apply camera (perspective and orthographic), clipping planes, and - visibility mode -- Render a static PNG - -### CLI interface - -```bash -ifc-url render "ifc://example.com/org/repo@abc123?path=model.ifc&selector=IfcWall&fov=60" -ifc-url render "ifc://example.com/org/repo@heads/main?path=model.ifc&camera=0,0,8,0,0,-1,0,1,0&scale=50" -``` - -### Notes - -- IfcOpenShell handles selector execution, model parsing, and geometry -- Rendering requires a headless approach; options include IfcOpenShell's own - geometry processing with a software or headless OpenGL renderer -- Git access uses the platform credential store for HTTPS and ssh-agent or - key files for SSH -- The CLI accepts the URL as a plain string so it can be called from - non-Python contexts - ---- - -## Phase 2 — Preview service - -Wrap the Phase 1 library in an HTTP service for use by Gitea and other -consumers. - -### Endpoint - -``` -POST /preview -body: { url: "ifc://..." } -response: image/png -``` - -### Caching - -| Tier | Cache key | Contents | Notes | -|---|---|---|---| -| 2 | commit hash | Parsed model structure | Shared across requests | -| 3 | commit hash + selector | Resolved GUID set | | -| 4 | full URL hash | Rendered PNG | Not applied to mutable refs | - -Mutable refs (`@heads/`, `@HEAD`) must not be cached at tier 4 since the -underlying commit may change. Commit hash refs are immutable and can be -cached indefinitely at all tiers. - -### Authentication - -- **Co-located with Gitea** — uses the requesting user's Gitea session token, - passed from the Gitea plugin -- **Standalone** — sideloaded token configuration per git host - ---- - -## Phase 3 — Gitea integration - -Add `ifc://` link detection and preview embedding to Gitea. - -### Go plugin responsibilities - -- Detect `ifc://` links in post and comment content -- Call the preview service with the URL and the current user's session token -- Embed the returned PNG inline with a link that opens the URL -- Degrade gracefully when the preview service is unavailable (show the raw - URL rather than failing) - -### Notes - -- The plugin has no IFC awareness; all IFC logic lives in the preview service -- Repeated embeds of the same URL across different posts do not trigger - re-renders — the preview service cache handles deduplication -- The preview service may run as a companion process on the same host or - as a separately configured external service - ---- - -## Phase 3b — Web IFC viewer - -Add a 3D viewer that opens in a separate browser window when viewing an `.ifc` -file in Forgejo. - -### Architecture - -- **Forgejo Go patch**: when rendering the file view for a `.ifc` file, inject - a "View in 3D" button that opens - `http://localhost:8000/viewer?url=ifc://...` in a new tab. The ifc:// URL - encodes the current host, repo, ref, and file path. - -- **`/viewer` endpoint** in the ifcurl preview service: returns a self-contained - HTML page. No server-side IFC processing — the browser does all rendering. - -- **Browser rendering**: the viewer page loads `@thatopen/components` (and its - `web-ifc` + Three.js peer dependencies) from a CDN via an ES module importmap. - It constructs the Forgejo raw file URL from the ifc:// URL parameters and - fetches the IFC bytes directly from Forgejo. - -### Current scope (Phase 3b) - -- Display the full model for whatever ref is shown in Forgejo. -- Basic orbit/pan/zoom camera controls. -- No filtering or selection UI yet. - -### Future scope - -- Element filtering and selection using `@thatopen/components` classifier and - highlighter APIs. -- Camera state serialised to an `ifc://` URL so the user can copy a view link - (feeds into Phase 4 Bonsai "Copy view URL"). -- Diff highlighting driven by ifcmerge output (see Phase 3c). - ---- - -## Phase 3c — ifcmerge integration - -`ifcmerge` is a git custom merge driver that resolves conflicts in `.ifc` files -that git's built-in 3-way merge cannot handle. Where two branches have both -modified an IFC file (a fork), git would report an unresolvable conflict; -ifcmerge performs a semantics-aware merge and produces a valid merged IFC file. - -### Git merge driver configuration - -Register ifcmerge as the git merge driver for `.ifc` files on the Forgejo -server: - -``` -# /etc/gitconfig or ~forgejo/.gitconfig -[merge "ifcmerge"] - name = IFC merge driver - driver = ifcmerge %O %A %B %L -``` - -``` -# .gitattributes in each repo, or server-side gitattributes -*.ifc merge=ifcmerge -``` - -With this in place, Forgejo's existing "merge automatically" path will invoke -ifcmerge via git when merging `.ifc` files, and the PR merge button will work -without any Go-side changes for the common case. - -### Forgejo integration - -- Verify that Forgejo's merge-ability check (the "these branches can be merged - automatically" indicator) correctly reflects ifcmerge's ability to merge - `.ifc` files — investigate whether the check runs a trial merge via git or - uses a heuristic. -- If needed, patch Forgejo to run a trial `git merge --no-commit` to get an - accurate result for IFC files. - -### 3D diff view (future) - -- After a merge completes, surface a "View merge in 3D" button in the PR using - the Phase 3b viewer. -- Use `@thatopen/components` highlighter to colour added, removed, and modified - elements differently, driven by comparing the merged IFC against either parent. - ---- - -## Phase 4 — Bonsai integration - -Add `ifc://` handling to Bonsai. - -### Protocol handler - -Register `ifc://` as an OS protocol handler on installation (Windows, -macOS, Linux). On receiving a URL, resolve the git source, load the model, -and apply the view state from the URL parameters. - -### "Copy view URL" - -A button that generates an `ifc://` URL from the current Bonsai state: - -- Git remote, ref, and file path of the open model -- Active selection converted to an IfcOpenShell selector -- Current camera converted to the `camera` + `fov` or `scale` format, - in IFC world coordinates -- Active clipping planes as `clip` parameters -- Current visibility mode - -The button always generates a commit hash ref by default. Generating a -`@heads/` ref is a secondary option for authoring links intended to track -a branch. - -### Federation - -When opening a model via `ifc://`, Bonsai resolves `IFCDOCUMENTREFERENCE` -locations using the repo and ref from the root file's URL as context. -`ifc://` locations in linked model references are resolved recursively. - ---- - -## Phase 5 — IFC Viewer integration - -Equivalent to Phase 4 for the IFC Viewer desktop application. - ---- - -## Phase 6 — Federation extension - -Extend the `IFCDOCUMENTREFERENCE` location field in Bonsai to accept -`ifc://` URLs, enabling cross-repo and cross-server federation. - -### Tasks - -- When saving a linked model reference, offer the option to write the - location as an `ifc://` URL rather than a relative path -- When a relative path would traverse above the repository root, prompt - the user to convert it to an `ifc://` URL -- Resolve `ifc://` location fields through the Phase 4 URL resolver - -The transformation matrix in `IFCDOCUMENTREFERENCE` is unaffected by this -change. Phase 4 must be complete before this phase begins. - ---- - -## Future work - -### BCF export - -An additional preview service endpoint returning a BCF file: - -``` -POST /bcf -body: { url: "ifc://..." } -response: application/zip -``` - -Resolves the selector to a GUID set and constructs a BCF viewpoint from the -camera, clipping planes, and visibility state. No implementation is planned -before Phase 2 is complete. - -### Web viewer integration - -JavaScript integration with web-based IFC viewers (IFC.js, xeokit) and -browser-side forum embedding. Not in current scope. - -### IfcOpenShell contribution - -Once the Python core is stable, the URL parsing and resolution logic is a -candidate for contribution to IfcOpenShell as a shared utility. This is not -a prerequisite for any phase. diff --git a/ifcurl/viewer.py b/ifcurl/viewer.py index 252db64..8395809 100644 --- a/ifcurl/viewer.py +++ b/ifcurl/viewer.py @@ -74,6 +74,7 @@ VIEWER_HTML = """\
Loading\u2026