Smelt is a local web tool for lightweight BIM authoring and GLB → IFC conversion. It can model simple buildings from scratch, edit GLB geometry, classify elements heuristically, enrich IFC metadata, and preview the result in a local IFC viewer.
The project is intentionally experimental: it is not a replacement for BIM authoring software, but it can turn many GLB building models into a more useful IFC starting point.
Smelt reads a GLB file, extracts mesh geometry, classifies elements, and exports an IFC4 STEP file.
Current exported IFC element types include:
IfcWallIfcSlabIfcBeamIfcColumnIfcStairIfcRoofIfcDoorIfcWindowIfcSpaceIfcZoneIfcBuildingElementProxy
The IFC file includes:
- IFC4 STEP-21 export
- Project → Site → Building → Storey hierarchy
- multi-storey detection
- storey assignment per element
- Y-up glTF → Z-up IFC axis conversion
IfcTriangulatedFaceSetgeometry to preserve original mesh triangles- STEP-21 string escaping
- Draco-compressed GLB support through
draco3dgltf - automatic millimetre → metre scale normalization for very large GLB extents
- fallback colours by IFC type when the GLB has no useful material colour
- preservation of explicit white materials instead of recolouring them
The converter uses geometry, mesh names, material names, dimensions, and triangle normal analysis to classify objects.
Current detection includes:
- walls and external walls
- slabs and floors
- beams and roof beams
- columns
- stairs
- roofs
- doors
- windows
- spaces / zones
- storeys
- roof-adjacent and upper-storey elements
Mesh names are checked in both English and French where useful, for example:
wall,mur,cloisonbeam,poutre,chevron,pannecolumn,poteau,colonnestair,escalier,marcheroof,toit,toiturewindow,fenêtre,vitragedoor,porte
The generated IFC can include common property sets such as:
Pset_WallCommonPset_BeamCommonPset_ColumnCommonPset_StairCommonPset_RoofCommon
For walls, the converter writes:
Pset_WallCommon
Reference = ""
IsExternal = true / false
LoadBearing = false
ExtendToStructure = falseThe converter also generates base quantities where supported, including examples such as:
Qto_WallBaseQuantities- gross / net style dimensions where available from bounding boxes
- length / width / height / area / volume approximations depending on type
These quantities are geometric approximations derived from mesh bounding boxes and triangulated geometry. They should be reviewed before use in production workflows.
The converter enriches IFC data with:
IfcMaterialIfcRelAssociatesMaterialIfcPresentationLayerAssignmentIfcClassificationIfcClassificationReferenceIfcRelAssociatesClassification
Uniformat II classification is currently assigned heuristically. Examples:
B1010Floor ConstructionB1020Roof ConstructionB2010Exterior WallsB2020Exterior WindowsB2030Exterior DoorsB3010Roof CoveringsC1010PartitionsC1020Interior DoorsC2010Stair Construction
Layers are generated by detected IFC type / discipline-style grouping, making it easier to review the model in the local viewer or external BIM tools.
The converter can generate simple IfcSpace and IfcZone entities from detected slabs and storeys.
Current space logic includes:
- filtering of tiny slab artefacts
- merging of contiguous slabs into cleaner spaces
- rough distinction between main space, balcony, terrace, and stair space
- room prototype generation for some storeys
- sloped roof space experiments
IfcRelSpaceBoundaryprototype relations
The space generation is still experimental. It is useful for visual QA and early data enrichment, but it does not yet replace true room detection from architectural plans.
The modeler at http://localhost:3737/modeler.html can now be used both to edit an existing GLB and to start a small model from scratch.
Current authoring MVP:
Nouveau modèlecreates an empty editable scene.- storey authoring:
+ Storeycreates a level above the current model,‹/›orPageUp/PageDownnavigate between storeys, andDuplicatecopies the active storey's authored walls/slabs/doors/windows/roofs to a new level above.Delete Storeyremoves the active storey, its authored elements, and its reference plan; the action is undoable with Ctrl/Cmd+Z. Copied doors/windows are re-linked to the copied host walls so deleting an opening restores the wall on the duplicated storey, not the source storey. - new Wall / Slab geometry is locked to the active storey and stores
storeyName/storeyElevationmetadata in the GLB user data. Plan 2Dimports an image reference for the active storey; reference planes are viewport-only overlays and are not exported to GLB / IFC.Scale plancalibrates the reference by clicking two points on the plan and entering their real distance in metres.Wallis a continuous polyline tool with live ghost preview: after the first click, the full wall volume, thickness, snap target, and alignment are visible before committing the next point. It supports centerline / left-face / right-face alignment; holdingShiftwhile placing the next point constrains the wall segment to 0° / 45° / 90° drafting guides;StoporEscends the trace and returns toSelect. Connected authored walls are re-trimmed by storey with a tiny anti-collision joint clearance so IFC exports do not contain overlapping wall solids at corners.Cloisonadds a dedicated interior partition tool: it uses a thinner centered wall profile, continuous tracing, Shift 0°/45°/90° guides, and names objectsCloison_###so the GLB → IFC classifier treats them as internal walls/partitions.Slabcreates a rectangular slab mesh from two short clicks on snapped grid corners, then returns toSelect. Authored slabs keep the drawn construction boundary for wall snapping, but their exported/visible body is automatically inset under the wall thickness to avoid visible coplanar slab edges on exterior façades. The default slab thickness is now 0.12 m instead of 0.25 m for a lighter floor plate.- left-drag remains reserved for camera orbit while authoring; drags do not place points.
- grid snap can be toggled and the grid step can be changed down to centimetre-level increments; the viewport now shows the active snap target (
Grille,Bord slab,Coin slab) next to the cursor. Wall points prioritize active-storey slab boundaries/corners when close enough, and object move snaps to the grid while dragging and re-snaps X/Z on release. - default storey height, wall height, wall thickness, and slab thickness are editable in the right panel.
Walls from slabcreates perimeter walls from the selected slab contour, using face alignment so the wall boundary hugs the slab outline.DoorandWindoware wall-snapped placement tools. Hovering a wall shows a live preview; clicking creates a separate monolithic 3D door/window mesh and regenerates the wall geometry as split solid pieces around the opening, giving a lightweight boolean cut before GLB → IFC conversion. Openings use the top of the active slab as their floor reference, so doors sit flush on the slab rather than at the raw wall base. Selecting a door/window exposes editable width/height values in the right panel; changing them updates both the object and the wall cut.Roofis a slab-based authoring tool. Select a slab, chooseFlat,Shed / monopente, orGable / double pente, adjust pitch/thickness/overhang/eaves height/ridge direction in the right panel, then clickRoof from slab. The preview shows the future roof before creation and the resulting mesh carries anIfcRoofhint.Escaliercreates a first parametric stair mesh in two clicks: click the start, click the end/direction, and Smelt generates one selectableStair_###mesh with configurable width, number of steps, and riser height. The mesh carries anIfcStairhint and can be duplicated with storeys.- authored doors/windows use bundled custom GLB assets when available, but are still baked as one selectable/exportable mesh each with stable
IfcDoor/IfcWindowhints. The wall cut still passes through the host wall, but the visible joinery asset is no longer stretched to 100% of the wall thickness: doors use about 38% of the wall thickness and fixed windows about 45%, with sensible min/max clamps. This keeps classification cleaner than uncontrolled multi-part visual assets and avoids oversized openings in the viewport/export. - created meshes are named
Wall_###,Cloison_###,Slab_###,Door_###,Window_###,Roof_###, andStair_###so the GLB → IFC classifier can map them to wall/partition, slab, door, window, roof, and stair IFC elements. - created elements remain editable with object transforms and vertex / edge / face edit mode. The modeler viewport uses a low-contrast CAD lighting setup rather than heavy ambient-occlusion-style shading, so junctions are easier to read while drafting.
Suppr/Deleteremoves the selected element and is undoable.Alt+Ztoggles Xray at any time; Xray is viewport-only and exported GLB materials keep their original opacity.- Numpad views switch to orthographic front/right/top/opposite views; zoom and pan stay orthographic, while starting a plain left-drag orbit returns to perspective.
- authoring tools live in a bottom floating dock (
Select,Slab,Wall,Cloison,Door,Window,Roof,Escalier) while view/storey/edit controls stay in the top toolbar; the shortcuts panel is hidden by default and can be opened from the?help button. - exports remain GLB baked, then the existing converter regenerates IFC.
This is intentionally a lightweight mesh authoring layer, not a full parametric BIM kernel yet.
For future custom door/window assets, prefer clean GLB files designed for BIM classification rather than decorative scene assets:
- one root object per element, named with a semantic prefix such as
Door_orWindow_; - dimensions in metres, Z-up after import/export, origin at the centre of the opening footprint unless documented otherwise;
- avoid nested decorative sub-meshes when the object should become one IFC element;
- keep pivots stable and local axes predictable: local X = width along the wall, local Y = height, local Z = wall thickness/depth;
- include simple metadata if possible:
ifcHint = IfcDoororifcHint = IfcWindow; - use lightweight geometry and simple materials. The BIM semantics should come from the wrapper/object name and metadata, not from visual complexity.
The project includes a local BIM viewer at:
http://localhost:3737/viewer.htmlThe viewer is built with the That Open / Fragments ecosystem and supports loading local IFC files.
Current viewer features:
- drag-and-drop IFC loading
- IFC → Fragments conversion in the browser
- orbit / pan / zoom
- reset camera
- local IFC spatial tree
- BIMData-like structure tree:
- Project
- Site
- Building
- Storey
- IFC type groups
- IFC elements
- element selection from the 3D view
- element selection from the tree
- second click to deselect
- property panel with accordions
- attributes
- classifications
- layers
- property sets
- quantities
- tabs for:
- Spatial
- Objects
- Classifications
- Layers
- show / hide checkboxes for:
- storeys
- groups
- individual elements
- keyboard shortcuts:
Hhide selected element or groupFfit view on selected element or group when possibleIisolate current selectionEscclear selection / exit isolation
For visual robustness, the viewer renders materials as double-sided. This helps when some triangulated roof or beam faces have inconsistent winding / normals.
GLB file
↓
@gltf-transform/core parses meshes and applies world transforms
↓
Draco decoder is registered when needed
↓
Input scale is checked
↓
If the model is probably in millimetres, coordinates are scaled to metres
↓
For each mesh:
- compute bounding box
- analyse triangle normals
- inspect material and mesh names
- classify wall / slab / beam / column / stair / roof / door / window / proxy
↓
Detect storeys
↓
Assign elements to storeys
↓
Detect exterior walls
↓
Generate spaces / zones where possible
↓
Generate IFC4 STEP-21 text file with:
- spatial hierarchy
- triangulated geometry
- property sets
- quantities
- materials
- colours
- layers
- Uniformat classifications
↓
IFC file downloadedInstall dependencies with Bun:
bun installStart the local server:
bun run startOpen the converter:
http://localhost:3737/Open the viewer:
http://localhost:3737/viewer.htmlThe viewer can ask a local Qwen reranker for IFC type suggestions. If Qwen is not available, the UI falls back to local heuristic suggestions.
First run the diagnostic. It checks .env.local, the GGUF model path, PATH, and the usual llama.cpp build folders without producing thousands of noisy candidates:
bun run qwen:doctorIf llama-server.exe is missing, first check/install the Windows prerequisites:
bun run qwen:prereqs:windowsThis checks git, cmake, and the Windows C++ build tools. The setup can auto-install git and cmake through winget when available. If CMake is still not visible in PATH, the setup downloads a portable CMake ZIP into .tools/cmake and uses it immediately in the same session. If C++ build tools are missing, install Visual Studio Build Tools with the Native Desktop workload, then restart PowerShell.
Then build or locate llama-server.exe, and write the paths into .env.local:
bun run qwen:setup:windowsThe script first searches PATH, .tools/llama.cpp, tools/llama.cpp, a sibling ../llama.cpp, and common C:\Github / F:\Github layouts. If it does not find llama-server.exe, it clones/builds llama.cpp under .tools/llama.cpp. The official llama.cpp Windows flow is CMake-based: configure a build directory, then build the Release binaries.
To only search without building:
bun run qwen:find:windowsIf you already have a binary somewhere else, point to it directly:
bun run qwen:setup -- --skip-build --llama-server-bin "F:\Github\llama.cpp\build\bin\Release\llama-server.exe"The setup writes:
QWEN_LLAMA_SERVER_BIN=...\llama-server.exe
QWEN_MODEL_PATH=...\models\Qwen3-Reranker-0.6B-Q4_K_M.gguf
QWEN_LLAMA_HOST=127.0.0.1
QWEN_LLAMA_PORT=8081
QWEN_LLAMA_CONTEXT=4096
QWEN_LLAMA_BATCH=1024You can also set the path manually in .env.local:
QWEN_LLAMA_SERVER_BIN=F:\Github\llama.cpp\build\bin\Release\llama-server.exe
QWEN_MODEL_PATH=F:\Github\Smelt\models\Qwen3-Reranker-0.6B-Q4_K_M.ggufThen start the app:
bun run devCheck status while the app is running:
bun run qwen:statusqwen:doctor is the better command when the app says llama-server executable not found.
bun run qwen:setup
# or
bun run qwen:setup:cuda
bun run qwen:setup:vulkanIf you prefer to run llama-server yourself, disable auto-start and point the app to the running reranker endpoint:
QWEN_AUTO_START=0
QWEN_RERANKER_URL=http://127.0.0.1:8081/v1/rerankpublic/
index.html
viewer.html
src/
viewer.js
server.js
package.json
bun.lock
README.mdGenerated or runtime folders should not be committed:
node_modules/
uploads/
outputs/Main runtime dependencies include:
expressmulter@gltf-transform/core@gltf-transform/extensionsdraco3dgltfvitethreeweb-ifc@thatopen/components@thatopen/fragments
Recommended files to commit:
server.js
package.json
bun.lock
README.md
public/
src/
.gitignore
LICENSEDo not commit:
node_modules/
uploads/
outputs/
.env
.env.localThe converter assumes the input GLB represents an architectural or building-like model.
It now attempts automatic scale normalization:
- plausible metre-sized model → no scaling
- very large model extent → likely millimetres → scale
0.001
This is heuristic. Some large sites or civil models may be incorrectly interpreted as millimetres.
Geometry is exported as IfcTriangulatedFaceSet. This preserves the original triangles but is not the same as native BIM solids. Some downstream tools may prefer extruded solids, swept profiles, or parametric IFC geometry.
Classification is heuristic and can be wrong.
Examples:
- a wall with many openings may look like a stair
- a thin tall furniture object may look like a wall
- a flat table may look like a slab
- a roof beam may look like floor construction without context
- merged meshes can confuse element boundaries
The current approach is a best-effort starting point, not a guaranteed BIM reconstruction.
Storey detection is based on slabs, wall bases, doors, columns, and other vertical signals. It works better on architectural models with clear floor levels. Split-level buildings, old buildings, mezzanines, and partial slabs can still be difficult.
Space generation is experimental. Current spaces are approximate and derived from slabs, storeys, walls and roof relationships. They may not match actual rooms.
Doors and windows are detected as separate elements where possible. In the modeler, authored doors/windows cut lightweight holes directly into the triangulated wall mesh before export. The converter still does not generate robust native IfcOpeningElement void relationships; the hole is baked into the wall geometry.
GLB materials are read when available. If no usable colour is found, the converter can use fallback colours by IFC type. Explicit white materials should remain white.
Some converted roof or beam elements may appear visually odd in certain viewers because triangulated face winding / normals can be inconsistent. The local viewer mitigates this with double-sided rendering.
Non-ASCII names are currently simplified for STEP output. Proper IFC Unicode escaping remains a future improvement.
Interesting next steps:
- improve stair detection and wall-with-openings robustness
- improve true room detection
- improve
IfcRelSpaceBoundary - add better
IfcOpeningElementsupport - add
IfcRailing - add
IfcCovering - improve material mapping
- improve layer naming and filtering
- add richer QA / conversion report JSON
- add viewer colour modes:
- by IFC type
- by storey
- by Uniformat
- by layer
- add selected element fit-view with true bounding box when Fragments exposes stable APIs
- add true isolation by opacity rather than hiding
- improve normals / triangle winding during IFC export
- support more input formats through pre-conversion to GLB
This project deliberately favours simple, inspectable JavaScript over a full BIM kernel.
The converter is useful for:
- testing GLB → IFC workflows
- enriching 3D building models with BIM-like structure
- prototyping IFC data generation
- reviewing classification heuristics
- building local QA tools around IFC output
It should not be treated as an authoritative BIM model generator without manual review.
See LICENSE.
When a model is created or edited in the GLB modeler and converted to IFC, the app now saves a linked local project in the browser IndexedDB. The saved project contains both:
- the baked editable GLB source;
- the generated IFC text.
This keeps the round trip usable:
Modeler from scratch → Convert to IFC → Open in viewer → Modifier géométrie → same GLB source reopensThe viewer matches a reloaded IFC to its local project by IFC content fingerprint first, then by file name. This means the link can still work even if the downloaded IFC was reopened manually from disk.
For an IFC that was not generated by this app, the viewer now creates a best-effort editable GLB snapshot from the displayed Fragments geometry when you click Modifier géométrie. This is not a perfect semantic IFC reconstruction: it bakes the visible triangulated geometry into GLB meshes, then the modeler can edit that geometry and use the existing GLB → IFC pipeline to regenerate an IFC.
When reopening geometry from the IFC viewer, the modeler now treats the GLB source as the editable source of truth. Authoring walls are rebuilt from their stored wall paths, and door/window meshes are relinked to their host wall openings so they stay snapped into the wall after a viewer → modeler round-trip.
Generated IFC spaces are loaded again by default when reopening geometry from the IFC viewer. They use a very light material and can be shown/hidden with the IFC Spaces toolbar toggle. When visible, they remain normal mesh geometry, so Object/Edit mode can be used to move vertices, edges, faces or the full space volume.
The modeler now uses bundled custom GLB assets for authored openings:
public/assets/openings/door-single.glb
public/assets/openings/window-fixed.glbAt startup each asset is loaded once, normalized to the modeler opening coordinate contract, then baked directly into every Door_### or Window_### mesh that is placed on a wall. The placement logic uses the slab top as the door floor line and as the reference for window sill height when a slab exists below the opening. The object depth is scaled from a joinery-depth ratio instead of the full wall thickness; the host wall is still cut through so the opening remains clean. If an asset cannot be loaded, the modeler falls back to the simple monolithic procedural geometry.
Recommended source-asset convention remains:
X = width
Y = height
Z = asset depth / joinery thickness, not necessarily full wall thickness
origin = center bottom of the opening
units = metres
one semantic root object / one IFC elementThe app normalizes the bundled GLBs internally, so placed openings remain editable by width/height/sill and still classify as IfcDoor or IfcWindow during GLB → IFC conversion.
Door and window authoring uses a two-layer live preview: a through-wall cut volume shows the future wall opening, while the thinner joinery asset shows the visible object depth ratio. This keeps doors and windows from filling the full wall thickness while making placement readable before clicking.
Selecting a door/window and pressing Delete or Backspace removes the opening object and restores the host wall geometry. Undo/redo preserves both the object and its linked wall cut.
When a placed Door_### or Window_### is selected in object mode, the properties panel shows a dedicated Door/Window section above the generic GLB geometry stats. Width, height and window sill can be edited there. For linked authoring openings, the visible object is resized and the host wall cut is regenerated immediately. If an older round-trip project lost the direct host-wall metadata, the modeler tries to relink by proximity; if relinking still fails, it can at least resize the visible opening mesh and warns that the wall cut was not recalculated.
The modeler keeps door/window dimension inputs interactive even while global viewport shortcuts and text-selection guards are active. Editable controls in the property panel now stop event propagation to the 3D viewport, and the no-selection guard no longer disables text selection inside inputs.
The Stair tool now supports two turning strategies:
- quarter-turn stairs with a cleaner landing platform generated from the incoming and outgoing flight directions;
- winder / balancé stairs, using fan-shaped closed step volumes at each turn.
When stair auto-opening is enabled, creating a stair searches the nearest slab on the upper storey and cuts a rectangular trimmer opening from the stair footprint. The slab keeps its original authoring boundary for snapping, but its visible/exported mesh is regenerated with the trimmer void. Undo/redo of stair creation and stair deletion restores the linked slab opening state.
Generated IFC Spaces remain editable when reopening the modeler from the IFC viewer. They are rehydrated as low-opacity IfcSpace meshes and can be shown/hidden with the IFC Spaces toolbar button.
Les slabs percées par les trémies d’escalier sont générées en volumes fermés avec un winding de faces sortant. Cela évite que la face supérieure disparaisse en viewport ou à l’export à cause d’un backface culling involontaire.
Le viewer prépare maintenant automatiquement un GLB éditable dès le chargement d’un IFC externe. Le bouton Modifier géométrie n’essaie donc plus de reconstruire un snapshot tardif depuis l’état courant du viewer : il ouvre le projet GLB pré-généré, sauvegardé localement avec le texte IFC source.
Cette approche suit le principe du pipeline web-ifc-viewer : convertir la géométrie IFC en glTF/GLB propre au moment de l’import, puis réutiliser ce modèle pour les workflows d’édition.