Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

roomgraph

A vector floor plan PDF goes in. Rooms, areas, doors, windows and a room adjacency graph come out. JSON, GeoJSON and a rough IFC.

a floor plan resolving into walls, materials, rooms and a room graph

No dependencies. The PDF reader, the geometry, the IFC writer and the GIF encoder above are all in this package, in the standard library.

python -m roomgraph.cli extract plan.pdf -o out/
plan.pdf  page 0
  scale     1:50  (dimension, confidence 0.70) - 1 dimension string(s) agreed
  walls     5
  openings  2  {'door': 2}

id    name         category  gross m2  net m2  labelled  check  connects to
----  -----------  --------  --------  ------  --------  -----  -----------
R001  PHONG KHACH  living    30.00     27.07   30.00     ok     R002
R002  BEP          kitchen   24.00     21.38   24.00     ok     R001

id    kind  symbol      width mm  conf
----  ----  ----------  --------  ----
O001  door  door_swing  1000      0.98
O002  door  door_swing  900       0.98

  plan: scale_bar (scale_bar, 0.87) {'divisions': 5, 'measured_m': 10.0,
        'stated_m': 10.0, 'delta_pct': 0.0, 'confirms_scale': True}
  plan: north_arrow (north_arrow, 0.84) {'bearing_deg': 0.0, 'label': 'N'}
  plan: hatch_legend (hatch_legend, 0.80) {'title': 'CHU THICH',
        'entries': ['GACH XAY', 'BE TONG'], 'count': 2}
  plan: hatch (hatch_pattern, 0.88) {'count': 2, 'named': 2, 'regions': [
        {'material': 'GACH XAY', 'style': 'single', 'rulings': 54,
         'families': [{'angle_deg': 45.0, 'spacing_mm': 120.0, 'rulings': 54}]},
        {'material': 'BE TONG',  'style': 'cross',  'rulings': 61,
         'families': [{'angle_deg': 45.0,  'spacing_mm': 120.0, 'rulings': 31},
                      {'angle_deg': 135.0, 'spacing_mm': 120.0, 'rulings': 30}]}]}

That is the plan in the animation above. Two materials, named in the drawing's own Vietnamese because its legend says what they are; a north bearing, which a plan records nowhere else; and a scale bar that independently confirms the scale — which had been read off a dimension string, an entirely separate witness.

That check column is the drawing's own printed room area compared against the measured one. It is the cheapest available proof that the scale was read right.

A drawing states more than it is usually asked to. Where a plan carries them, roomgraph measures its scale bar against its own label, sums its dimension chains against the spans they cover, compares a stated travel distance against the route it measures, and counts the door schedule against the doors actually found. Each disagreement becomes a warning. They are independent witnesses, which is exactly what you want checking a number that everything else rests on.

Scope

Clean CAD-exported PDFs. Scans are explicitly out of scope, and that is the whole reason this is finishable: line and symbol detection on a scan is a research project, while a vector export already contains the lines — they just need to be understood. Feed it a scan and it will tell you it found no walls rather than guess.

See docs/LIMITATIONS.md for the rest of the boundary, including the ones that will bite you.

How it works

PDF content stream    walls are drawn as TWO parallel lines, and an
       |              opening is where BOTH of them stop. Everything
       v              downstream follows from that one observation.
  scale calibration   dimension strings > title block > door widths,
       |              each reported with its confidence
       v
  line clustering     segments -> the infinite lines they lie on
       |
       v
  face pairing        parallel lines 60-420mm apart -> walls + openings
       |
       v
  corner repair       free ends aimed at a missing corner are rejoined,
       |              the invented length becoming a bridged opening
       v
  planar arrangement  split at crossings and T-junctions, walk half-edges
       |              -> minimal cycles = rooms
       v
  symbol library      each gap classified: door, window, cased opening
       |
       v
  room graph          shared wall = adjacent; opening on it = walkable

Pairing lines before pairing segments is the part that matters. Match raw segments first and every wall broken by a doorway falls apart — and every real plan has those.

Install

git clone https://github.com/sophie-nguyenthuthuy/roomgraph
cd roomgraph
python -m roomgraph.cli --help

Python 3.10+. That is the entire installation.

Use

# everything, into out/
python -m roomgraph.cli extract plan.pdf -o out/ -f json,geojson,ifc,svg,gif

# when you know the scale, say so -- it is exact, and detection is not
python -m roomgraph.cli extract plan.pdf --scale 1:100

# what does this PDF actually contain? layers, text, scale candidates
python -m roomgraph.cli inspect plan.pdf --text 20

# what can the library recognise?
python -m roomgraph.cli symbols

# fail the build if anything is uncertain
python -m roomgraph.cli extract plan.pdf --strict

As a library:

from roomgraph import extract

model = extract("plan.pdf", scale="1:50")

for room in model.rooms:
    print(room.name, room.area_gross_m2, model.graph.neighbours(room.id))

print(model.graph.is_connected())     # is every room reachable?
print([e.a for e in model.graph.entrances])
for w in model.warnings:
    print("warning:", w)

Outputs

Format What it is for
JSON the canonical model — rooms, walls, openings, graph, warnings. Every other export is a projection of this.
GeoJSON rooms as polygons, walls as lines, openings as segments. Local metres by default, or pass --geo-origin lat,lon for real WGS84.
IFC IFC4 SPF: Project/Site/Building/Storey, IfcSpace per room, IfcWallStandardCase with real voids, IfcDoor/IfcWindow filling them. Rough — every height is an assumption.
SVG the figure above, with a toggleable graph layer. Doubles as the debugging view when a room comes out wrong.
GIF the animation at the top. Written by export/raster.py, LZW and all. apartment.gif is the same animation on a plan with no hatch, so it skips the materials beat.

