applySelector() now returns the resolved GUIDs array in both the simple
(ThatOpen classifier → getGuid()) and complex (/select → GUIDs already
fetched) paths. generateBcf() reads currentGuids directly and builds the
full BCF 2.1 zip in the browser, including <Selection>/<Exceptions> in
the viewpoint component list.
The Python POST /bcf endpoint, BcfRequest model, and build_bcf()/
_viewpoint_xml()/_markup_xml() functions are removed — the browser had
all the data needed to do this itself.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Capture the WebGL canvas client-side before POSTing to /bcf, send the
base64 PNG as a new `snapshot` field, and embed it as snapshot.png in
the zip alongside viewpoint.bcfv. Also wires up the <Snapshot> element
in markup.bcf when a snapshot is present.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
web-ifc disposes its IfcAPI after load so context filtering must happen
before the buffer reaches ifcLoader.load(). Pre-process the IFC-SPF buffer
to find IfcGeometricRepresentationSubContext entities with ContextIdentifier
'Clearance', then empty the items list of any IFCSHAPEREPRESENTATION that
references those contexts, so door-swing and equipment-clearance volumes
produce no geometry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Inline script at top of <body> creates the snapshot overlay immediately
on page load, before the deferred module script runs, eliminating the
black flash between navigation and overlay appearance
- Save a JPEG snapshot to sessionStorage before navigating to a new ref;
the overlay covers the blank WebGL canvas while the model reloads
- Fade the overlay out after two animation frames post-load so the canvas
has painted before the transition begins
- Reload on pageshow with persisted=true: bfcache restore left the WebGL
canvas blank and skipped the inline script; a fresh reload lets the
snapshot overlay work correctly
- Replace native <datalist> ref picker with a custom dropdown; the native
datalist won't open programmatically via .focus() in most browsers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the ifc:// URL is edited and Enter pressed, capture toRawUrl() of
the loaded model at load time. On any subsequent loadUrl() call, compare
against that snapshot: if the raw file URL is unchanged, call the new
applyViewChanges() function in-place instead of navigating to a new page.
applyViewChanges() diffs old vs new URL query params and applies only what
changed: camera position (with cameraControls.update(0) flush before
render), FOV/scale, clipping planes, selector/visibility, and query. A
full page reload now only happens when host, repo, ref, or path changes.
Fixes ifcurl-ntm.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add /query proxy rule to nginx config (was missing, caused 404)
- Fix populateMetaPanel: call groupData.get() instead of reading empty
groupData.map, and use ids.size (Set) not ids.length
- Update CLAUDE.md check #17 to reflect type counts now working
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
web-ifc's COORDINATE_TO_ORIGIN=true shifts all model geometry so the first
element lands at origin (≈12m shift for simple-ifc). GetCoordinationMatrix
returns identity despite the shift, so engine_fragment can't compensate.
Camera URLs are in IFC world coords (per spec §5), but the model was in
shifted coords, causing the JS viewer and Python renderer to disagree.
Set COORDINATE_TO_ORIGIN: false in ifcLoader.setup() so model geometry
stays in IFC world coords, matching what IfcOpenShell outputs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
viewer-url.js: parseIfcUrl returns query field; buildIfcUrl gains optional
fifth query param, included in the QS when non-empty.
viewer.html: add 'query' text field (label + input) in toolbar row 2
after the visibility selector. Add #query-panel (fixed bottom-centre)
with a scrollable table showing GlobalId | value rows, a header
displaying the query path, and a close button.
viewer.js: populate queryInput from parsed URL on load; include query
in buildIfcUrl() wrapper; wire queryInput into Enter-key and focus/blur
listeners. Add applyQuery(srcUrl, queryPath) which calls GET /query,
populates the table, and shows the panel. Called from main() after
applySelector when both selector and query params are present.
viewer-url.test.js: add tests for parseIfcUrl query field, buildIfcUrl
with query, and buildIfcUrl omitting empty query.
https://claude.ai/code/session_01NBsytCd6L7UQPiKbcLn4kn
viewer.html: add 'clash' option to the visibility <select> dropdown.
viewer.js: add clash branch in applySelector for both the simple
type-selector path and the complex server-side GUID path. Selected
elements are coloured 0xdc3232 (red, matching render.py's _CLASH_COLOR);
non-selected elements remain at their normal material colours, consistent
with highlight mode behaviour.
https://claude.ai/code/session_01NBsytCd6L7UQPiKbcLn4kn
Displays "Double-click model surface to place clip plane" in the status
bar when clip mode is toggled on, clears it when toggled off.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bundle OBCF (components-front) alongside OBC so Highlighter is sourced from the
correct package. Update click handler to use item.getGuid() — GlobalId is no
longer in the attributes map in fragments 3.4.x. Fix populateMetaPanel crash
(ClassificationGroupData now wraps ids in a .map property, not a plain object).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract parseCommitHref, isSimpleTypeSelector, subtractIdMap, and
isCommitHash into a new viewer-util.js ES module so they can be tested
without a browser. Convert ifcurl.js from an IIFE to an ES module
importing from viewer-util.js; update footer.tmpl to type="module".
Add node:test suites for viewer-url.js (32 tests) and viewer-util.js
(28 tests). Add npm test script and document it in the build section.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ifcurl.js: scan a[href^="ifc://"] at page load and replace with preview
figures — works with [title](ifc://...) syntax without any Go extension
- viewer.js: Issue button now emits [title](ifc://...) markdown link syntax
instead of a bare URL, so shared views render as previews without the patch
- README: document no-rebuild quick setup path; Go patch section is now optional
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
viewer.html was ~1250 lines of almost entirely JavaScript. The script
block is now a single <script type="module" src="./viewer.js"> tag,
leaving viewer.html as pure HTML/CSS (~238 lines).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>