commit 3d2ff476d6f4441c2a0ed33c7389ff2784c0e1d1 Author: Bruno Postle Date: Wed Apr 15 20:08:52 2026 +0100 Initial commit diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..617547c --- /dev/null +++ b/PLAN.md @@ -0,0 +1,203 @@ +# 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 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/SPECIFICATION.md b/SPECIFICATION.md new file mode 100644 index 0000000..77f0862 --- /dev/null +++ b/SPECIFICATION.md @@ -0,0 +1,276 @@ +# IFC URL — Specification + +## 1. Overview + +IFC URL is a URL scheme for addressing a specific view of an IFC model stored +in a Git repository. A single URL encodes the model source, an optional +element selection, and an optional camera viewpoint. + +The scheme identifier is `ifc://`. + +--- + +## 2. Git source encoding + +### Transport inference + +The git transport is inferred from the URL structure. No transport prefix is +required. + +| URL form | Inferred transport | +|---|---| +| `ifc://user@host/org/repo` | SSH | +| `ifc://host/org/repo` | HTTPS | +| `ifc:///path/to/repo` | Local file | + +The presence of `user@` userinfo signals SSH. A bare hostname signals HTTPS. +An empty authority (triple slash) signals a local file path. + +Credentials are never embedded in the URL. Authentication is handled by the +platform's git credential store (desktop apps) or by sideloaded tokens +(preview service). + +SCP-style SSH addresses (`git@host:org/repo`) are normalised to +`ifc://git@host/org/repo` when generating URLs. + +### Ref format + +Refs follow git's namespace structure to prevent ambiguity between branches +and tags that share a name. + +| Ref type | Form | Example | +|---|---|---| +| Branch | `@heads/` | `@heads/main` | +| Tag | `@tags/` | `@tags/v1.2` | +| Commit hash | `@` | `@abc123def` | +| Default branch | `@HEAD` | `@HEAD` | + +The `@` character delimits the repository path from the ref. It is always +present. + +### Full structure + +``` +ifc://[user@]host/org/repo@? +``` + +--- + +## 3. Parameters + +All parameters are optional. A URL with no parameters opens the model at its +default view with no selection. + +### `path` + +Path to the IFC file within the repository, relative to the repository root. + +``` +path=models/architectural/building.ifc +``` + +May be omitted when the target is a federated root file whose linked model +references are defined within the IFC itself (see section 5). + +### `selector` + +An IfcOpenShell selector expression, percent-encoded. The selector syntax is +specified at: + + https://docs.ifcopenshell.org/ifcopenshell-python/selector_syntax.html + +Selectors are single-line expressions. Filters within a group are +comma-separated; groups are joined with `+` for union. + +``` +selector=IfcWall +selector=IfcWall,+Name="Core+Wall" +selector=IfcWall+IfcSlab +selector=IfcWall,+Pset_WallCommon.FireRating=2HR +selector=325Q7Fhnf67OZC$$r43uzK +``` + +When omitted, no selection is applied. + +### `camera` + +Nine comma-separated decimal values defining the camera in IFC world +coordinates (see section 4): + +``` +camera=px,py,pz,dx,dy,dz,ux,uy,uz +``` + +| Values | Meaning | +|---|---| +| `px,py,pz` | Camera position | +| `dx,dy,dz` | View direction vector (unit vector) | +| `ux,uy,uz` | Up vector (unit vector) | + +`camera` must be accompanied by either `fov` (perspective) or `scale` +(orthographic). Their presence signals the projection type. Exactly one must +be present if `camera` is present. + +When `camera` is omitted entirely, the viewer fits the view to the selection +if a selector is present, or shows the model's default view otherwise. + +### `fov` + +Field of view in degrees. Signals perspective projection. + +``` +fov=60 +``` + +### `scale` + +View-to-world scale. Signals orthographic projection. The value represents +how many model units correspond to one unit in the normalised view, equivalent +to BCF's `ViewToWorldScale`. + +``` +scale=50 +``` + +### `clip` + +A clipping plane, defined by a point on the plane and a normal vector pointing +toward the visible side. All values are in IFC world coordinates. + +``` +clip=px,py,pz,nx,ny,nz +``` + +Repeatable. Multiple `clip` parameters define the intersection of their +half-spaces. + +``` +clip=0,0,3,0,0,-1&clip=0,0,0,0,0,1 +``` + +### `visibility` + +Controls how selected and unselected elements are displayed. + +| Value | Behaviour | +|---|---| +| `highlight` | Show all elements, highlight selection (default) | +| `ghost` | Show selection normally, dim all other elements | +| `isolate` | Show only selected elements, hide all others | + +When omitted, `highlight` is assumed. + +--- + +## 4. Coordinate system + +All spatial values (`camera`, `clip`) are expressed in the IFC project's +world coordinate system, consistent with BCF viewpoint conventions. Local +coordinate systems, object placements, and georeferenced survey coordinates +are not used. + +Where an IFC file uses `IfcMapConversion` (IFC 4.1+) to define a +georeferenced coordinate system, camera and clipping plane values remain in +IFC world coordinates, not in the mapped survey coordinate system. + +--- + +## 5. Federation + +Federated models are referenced within the IFC file itself using +`IFCDOCUMENTINFORMATION` with a `LINKED_MODEL` purpose, associated with the +root `IFCPROJECT` entity via `IFCRELASSOCIATESDOCUMENT`. The location is +carried in `IFCDOCUMENTREFERENCE`: + +``` +#46471=IFCDOCUMENTINFORMATION('X','model.ifc',$,$,$,$,'LINKED_MODEL',$,$,$,$,$,$,$,$,$,$); +#46472=IFCRELASSOCIATESDOCUMENT('0mypTfsjT...',$,$,$,(#10510),#46471); +#46473=IFCDOCUMENTREFERENCE('../conservatory-ifc/model.ifc','<4x4 matrix>',$,$,#46471); +``` + +The second field of `IFCDOCUMENTREFERENCE` is a 4x4 transformation matrix +(sixteen comma-separated values) that positions the linked model in the shared +coordinate system. This is resolved entirely by the viewer. + +### Location field values + +The location field (first field of `IFCDOCUMENTREFERENCE`) supports: + +- **Relative path** — resolved relative to the root IFC file's directory + within the repository, using the same repo and ref context +- **Absolute local path** — resolved on the local filesystem (desktop use only) +- **`ifc://` URL** — resolved as a full IFC URL, enabling cross-repo and + cross-server federation + +When a relative path would traverse above the repository root, an `ifc://` +URL must be used instead. This is the primary motivation for supporting +`ifc://` in the location field. + +--- + +## 6. BCF interoperability + +The camera and selection in an IFC URL map directly to BCF viewpoint concepts. + +**IFC URL to BCF viewpoint:** +1. Resolve the selector against the model to obtain a set of GUIDs +2. Construct a BCF `PerspectiveCamera` or `OrthogonalCamera` from the camera + parameters +3. Add clipping planes if present +4. Populate `Components/Selection` with the resolved GUIDs + +**BCF viewpoint to IFC URL:** +1. Extract the GUID list from `Components/Selection` +2. Construct an IfcOpenShell GUID selector +3. Convert the BCF camera to `camera`, `fov` or `scale` parameters +4. Convert clipping planes to `clip` parameters + +--- + +## 7. Viewer compliance + +A compliant viewer must: + +- Register `ifc://` as an OS-level protocol handler +- Resolve the git source (repo, ref, path) and fetch the IFC file +- Resolve an IfcOpenShell selector against the loaded model +- Apply perspective camera (`camera` + `fov`) +- Apply orthographic camera (`camera` + `scale`) +- Apply clipping planes (`clip`) +- Apply visibility mode (`highlight`, `ghost`, `isolate`) +- Resolve relative `IFCDOCUMENTREFERENCE` paths using the root file's repo + and ref context +- Resolve `ifc://` `IFCDOCUMENTREFERENCE` locations + +Optional extensions: + +- Semantic colouring +- Advanced visual styles beyond the three visibility modes + +--- + +## 8. Complete examples + +Open a model at the default view: + +``` +ifc://example.com/org/project@heads/main?path=models/building.ifc +``` + +Perspective view of selected walls: + +``` +ifc://git@example.com/org/project@abc123def?path=models/building.ifc&selector=IfcWall,+Name="Core+Wall"&camera=10,20,5,0,-1,0,0,0,1&fov=60&visibility=ghost +``` + +Orthographic plan view at level 2 with a clipping plane: + +``` +ifc://example.com/org/project@tags/v2.0?path=models/building.ifc&camera=0,0,8,0,0,-1,0,1,0&scale=50&clip=0,0,5,0,0,-1 +``` + +Local file, default branch: + +``` +ifc:///home/alice/projects/office@HEAD?path=model.ifc +```