The symbol library is the contributor unit

One symbol is one file in roomgraph/symbols/. You add a file with a SYMBOL and its FIXTURES; you do not touch the pipeline, the exporters, or the tests. The suite discovers your symbol, runs your fixtures, and separately checks that your detector does not outbid an existing symbol on that symbol's own fixtures — so a too-greedy detector fails loudly instead of quietly stealing openings.

Currently:

id scope detects
door_swing opening leaf line plus a swing arc of the opening's width
door_double opening two half-width arcs hinged on opposite jambs
door_sliding opening one leaf parallel to the wall, offset, no arc
door_folding opening three or more equal leaves zigzagging across the opening
door_revolving opening evenly spaced leaves radiating from a hub at the opening centre
door_roller opening a slatted curtain, corrugated or wound onto a barrel
door_fire_shutter opening the same shutter, drawn on a fire-rated layer
window opening two or more glazing lines spanning the opening
window_bay opening straight facets projecting out of the wall, jamb to jamb (box, canted, bow)
window_corner opening glazing wrapping a corner the drawing left out
curtain_wall opening a wide glazed run divided by mullions at a regular module
opening_plain opening a gap with nothing drawn in it
stairs room three or more evenly spaced parallel treads
stairs_spiral room six or more treads radiating from a newel
escalator room a long run of steps between full-length balustrades
travelator room a walkway band, named or longer than any escalator
ramp room a band carrying a gradient label
turning_circle room an empty 1500 mm circle of clear floor
kitchen room units sharing one depth: a fitted run
column room small poched or repeating squares
sanitary room outlines matching standard bath, shower, WC, bidet or basin sizes
lift room a car-sized rectangle with both diagonals drawn
dumbwaiter room the same crossed box, too small to stand in
parking_bay room two or more car-sized rectangles of matching size
furniture_layout room standard bed and desk sizes
fire_equipment room cabinets on a fire layer or beside a fire label
planting room scalloped canopies: ragged where a circle is smooth
lab_bench room runs at bench depth (750-900) or island depth (1500-1800)
theatre_seating room a dozen or more identical seats in rows at a regular pitch
ward_bay room three or more beds of one size at a regular bay pitch
plant_equipment room equipment on a services layer, labelled, or in a plant room
drainage room gullies and channels on a drainage layer, with any stated fall
extract_canopy room a deep hood over a cooking line
loading_dock room matching leveller plates in a row
raised_floor room a tile grid at roughly 600 mm both ways
structural_grid plan reference lines at regular bays, ending in lettered bubbles
escape_route plan open polylines on an escape layer, measured against any stated distance
north_arrow plan an arrow beside an N, reported as a bearing
scale_bar plan graduated divisions, measured against their own label
section_mark plan a cut line ending in two bubbles carrying the same letter
revision_cloud plan a scalloped outline on a revision layer
level_spot plan stated floor levels, the only height a plan records
dimension_chain plan a run of collinear dimensions, checked against the span it covers
elevation_mark plan a lone lettered bubble with an arrow, attached to nothing
door_schedule plan the drawing's door list, counted against the doors found
hatch_legend plan a column of captioned swatches: the drawing's material vocabulary
hatch_pattern plan regions of dense rulings, named by matching the legend

There are three scopes. Opening symbols compete, because one opening is one thing, and the most confident wins. Room symbols accumulate: a bathroom carries both fittings and a stair, and a ward reports its bays and the beds in them. Plan symbols see the whole drawing, including the geometry outside the building — a structural grid crosses every room and puts its bubbles in the margin, so neither of the other two scopes can reach it.

Forty-seven symbols. The specialised end of the library is where the method shows its limit: a lab bench is identified by depth, an auditorium by repetition, a plant room by the layer the drafter used. Where a drawing carries no such evidence, the symbol declines rather than guessing, and docs/LIMITATIONS.md says which ones do that and why.

Two plan symbols earn their place by reporting something nothing else in the model can know: north_arrow gives the building's bearing, and level_spot gives its datum. A plan is otherwise entirely flat and entirely unoriented.

hatch_pattern is worth singling out for what it refuses to do. It carries no table of ANSI patterns, because what 45-degree rulings mean is a matter of office and national convention — brickwork in one practice, general material in another, and Vietnamese drawings differ again. Hard-coding one would be inventing a standard nobody agreed to.

So it asks the drawing. A legend swatch is a labelled specimen of a pattern, so the same signature is computed inside each swatch and the regions elsewhere are matched against it. A plan with a legend gets its materials named in its own vocabulary; a plan without one gets the geometry, unnamed and honest about it.

That is the materials beat in the animation at the top: terracotta rulings for GACH XAY, crosshatch for BE TONG, both names read off the drawing's own legend. It draws the rulings the detector actually grouped rather than a filled outline — a solid block would show something the drawing never contained — and plans without hatch simply skip the beat.

Not every symbol is only a symbol file, though. A corner window deletes the corner, so both walls stop short, nothing encloses, and the room is lost before any detector runs — that one needed walls.bridge_corners to reconstruct the missing corner first. If a symbol changes what counts as a wall, expect to touch the wall stage too.

docs/SYMBOLS.md has the local-frame diagram, the context API, the confidence bands and a checklist.

Development

python examples/make_fixtures.py            # generate the fixture PDFs
python -m unittest discover -s tests        # 249 tests
make test                                   # the same, plus a demo render

Fixture PDFs are generated by examples/make_fixtures.py rather than committed, so their ground truth is written down next to the geometry that produces it. They are synthetic — see the last section of docs/LIMITATIONS.md for why that matters and what would help most.

Licence

MIT.

About

Vector floor plan PDF to rooms, openings and a room adjacency graph. JSON, GeoJSON and rough IFC. No dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages