- ifcurl/bcf.py: build_bcf() builds BCF 2.1 zips with perspective/
orthographic camera, clipping planes, component selection, and
isolate visibility mode
- ifcurl/service.py: /bcf POST endpoint parses ifc:// URL, resolves
selector → GUIDs when present, delegates to build_bcf()
- viewer.html: BCF toolbar button + collapsible title/comment form;
client-side zip generation via JSZip (esm.sh CDN) so the browser
does not need service access
- tests/test_bcf.py: 21 tests covering build_bcf() unit behaviour and
the /bcf endpoint including SSRF rejection and selector resolution
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Investigation confirmed: Forgejo's git merge-tree --write-tree (git ≥2.38)
does invoke configured merge drivers for .ifc files.
Key finding: bare repositories do NOT read committed .gitattributes — the
merge driver is silently skipped without a system-wide core.attributesFile.
Changes:
- server-config/gitattributes: new file to deploy at /etc/gitattributes,
containing "*.ifc merge=ifcmerge"
- server-config/gitconfig-ifcmerge: adds [core] attributesFile = /etc/gitattributes
and explains the bare-repo constraint
- forgejo/README.md: rewrites ifcmerge section with investigation findings,
deployment steps, and distinction between server-side vs client-side setup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- forgejo/server-config/ifcurl-preview.service: systemd unit for the preview
service with dedicated user, EnvironmentFile for cache config, and
--allowed-hosts in ExecStart
- forgejo/server-config/gitconfig-ifcmerge: registers two [merge] driver
variants (ifcmerge and ifcmerge_ours) for the asymmetric .ifc merge case
- forgejo/README.md: systemd deployment section (manual + service), ifcmerge
section covering Perl script install, driver registration, per-repo
gitattributes, and merge-direction constraint (merge-commit preserves main's IDs)
- README.md: remove stale "IFC file proxy" description; update quick-summary
to show systemd install path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
viewer.html, viewer-url.js, and footer.tmpl are intended for Forgejo
contribution and carry MIT headers matching Forgejo's per-file convention.
ThatOpen @thatopen/components (loaded from CDN) is also MIT-licensed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The preview endpoint is co-hosted with a specific Forgejo instance, so it
should only be allowed to fetch from that host.
- Local file transport (ifc:///path) is always rejected with 403 — the service
must not read from the server's own filesystem
- New --allowed-hosts CLI option for `ifcurl serve` takes a comma-separated
list of permitted git hostnames (e.g. git.example.com or localhost:3000);
any unlisted host is rejected with 403
- Defense-in-depth: literal private/loopback/link-local IP addresses are
blocked even without an allowlist (169.254.x.x, 127.x, RFC1918, etc.)
- README and forgejo/README.md updated to include --allowed-hosts in the
standard deployment invocation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add the missing Go source files and a README covering the full apply
procedure. Previously only custom assets and the test file were tracked
here; ifc_url.go and the two-file diff were only in the Forgejo tree.
- forgejo/modules/markup/markdown/ifc_url.go — goldmark extension source
- forgejo/go.patch — diff for markdown.go + markup.go (7 lines total)
- forgejo/README.md — step-by-step: apply patch, run tests, build,
deploy assets, configure app.ini, upgrade procedure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7 tests in forgejo/modules/markup/markdown/ifc_url_test.go covering:
link form, bare URL, bare URL with underscores (sibling-node reassembly),
disabled when no service URL configured, HTML escaping, non-ifc links
unchanged, and inline-among-text placement.
Tests use goldmark.New() directly — no Forgejo DB/git test infrastructure
needed. Only dependencies are setting.IfcURL (ours) and goldmark (stable).
File lives in the same package as ifc_url.go and is part of the patch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
parseIfcUrl, buildIfcUrl, toRawUrl extracted from viewer.html into
viewer-url.js so they can be imported and tested independently.
viewer.html imports from the module. 32 Node test-runner tests in
tests/test_viewer_url.mjs cover all three functions across GitHub,
GitLab, Forgejo/Gitea, SSH user@ prefix, and round-trip cases.
Caught a bug: + (selector union operator) was being URL-encoded as
%2B in buildIfcUrl; fixed by adding .replace(/%2B/g, "+") alongside
the existing comma fix, in both viewer-url.js and syncCameraUrl.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the raw renderer.clippingPlanes approach with OBC.Clipper:
URL-specified clip= planes are seeded as draggable SimplePlanes.
Toolbar '✂ clip' toggle + double-click on model surface to add planes;
'⊗ clips' button (shown when planes exist) to clear all. syncCameraUrl
now serialises current plane state back to the ifc:// URL on every
camera move or plane drag/create/delete.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
applyClipPlanes() parses all repeatable clip= params (6 floats: point +
normal in IFC world coords), transforms each via toThree() into Three.js
space, and sets world.renderer.three.clippingPlanes. syncCameraUrl already
preserves clip= params when rebuilding the URL on camera movement.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
applySelector() classifies elements via OBC.Classifier.byCategory(), then
maps the selector type names to matching categories using prefix matching
(so IfcWall also covers IfcWallStandardCase), and calls hider.isolate() to
show only matching elements. Supports simple type names and + union syntax.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a second toolbar row with labelled fields for repo, ref, path,
and selector. Editing any field rebuilds and reloads the ifc:// URL.
The raw URL in row 1 remains editable and copyable. Closes ifcurl-yju.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- toRawUrl() now handles SSH user@ prefix, GitHub (raw.githubusercontent.com),
GitLab (/-/raw/), and Forgejo/Gitea; strips heads/tags/ prefix for hosts
that don't use gitnamespace form
- FOV input in toolbar: shows current FOV, editing it updates the camera
and syncs the fov= param in the ifc:// URL
- applyCameraParam now restores fov= from URL when placing camera
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Shrink viewer div to start below toolbar (CSS var --toolbar-h via
ResizeObserver) so the canvas never overlaps the input area
- Disable camera controls on input focus, re-enable on blur
- Stop pointer event propagation from input to prevent camera-controls
capture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace url-display span with editable input; Enter or drag-and-drop loads
a new ifc:// URL by navigating to the same page with updated ?url= param
- Fix syncCameraUrl: convert Three.js → IFC world coords (x,-z,y) before
serialising camera= param so URLs are in Z-up IFC space
- Apply IFC → Three.js transform (x,z,-y) when placing camera from URL param
- Drop viewer.py: viewer is now a Forgejo static file; service.py has no
/viewer or /proxy endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
viewer.html is now served directly from Forgejo at /assets/viewer.html,
fetching IFC bytes via Forgejo's raw URL (same-origin, auth inherited).
footer.tmpl updated to point to /assets/viewer.html instead of the
ifcurl service. No proxy needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /viewer?url=ifc://... serves a self-contained HTML page that loads
@thatopen/components v3 from CDN and renders the IFC model in WebGL
- GET /proxy?url=<raw> proxies IFC bytes from Forgejo to the browser,
avoiding CORS restrictions
- forgejo/templates/custom/footer.tmpl injects a "View in 3D" button on
.ifc file view pages in Forgejo; deploy to
/var/lib/forgejo/custom/templates/custom/footer.tmpl
CDN loading required several non-obvious fixes documented in viewer.py:
web-ifc loaded from unpkg (not esm.sh) to avoid the Node.js process polyfill
that breaks the browser ST build's environment check; fragments worker wrapped
in a blob URL for Firefox cross-origin worker compatibility; model.box used for
camera fit before tiles exist; fragments.core.update(true) to flush tile queue.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>