A villa from one picture
xeoIFC does not only read IFC, it writes it, and generating an IFC file with it is easy. This is the prompt that was given to
Claude Code (model Fable 5.1) with the xeoifc command-line tool at hand:
"myVilla.png" take the picture of this villa and create an IFC model, including terrain around, high details, including furniture etc. Use xeoIFC API
Claude read the API with xeoifc describe, wrote one JavaScript file and ran it. Three short
follow-ups refined the result (which also demonstrates how to edit existing models):
there should be a small hallway at the top of the stairs, so that all 3 rooms can be accessed from there
add a staircase window on the north wall
make bathroom and kitchen more detailed
The result is the furnished villa below, with pool, garden and terrain. No CAD system was involved, nobody wrote a line of IFC.
1. The picture
2. The IFC model
This is xeoIFC Web, the browser viewer on the xeoIFC WebAssembly library, with the generated villa.ifc.
Rotate, zoom and pan with the mouse, click the sofa, a cypress or the lawn to see what it is.
| Input | 1 picture, 1 prompt, 3 short follow-ups |
|---|---|
| Generated | 1 script of about 440 lines |
| Output | IFC4, 312 products, 7555 entities, 0.7 MB, validated without findings |
| Run time | below one second |
| Content | 3 storeys, walls with cut openings, slabs, stair, glazed fronts, balustrades, pergola with solar glass, solar panels, heat pump, sofas, tables, chairs, beds, fitted kitchen with appliances, bathroom with sanitary ware, planters, sun loungers, parasol, pool, garden walls, gate, 15 cypresses, shrubs and a terrain mesh with a stepped lawn |
3. How it is made
Why so few words are enough: the API is small, self-describing and works on the level of building elements, so the generated
script stays short and readable. The command-line tool runs it. The script is plain JavaScript, xeo is the API. The excerpts below are
shortened, villa.js is the whole truth.
xeoifc run villa.js --root .
A document with site, building and storeys is one call. Lengths are metres, whatever the file unit is.
const doc = await xeo.document.create({
schema: 'IFC4', units: { length: 'm' }, project: { name: 'Villa from a picture' }, stableGuids: 'xeoifc-showcase-villa',
scaffold: { site: 'Garden', building: 'Villa', storeys: [
{ name: 'Ground floor', elevation: 0 }, { name: 'First floor', elevation: 3.3 }, { name: 'Roof', elevation: 6.5 }] },
});
stableGuids makes the script re-runnable: GlobalIds are derived from type, name and owner instead of drawn at
random, so a later change to the script adds or changes elements without touching the GlobalId of any other (BCF topics, saved
selections and xeoifc diff keep working across revisions).
An element is one call as well: entity, containment, placement, geometry, colour and property sets. An opening with its window is a second one. xeoIFC cuts the opening when the model is displayed or converted.
const north = await xeo.element.add({ type: 'IfcWall', name: 'North wall', container: groundFloor,
geometry: { kind: 'wall', start: [21, 8.85], end: [0, 8.85], height: 2.9, thickness: 0.3 },
color: [0.96, 0.95, 0.92], psets: { Pset_WallCommon: { IsExternal: true } } });
await xeo.opening.add({ host: north.ref, offset: 2, width: 1.8, height: 1.3, sill: 1.0, fill: { type: 'IfcWindow' } });
Furniture is a small library of functions inside the script. A piece is built once from coloured boxes and then placed and rotated as often as needed.
await sofa('Terrace sofa', groundFloor, [6.6, -2.4, 0], 0, 3.4, WOOD, FABRIC);
await bed('Master bed', firstFloor, [2, 4.6, 0], 0, 2.0);
for (let i = 0; i < 4; i++) await lounger('Pool lounger ' + (i + 1), site, [13.2 + 0.25 * i, -12.4 - 1.75 * i, -0.85], -68);
Free forms are meshes. The cypresses and the pots are lathe-turned profiles, the lawn is a height function on a grid that leaves out the house, the paving and the pool.
function ground(x, y) {
const south = smooth(-7, -9.5, y), east = smooth(22, 24.5, x) * (1 - smooth(6, 11, y));
return -0.05 - 0.85 * Math.max(south, east) + 0.03 * Math.max(0, y - 12);
}
await xeo.element.add({ type: 'IfcGeographicElement', predefinedType: 'TERRAIN', name: 'Lawn terrain', container: site,
geometry: { kind: 'mesh', positions, indices }, color: GRASS });
The API checks every call against the IFC schema and answers with a hint, which is what makes it usable by people and by AI assistants alike. The first run of this script stopped here:
schema_violation: RETAININGWALL is not a value of IfcWallTypeEnum
(hint: allowed: MOVABLE, PARAPET, PARTITIONING, PLUMBINGWALL, SHEAR, SOLIDWALL, STANDARD, ...)
At the end the script validates the document and saves it.
const check = await xeo.document.validate({ rules: ['schema', 'references', 'spatial', 'guid'] });
await xeo.document.save({ path: 'villa.ifc', overwrite: true });
Files: villa.js (the script), villa.ifc (the result),
villa.jpg (the picture). xeoifc describe prints the whole API as TypeScript declarations.