Skip to content

Instantly share code, notes, and snippets.

@arcs-
Last active May 16, 2025 09:29
Show Gist options
  • Save arcs-/9e98886f56385cfa78d09f8c35e8edde to your computer and use it in GitHub Desktop.
Save arcs-/9e98886f56385cfa78d09f8c35e8edde to your computer and use it in GitHub Desktop.
import { IFC4, IfcAPI, IfcLineObject, Schemas } from 'web-ifc'
const ID_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$'
/**
* This library creates an object and context to work with web-ifc
* as web-ifc is provides simply types and a way to write to files
*/
export class EasyIfc {
public ctx: IfcAPI
public modelID: number
public unit: IFC4.IfcUnitAssignment
public wcs: IFC4.IfcAxis2Placement3D
/**
* Creates a new EasyIfc Object, used for creating IFC files.
* Used internally by the EasyIfc class. Use EasyIfc.create() to create a new EasyIfc Object.
* @param _ctx an IfcAPI object
* @param name the name of the model
*/
private constructor(_ctx: IfcAPI, name: string) {
this.ctx = _ctx
this.modelID = this.ctx.CreateModel({
schema: Schemas.IFC4,
name,
authors: ['Patrick Stillhart'],
})
const metric = this.setupMetric()
this.unit = metric[0]
this.wcs = metric[1]
}
/**
* Creates a new ifc object builder
* @param name the name of the model
* @returns a new EasyIfc Object
*/
static async create(name: string) {
const ctx = new IfcAPI()
await ctx.Init()
return new EasyIfc(ctx, name)
}
/**
* Generates IFC compliant ids
* specification: https://standards.buildingsmart.org/IFC/RELEASE/IFC2x3/TC1/HTML/ifcutilityresource/lexical/ifcgloballyuniqueid.htm
* implemenation based on: https://github.com/IfcOpenShell/IfcOpenShell/blob/master/src/ifcopenshell-python/ifcopenshell/guid.py
* @returns an IFCGloballyUniqueId
*/
static guid() {
const g = crypto.randomUUID().replace(/-/g, '')
const bs: number[] = []
for (let i = 0; i < g.length; i += 2) {
bs.push(parseInt(g.slice(i, i + 2), 16))
}
const b64 = (v: number, l = 4) => (Array.from({ length: l })
.map((_, i) => ID_CHARS[Math.floor(v / (64 ** i)) % 64])
.reverse()
.join("")
)
const newId = [
b64(bs[0], 2),
...Array
.from({ length: Math.floor((bs.length - 1) / 3) })
.map((_, i) => b64((bs[i * 3 + 1] << 16) + (bs[i * 3 + 2] << 8) + bs[i * 3 + 3]))
].join("")
return new IFC4.IfcGloballyUniqueId(newId)
}
/**
* Sets the context up for the metric unit system
* @returns a tuple of the unit assignment and the axis2placement3d
*/
setupMetric(): [IFC4.IfcUnitAssignment, IFC4.IfcAxis2Placement3D] {
const unit = this.add(new IFC4.IfcUnitAssignment([
new IFC4.IfcSIUnit(IFC4.IfcUnitEnum.LENGTHUNIT, IFC4.IfcSIPrefix.MILLI, IFC4.IfcSIUnitName.METRE),
new IFC4.IfcSIUnit(IFC4.IfcUnitEnum.AREAUNIT, null, IFC4.IfcSIUnitName.SQUARE_METRE),
new IFC4.IfcSIUnit(IFC4.IfcUnitEnum.VOLUMEUNIT, null, IFC4.IfcSIUnitName.CUBIC_METRE)
]))
const cartPoint = this.add(new IFC4.IfcCartesianPoint([
new IFC4.IfcLengthMeasure(0),
new IFC4.IfcLengthMeasure(0),
new IFC4.IfcLengthMeasure(0),
]))
const axis = this.add(new IFC4.IfcDirection([
new IFC4.IfcLengthMeasure(0),
new IFC4.IfcLengthMeasure(0),
]))
const dir = this.add(new IFC4.IfcDirection([
new IFC4.IfcLengthMeasure(1),
new IFC4.IfcLengthMeasure(0),
]))
const wcs = this.add(new IFC4.IfcAxis2Placement3D(
cartPoint,
axis,
dir
))
return [unit, wcs]
}
/**
* Adds an object to the context
* @param object the object to add
* @returns the object
*/
add<T extends IfcLineObject>(object: T) {
this.ctx.WriteLine(this.modelID, object)
return object
}
/**
* Compiles the context
* @returns the compiled context
*/
compile() {
return this.ctx.SaveModel(this.modelID)
}
/**
* Adds a 3D context
* @returns the 3D context
*/
add3dContext() {
return this.add(new IFC4.IfcGeometricRepresentationContext(
new IFC4.IfcLabel('3D'),
new IFC4.IfcLabel('Model'),
new IFC4.IfcDimensionCount(3),
new IFC4.IfcReal(1.E-05),
this.wcs,
null,
))
}
}
export function createWorldScaffolding(easyIfc: EasyIfc, visualContext: IFC4.IfcGeometricRepresentationContext) {
const project = easyIfc.add(new IFC4.IfcProject(
EasyIfc.guid(),
null,
new IFC4.IfcLabel('Kabelschacht'),
null,
null,
null,
null,
[visualContext],
easyIfc.unit
))
// ifc structure
const site = easyIfc.add(new IFC4.IfcSite(
EasyIfc.guid(),
null,
new IFC4.IfcLabel('Baustelle'),
new IFC4.IfcText(''),
null,
null,
null,
null,
null,
null,
null,
null,
null,
null
))
easyIfc.add(new IFC4.IfcRelAggregates(
EasyIfc.guid(),
null,
null,
null,
project,
[site]
))
// in most examples i've seen there is also a building and a building storey
// however, it doesn't seem to be necessary for a kabelschacht
/*
const building = easyIfc.add(new IFC4.IfcBuilding(
EasyIfc.guid(),
null,
new IFC4.IfcLabel('Gebaeude'),
new IFC4.IfcText(''),
null,
null, null, null, null, null, null, null
))
easyIfc.add(new IFC4.IfcRelAggregates(
EasyIfc.guid(),
null,
null,
null,
site,
[building]
))
const buildingStorey = easyIfc.add(new IFC4.IfcBuildingStorey(
EasyIfc.guid(),
null,
new IFC4.IfcLabel('Erdgeschoss'),
new IFC4.IfcText('Datum'),
null,
null, null, null, null,
new IFC4.IfcPositiveLengthMeasure(0)
))
easyIfc.add(new IFC4.IfcRelAggregates(
EasyIfc.guid(),
null,
null,
null,
building,
[buildingStorey]
))
*/
return {
project,
site,
// building,
// buildingStorey
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment