An IFC model goes in. One HTML file comes out, and it is the whole viewer.
No CDN, no three.js, no server, no cloud account, no upload. The geometry layer, the WebGL2 renderer and the Revit compound-file reader are all in this package, in the standard library, on top of ifc-spf.
pip install git+https://github.com/aecplatform/bimview.git
bimview office.ifcoffice.html
20 elements · 944 triangles · 78 KB
Open office.html anywhere. Orbit, pan, zoom, click an element to read its
property sets, filter by IFC class or by storey. Mail it to a client, attach it
to a pull request, open it on a site laptop with no network.
.rvt files have no geometry here, and never will. Revit stores element
data in undocumented proprietary streams. The only supported routes to it are
Autodesk's paid Model Derivative service or an IFC export from Revit itself.
Guessing at the format would produce a viewer that is right until it silently
isn't, which is worse than useless for a compliance conversation.
So bimview reads what a .rvt genuinely offers — and says so:
bimview Tower.rvtTower.rvt.html
Revit version 2024
Build 20230512_1200(x64)
Worksharing Not enabled
Last saved to C:\Projects\Tower.rvt
Preview embedded
No geometry. Revit's element data is in undocumented streams;
export IFC from Revit (File → Export → IFC) and view that.
That comes from a real compound-file reader — [MS-CFB], including the extended
DIFAT and the mini-stream, because BasicFileInfo lives in the mini stream in
every real model. Exit code is 2: the file was read, no geometry was drawn.
.rfa, .rte and .rft are handled the same way.
A viewer that renders three quarters of a model and says nothing is how a missing shaft becomes a site problem. Every representation bimview cannot tessellate is counted, named, and printed — and carried into the HTML, so the person you sent the file to sees the same gaps you did:
14 of 220 products did not draw:
9 unsupported representation IfcSweptDiskSolid
3 boolean subtraction not applied (openings render filled)
2 outer curve is not a polyline, indexed poly-curve or circle
Two consequences worth stating plainly:
- Openings render filled. A door void is an
IfcBooleanClippingResult. Without a CSG kernel the honest output is the un-cut wall plus a recorded reason — not a hole drawn where no hole was computed. - Hollow sections render solid. The bore of a
CircleHollowProfileDefis not cut.
| Solids | IfcExtrudedAreaSolid · IfcFacetedBrep · IfcPolygonalFaceSet · IfcTriangulatedFaceSet · IfcMappedItem · IfcBooleanResult (first operand) |
| Profiles | rectangle · rounded rectangle · circle · circle-hollow · I-shape · arbitrary closed · derived |
| Curves | polyline · indexed poly-curve incl. arc segments · circle · composite |
| Placement | full IfcLocalPlacement chains, IfcAxis2Placement2D/3D, cartesian transformation operators |
| Units | resolved to metres once, so a millimetre file is not a thousand times too big |
Two decisions do most of the work:
- Normals come from screen-space derivatives in the fragment shader, so the vertex buffer carries a position and an element id — sixteen bytes — and nothing else.
- Colour and visibility live in data textures indexed by that element id.
Hiding a storey is a texture upload, not a rebuild, and the entire model
stays a single
drawArrayscall.
Selection is a colour-picking pass into an offscreen framebuffer, which is exact at any zoom and needs no spatial index.
python examples/make_demo.py examples/office.ifc
bimview examples/office.ifcmake_demo.py writes a two-storey building — wall ring, slabs, circular
columns, an I-section beam — as readable IFC4 text. No binary fixture nobody
can diff.
from bimview import from_path, render
scene = from_path("office.ifc")
print(scene.coverage.drawn, "of", scene.coverage.products, "products drew")
for reason, count in scene.coverage.top_reasons():
print(f" {count:>4} {reason}")
Path("office.html").write_text(render(scene), encoding="utf-8")ifc-spf stops at products on purpose — it promises no geometry kernel, and it
keeps that promise: it knows IfcWall has a Representation and knows nothing
about IfcExtrudedAreaSolid. So the geometry entities are addressed
positionally here, from a table in
bimview/geometry/ifcattr.py that lists each
type's full attribute sequence. A wrong position reads a neighbouring attribute
and produces confidently wrong geometry, so the table is written out in full
rather than by index.
pip install -e ".[dev]" && pytest50 tests. The IFC fixtures are hand-written SPF text, so every coordinate in an expectation traces to a line you can read. The compound-file fixtures are real containers assembled byte by byte — a mocked reader would prove nothing about the FAT walk, and the FAT walk is what breaks on real files.
MIT.