Floorplan extraction
The floorplan extractor converts labelled DXF geometry into the YAML room format used by the simulation. It is intended for local Cartesian floorplans; no coordinate reference system is required.
The extraction pipeline:
- reads room boundary linework and polygonises it;
- filters and attaches room labels for the configured floor;
- applies configured polygon splits, additions, and merges;
- normalises accepted adjacent wall faces to a shared midline;
- constructs configured open-boundary spans from the final room boundaries;
- projects CAD door symbols onto subsections of the final room boundaries;
- combines both kinds of connection in the existing door column and serialises labelled rooms as wall and door line segments.
The resulting geometry represents walls and doors as zero-thickness lines. Physical wall thickness can be applied when the YAML is loaded into another model.
Running the example
Run the example from the python-code directory:
uv run python ../examples/floorplan_extraction.py
The default paths are relative to the current directory:
floorplan.dxf: source floorplan;config.yaml: extraction configuration;output.yaml: extracted simulation geometry;output_diagnostic.png: labelled room and door diagnostic.
Alternative paths can be supplied explicitly:
uv run python ../examples/floorplan_extraction.py \
--dxf path/to/floorplan.dxf \
--config path/to/config.yaml \
--output path/to/output.yaml \
--diagnostic path/to/output_diagnostic.png
The example writes parent directories for the output files when required. Building name, address, and floor level are example constants in the script and should be adapted for production use.
Configuration
The configuration is a YAML mapping. The polygons block is required; all
other blocks are optional.
polygons:
polygon_layer_name: "ROOM_BOUNDARIES"
label_layer_name: "ROOM_LABELS"
polygon_label_column: "Text"
polygon_label_target: "room_numbers"
floor_filter: "E02"
excluded_room_numbers: []
doors:
layer_name: "Internal doors"
entity_col: "EntityHandle"
out_col: "doors"
predicate: "intersects"
shared_walls:
enabled: true
min_gap: 50.0
max_gap: 250.0
angle_tolerance_degrees: 2.0
min_overlap_ratio: 0.5
min_overlap_length: 150.0
canonical_line: "midline"
open_boundaries:
tolerance: 1.0e-6
min_length: 0.0
pairs:
- rooms: ["ROOM_A", "CORRIDOR"]
- rooms: ["ROOM_B", "ANTE_ROOM"]
selector_point: [5.0, 10.0]
- rooms: ["ROOM_C", "L_SHAPED_CORRIDOR"]
allow_multiple_spans: true
Room polygons and labels
polygon_layer_name identifies the linework used to construct room polygons.
label_layer_name identifies the room-label geometries. Labels are normalised
to the line beginning with floor_filter, then excluded room numbers are
removed before spatial attachment.
Polygons with no label, ambiguous labels, no attached doors, or unresolved shared-wall decisions are marked for review by the returned GeoDataFrame.
Explicit polygon corrections
Floorplan-specific corrections are applied through three optional lists:
polygon_splitsdivides a selected polygon with configured cut lines and assigns labels using seed points;polygon_additionscreates a missing polygon from explicit coordinates;polygon_mergescombines labelled source polygons into one target polygon.
Splits and additions occur before label attachment. Merges occur before shared-wall and door normalisation.
Open boundaries
The optional open_boundaries block identifies room pairs whose shared edge
has no physical wall. Each pair must contain exactly two distinct room labels.
An optional selector_point disambiguates multiple candidate spans for the
same pair. Set allow_multiple_spans: true for a pair with multiple straight
shared segments that should all be open; it cannot be combined with
selector_point.
tolerance controls permitted numerical coordinate drift and min_length
sets the minimum accepted span length, both in floorplan coordinate units.
They must be finite and non-negative. Configured labels must each resolve to
exactly one room after polygon corrections and shared-wall normalisation. The
extractor constructs each span from the final room boundaries, orders its
endpoints deterministically, and stores the result in
GeoDataFrame.attrs["open_boundary_spans"]. The spans are then added to the
configured door column (or a new doors column when no CAD door layer is
configured), so both connected rooms receive the same segment. Exact matches
to CAD doors are deduplicated; partial overlaps are rejected. A pair must have
one contiguous, straight, non-zero-length span that lies on both room
boundaries, unless allow_multiple_spans is set. In that case, every shared
segment must be straight and satisfy the same checks. Point-only, ambiguous,
and third-room spans are rejected.
Two rectangular CAD door markers on the same shared room boundary are treated as jambs: their outer endpoints define one passable opening. Other CAD door geometries retain their directly projected boundary overlap.
All CAD door spans are emitted in doors and removed from the physical walls
list. This makes the serialised door segment both the room-connection record and
the corresponding collision-free opening for movement.
The returned GeoDataFrame includes open_boundary_count for each room and an
open_boundary_attachment_report metadata entry. The report records the
configured room pair, canonical segment, and whether each room received a new
segment or deduplicated an exact CAD-door match. Invalid configurations and
partial CAD-door overlaps remain actionable extraction errors.
When open_boundaries is configured, the extraction example also emits the
same segments in an openings list. These non-operable spatial boundaries are
removed from the room's physical walls and supplied separately so the spatial
reader can close the room region without treating the span as a solid collision
wall. Existing YAML files without openings are unchanged.
Shared walls
Source room polygons often follow opposite faces of the same physical wall. Shared-wall normalisation detects plausible parallel overlaps and moves both room boundaries to a common midline.
Candidates must satisfy the configured gap, angle, overlap length, and overlap ratio thresholds. Candidates are rejected when another room occupies the strip between the wall faces, when matches are ambiguous, or when applying the midline would invalidate a room polygon.
Candidate selection uses a Shapely spatial index before the exact geometric
checks. Accepted candidates and rejection reasons that can require review are
the supported diagnostic contract. gap_too_large records for distant segment
pairs are broad-phase implementation details and are not guaranteed, because
those pairs may be excluded before exact evaluation.
The result includes:
shared_wall_count;shared_wall_review;shared_wall_rejections;- detailed detection records in
GeoDataFrame.attrs["shared_wall_detection"].
Doors
Door entities in architectural DXF files may contain leaf edges, frames, swing
arcs, and other symbol geometry. The extractor groups geometry by
entity_col, then projects each door onto a matching final room-boundary
segment.
The output door is therefore a canonical line:
- it lies on a room wall;
- its length represents the detected opening along that wall;
- the same coordinates are attached to both connected rooms where possible;
- CAD swing and leaf geometry is not written to the simulation YAML.
Attachment diagnostics are stored in
GeoDataFrame.attrs["door_attachment_report"]. A warning is recorded when a
door cannot be projected onto any room or is attached to more rooms than the
configured maximum.
Output YAML
Each room contains a name, ordered wall segments, and door segments:
rooms:
- name: E02NN012
walls:
- [x1, y1, x2, y2]
doors:
- [x1, y1, x2, y2]
Door coordinates overlay subsections of the corresponding wall boundaries. Shared doors are repeated in each connected room because room construction uses those coordinates to identify connectivity.
Diagnostic image
The example plots:
- room polygons in pale blue;
- final room boundaries in grey;
- room labels at representative interior points;
- physical canonical door openings as solid red overlays on the wall boundaries;
- configured open-boundary spans as solid blue overlays.
A white underlay makes door segments visible against the room boundary. Shared doors and open boundaries are deduplicated for plotting, so each connection appears once even though it is present in both room records.
The example also contains an optional shared-wall diagnostic helper. When enabled, it can display accepted midlines and rejected candidate overlaps. This overlay is intended for extraction review rather than production output.
Shared-wall performance baseline
Shared-wall candidate detection can be measured independently of the unit-test suite using synthetic square-room grids:
uv run python benchmarks/shared_wall_detection.py
The script reports room and wall-segment counts, indexed and exhaustive median
timings, speed-up, and material result counts for three input sizes. Use
--grid-sizes, --repeat, and --warmup to control a run. It uses generated
local Cartesian geometry only; no real or sensitive floorplan data is loaded.
The baseline recorded on 20 June 2026 for the exhaustive implementation is:
Environment: Python 3.13.11, Shapely 2.1.2, Linux 7.0.10-1-MANJARO x86-64. Each size used one warm-up and three measured runs.
| Grid | Rooms | Segments | Median (s) | Best (s) | Accepted | Rejected |
|---|---|---|---|---|---|---|
| 4x4 | 16 | 64 | 0.013184 | 0.012707 | 24 | 768 |
| 8x8 | 64 | 256 | 0.162462 | 0.155485 | 112 | 14,336 |
| 12x12 | 144 | 576 | 0.793025 | 0.786961 | 264 | 76,032 |
The indexed implementation was compared with the retained exhaustive reference on the same environment and benchmark settings:
| Grid | Rooms | Segments | Indexed median (s) | Exhaustive median (s) | Speed-up |
|---|---|---|---|---|---|
| 4x4 | 16 | 64 | 0.011804 | 0.011553 | 0.98x |
| 8x8 | 64 | 256 | 0.048875 | 0.095364 | 1.95x |
| 12x12 | 144 | 576 | 0.110366 | 0.383982 | 3.48x |
All three comparisons produced the same accepted and material rejection counts. The small layout is effectively unchanged, while the benefit increases with the number of wall segments. At 12x12, the indexed result is about 7.2 times faster than the original task-1 exhaustive baseline of 0.793025 seconds; part of that additional gain comes from indexing third-room obstruction queries.
Timings are descriptive rather than test thresholds because they depend on the machine and runtime environment. Candidate and rejection counts provide a deterministic correctness check alongside the regression tests.
API reference
The generated API documentation covers: