- git.py: unpack (repo, is_stale) tuple from _get_repo() in diff_text;
was AttributeError 'tuple has no attribute commit' on every render_diff
- render.py: filter removed_entities and _entity_bounds include list to
IfcProduct only; non-geometric entities (IfcTaskTime etc) crash the
ifcopenshell geom iterator
- render.py: replace reset_camera(bounds=diff_bounds) with explicit camera
positioning; pyvista's reset_camera re-expands clipping to all actors
so the full scene remained visible — now camera is repositioned at the
default viewing direction but at a distance sized to the diff bounds,
zooming tightly to just the changed elements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When no explicit camera is given, compute the combined bounding box of all
changed elements (added/modified from head model, removed from base model)
using quick subset iterators before pass 1 renders. Pass
plotter.reset_camera(bounds=...) so the view zooms to the area of change
rather than the whole scene. Falls back to reset_camera() (whole scene) only
if no changed elements have renderable geometry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the ifcgit/Bonsai diff algorithm for the preview service:
diff.py: step_ids_from_diff() parses git diff text (regex on +#NNN= / -#NNN=
lines) to classify step IDs as added/modified/removed. expand_step_ids()
walks IfcShapeRepresentation, IfcObjectPlacement, IfcPropertySet, and
IfcTypeProduct relationships to promote changed sub-entities to the parent
IfcProduct that is visually affected.
render.py: render_diff() does two passes over the same camera. Pass 1 renders
the head model with green=added, blue=modified, ghost=unchanged. Pass 2
renders only the removed elements from the base model in red, using the camera
auto-fitted to pass 1. PIL composites the passes: wherever pass 2 is not
white background it stamps onto pass 1. Moved elements (modified) appear once
in blue with no doubling artefact.
git.py: diff_text() runs git diff against the cached bare repo clone and
returns the raw text for diff.py to parse.
service.py: GET+POST /render_diff endpoint. Fetches both commits, gets
diff text, runs the full pipeline sandboxed. Caches on disk when both refs
are immutable. Pillow added to render/service extras.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- render.py: replace bare cpu_count() with _WORKER_COUNT = min(cpu_count, 4),
configurable via IFCURL_RENDER_WORKERS env var; prevents N concurrent
requests from spawning N×cpu_count geometry worker processes
- auth.py: document that inject_token() embeds the token in git CLI arguments,
making it visible in the process list; note SSH as the workaround
- README: replace placeholder comment with actual tempfile.mkstemp pattern for
loading IFC bytes into ifcopenshell (which requires a file path, not bytes)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>