Initial commit

This commit is contained in:
Bruno Postle 2026-04-15 20:08:52 +01:00
commit 3d2ff476d6
2 changed files with 479 additions and 0 deletions

203
PLAN.md Normal file
View file

@ -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.

276
SPECIFICATION.md Normal file
View file

@ -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/<name>` | `@heads/main` |
| Tag | `@tags/<name>` | `@tags/v1.2` |
| Commit hash | `@<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@<ref>?<parameters>
```
---
## 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
```