Native IFC collaboration tools: a git-aware IFC viewer integrated into an IFC-aware forgejo mod. Tied together with an ifc:// URL scheme that allows sharing exact IFC versions and viewpoints as persistent links
Find a file
Bruno Postle 502e497d92 Add GET /preview endpoint for HTML img tag use
POST /preview requires a JSON body so cannot be used in <img src>.
The new GET /preview?url=... delegates to the same handler and shares
all caching and authentication behaviour.
2026-04-16 00:43:40 +01:00
dev Add local Gitea dev environment 2026-04-15 23:38:07 +01:00
ifcurl Add GET /preview endpoint for HTML img tag use 2026-04-16 00:43:40 +01:00
tests Fix port preservation and HTTP fallback for local Gitea 2026-04-15 23:50:54 +01:00
.gitignore Add local Gitea dev environment 2026-04-15 23:38:07 +01:00
CLAUDE.md Preview service and improved CLI help 2026-04-15 21:46:50 +01:00
LICENSE Python core library and ifcurl CLI 2026-04-15 21:24:35 +01:00
PLAN.md Initial commit 2026-04-15 20:08:52 +01:00
pyproject.toml Preview service and improved CLI help 2026-04-15 21:46:50 +01:00
README.md Authentication and test coverage 2026-04-15 23:21:13 +01:00
SPECIFICATION.md Initial commit 2026-04-15 20:08:52 +01:00

ifcurl

ifcurl is a command-line tool and Python library for resolving ifc:// URLs — a URL scheme for addressing a specific view of an IFC building model stored in a git repository.

A single URL encodes the model source (git host, repository, ref), an optional element selection, and an optional camera viewpoint. ifcurl fetches the model and renders a PNG, the way curl fetches a web resource.

ifcurl render "ifc://github.com/brunopostle/simple-ifc@heads/main?path=_test_simple.ifc"

URL format

ifc://[user@]host/org/repo@<ref>?<parameters>

Transport

Transport is inferred from the URL structure — no prefix required:

URL form Transport
ifc://host/org/repo HTTPS
ifc://git@host/org/repo SSH
ifc:///path/to/repo Local file

Ref

Form Meaning
@heads/main Branch tip
@tags/v1.2 Tag
@abc123def Commit hash
@HEAD Default branch

Parameters

Parameter Description
path= Path to the IFC file within the repository
selector= IfcOpenShell selector expression to filter elements
camera=px,py,pz,dx,dy,dz,ux,uy,uz Camera position, view direction, and up vector in IFC world coordinates
fov=60 Perspective field of view in degrees (use with camera)
scale=50 Orthographic view-to-world scale (use with camera)
clip=px,py,pz,nx,ny,nz Clipping plane — repeatable
visibility= highlight (default), ghost, or isolate

Visibility modes

Mode Behaviour
highlight All elements shown; selected elements shown in highlight colour
ghost Selected elements shown normally; all others dimmed
isolate Only selected elements shown

Examples

Open a model at the default isometric view:

ifcurl render "ifc://github.com/brunopostle/simple-ifc@heads/main?path=_test_simple.ifc"

Walls only, ghost mode:

ifcurl render "ifc://github.com/brunopostle/simple-ifc@heads/main?path=_test_simple.ifc&selector=IfcWall&visibility=ghost"

Perspective view with a clipping plane (section cut at z=3):

ifcurl render "ifc://example.com/org/project@abc123def?path=models/building.ifc&camera=10,20,5,0,-1,0,0,0,1&fov=60&clip=0,0,3,0,0,-1"

Orthographic plan view from above:

ifcurl render "ifc://example.com/org/project@tags/v2.0?path=models/building.ifc&camera=0,0,8,0,0,-1,0,1,0&scale=50"

Local repository:

ifcurl render "ifc:///home/alice/projects/office@HEAD?path=model.ifc"

Save to a specific file:

ifcurl render "ifc://..." -o output.png

Installation

pip install "ifcurl[render]"    # CLI rendering tool
pip install "ifcurl[service]"   # preview service (includes render)

Dependencies:


Python library

from ifcurl import IfcUrl, fetch_ifc
from ifcurl import render as render_mod
import ifcopenshell
import tempfile, os

url = IfcUrl.parse(
    "ifc://github.com/brunopostle/simple-ifc@heads/main"
    "?path=_test_simple.ifc&selector=IfcWall&visibility=ghost"
)

# fetch_ifc returns (commit_hexsha, ifc_bytes) — the hexsha is useful
# as a cache key even when the URL uses a mutable ref like a branch
hexsha, ifc_bytes = fetch_ifc(url)

tmp_fd, tmp_path = tempfile.mkstemp(suffix=".ifc")
os.write(tmp_fd, ifc_bytes)
os.close(tmp_fd)
model = ifcopenshell.open(tmp_path)
os.unlink(tmp_path)

png_bytes = render_mod.render(
    model,
    selector=url.selector,
    camera=url.camera,
    fov=url.fov,
    scale=url.scale,
    clips=url.clips or None,
    visibility=url.visibility,
)

with open("output.png", "wb") as f:
    f.write(png_bytes)

Preview service

ifcurl includes an HTTP preview service for use by Gitea and other consumers.

ifcurl serve                          # 127.0.0.1:8000
ifcurl serve --host 0.0.0.0 --port 9000

Endpoint

POST /preview
Content-Type: application/json

{"url": "ifc://...", "token": "optional-git-token"}

Returns image/png.

The optional token field is a bearer token for git authentication. When provided it takes precedence over any token in the config file. Intended for co-located Gitea deployments that pass the requesting user's session token.

Authentication

For private repositories, configure a token per host in ~/.config/ifcurl/tokens.json (Linux/macOS) or %APPDATA%\ifcurl\tokens.json (Windows):

{
    "hosts": {
        "github.com": "ghp_your_token_here",
        "gitlab.example.com": "glpat_your_token_here"
    }
}

Tokens are injected into HTTPS remote URLs (https://<token>@host/path) for clone and fetch operations and are never written to the on-disk git config. SSH transport uses the platform key store and ignores this config.

Service caching

Tier Key Contents Notes
2 commit hash + path IFC bytes In-memory LRU, avoids repeated git blob reads
3 commit hash + path + selector Resolved GlobalId set In-memory LRU, avoids re-running selector
4 SHA-256 of full URL Rendered PNG Filesystem, immutable refs only

Tier 4 is never written for mutable refs (@heads/, @HEAD) since the underlying commit may change.


Remote repository caching

Remote repositories are cloned as bare repos to the OS cache directory (~/.cache/ifcurl/ on Linux, ~/Library/Caches/ifcurl/ on macOS, %LOCALAPPDATA%\ifcurl\Cache\ on Windows) on first use. Subsequent calls to the same repository reuse the cache. Mutable refs (@heads/, @HEAD) trigger a git fetch to pick up upstream changes; immutable refs (commit hashes, tags) use the cache as-is.


Development

git clone https://github.com/brunopostle/ifcurl
cd ifcurl
pip install -e ".[service]"
python -m pytest tests/

Licence

LGPL-3.0-or-later