Skip to main content

A general-purpose, extensible floorplan library for architects and developers.

Project description

archit-app

A general-purpose, extensible Python library for architectural floorplan design and analysis.

pip install archit-app

Python 3.11+ License: MIT


Overview

archit_app provides a clean, layered data model for working with architectural floorplans in Python. It supports non-Manhattan shapes, curve walls, multi-level buildings, and exports to SVG, DXF, GeoJSON, and a canonical JSON format — all built on immutable, type-safe objects.

Key features:

  • Geometry primitives with coordinate system (CRS) tagging — mixing spaces raises an error immediately
  • Linear primitivesSegment2D (finite directed segment), Ray2D (half-line), Line2D (infinite line with parallel_offset, side_of, intersect), Polyline2D (ordered point sequence with segments(), bbox(), close(), to_polygon(), intersections())
  • CoordinateConverter — graph-based multi-CRS path-finding; Point2D.to(target, conv); build_default_converter() for screen/image/world viewports
  • Full NURBS evaluatorNURBSCurve uses the Cox–de Boor algorithm (exact rational evaluation, not linear interpolation); clamped_uniform() factory for smooth curves through endpoints; supports exact conic sections via rational weights
  • Architectural elements: walls (straight, arc, spline), rooms, openings (doors/windows), columns, staircases, slabs, ramps, elevators, beams, furniture (20 categories, 18 named factories — sofa, beds, tables, desk, bathtub, toilet, sink, etc.), annotations (TextAnnotation with room-label factory, DimensionLine with auto-computed labels, SectionMark with view-direction arrows)
  • Architectural furniture SVG symbols — 19 category-specific plan-view symbols drawn in SVG: bed (headboard + pillows + fold line), sofa (back panel + armrests + cushion dividers), armchair, chair (leg dots), office_chair (5-arm base + backrest arc), dining_table, coffee_table (double inset), wardrobe (door panels + handles), desk (monitor rect), tv_unit (dark screen), kitchen_counter (worktop line + cabinet dividers), island, bathtub (basin ellipse + drain + faucet), shower (diagonal hatch + drain circle), toilet (tank + bowl + seat ring), sink (basin + drain + faucet), washing_machine (porthole drum + control panel), bookshelf (shelves + colored book spines), dresser (drawer lines + pulls). Wet areas use a distinct cool base fill; wood surfaces use a warm parchment fill.
  • Wall joiningmiter_join(), butt_join(), join_walls() for clean corner geometry
  • Structural grid — named axes (A–H, 1–8), intersection queries, and point snapping
  • Multi-level building structure: Level → Building, with elevators and a grid attached at building level
  • Land parcel model — GPS coordinates, setbacks, buildable envelope, and an AI-agent context hook; Land.minimal() for orientation-only use; SiteContext is a backward-compatible alias for Land
  • Spatial analysis (archit_app.analysis):
    • Room adjacency graph and connected components (networkx)
    • Egress path finding and compliance reporting
    • Area program validation against design targets
    • Zoning compliance checker (FAR, lot coverage, height, setbacks)
    • Daylighting and solar orientation report per room
    • Isovist (visibility polygon) from any viewpoint
  • Floorplan Agent Protocol (archit_app.protocol) — versioned, strict-Pydantic inter-agent message layer: FloorplanSnapshot (compact/detailed), AgentHandoff (decisions + changes + open questions), MutationEnvelope (per-operation audit log), ProtocolReport (unified analysis report). All models use extra="forbid" + discriminated-union parsing. Includes five analysis adapters, Building.to_protocol_snapshot(), and a JSON Schema export CLI (archit-app-export-protocol).
  • I/O: JSON (fully round-trippable), SVG, GeoJSON, DXF round-trip (read + write, optional), IFC 4.x export (optional), PNG raster (Pillow, optional), PDF (reportlab, multi-page, optional)
  • Layer registryLayer model with color, visibility, and lock state; Building.add_layer() / is_layer_visible(); SVG/PDF/PNG renderers honour visible_layers filter; material-linked fill colours in SVG output
  • Unit conversion (archit_app.units) — to_feet, from_feet, to_inches, from_inches, to_mm, from_mm, to_cm, from_cm; parse_dimension() accepts "12'-6\"", "3800mm", "10ft", "36\"", or bare floats (meters)
  • Element transform utilitiescopy_element(el, dx, dy), mirror_element(el, axis_x=…, axis_y=…), array_element(el, count, dx, dy) — all return new elements with fresh UUIDs
  • Building validationBuilding.validate()ValidationReport with errors, warnings, and has_errors; Building.to_agent_context() for AI-agent handoff; Building.duplicate_level(src, new_index, new_elevation)
  • GeoJSON importlevel_from_geojson() / level_from_geojson_str() for full round-trip
  • Level spatial indexLevel.spatial_index() returns a Shapely STRtree over all spatially-aware elements for fast spatial queries
  • Plugin registry for extending the library without touching core code
  • Immutable Pydantic models throughout — every "mutation" returns a new object

Installation

pip install archit-app

For DXF and SVG export support:

pip install "archit-app[io]"

For IFC export (open BIM standard — Revit, ArchiCAD, FreeCAD compatible):

pip install "archit-app[ifc]"

For PNG raster export (Pillow):

pip install "archit-app[image]"

For PDF export (reportlab):

pip install "archit-app[pdf]"

For graph-based analysis (room adjacency, egress path-finding):

pip install "archit-app[analysis]"

For all optional dependencies:

pip install "archit-app[io,ifc,image,pdf,analysis]"

Requirements: Python 3.11+, pydantic ≥ 2.0, shapely ≥ 2.0, numpy ≥ 1.26


Quick Start

from archit_app import (
    Wall, Room, Level, Building, BuildingMetadata,
    Opening, Column, Point2D, Polygon2D, WORLD,
)

# 1. Create a room
boundary = Polygon2D.rectangle(0, 0, 6, 4, crs=WORLD)
living_room = Room(boundary=boundary, name="Living Room", program="living")

# 2. Create walls
north_wall = Wall.straight(0, 4, 6, 4, thickness=0.2, height=3.0)
south_wall = Wall.straight(0, 0, 6, 0, thickness=0.2, height=3.0)
west_wall  = Wall.straight(0, 0, 0, 4, thickness=0.2, height=3.0)

# Add a door to the west wall
door = Opening.door(x=0, y=1.5, width=0.9, height=2.1)
west_wall = west_wall.add_opening(door)

# 3. Add a structural column
col = Column.rectangular(x=2.8, y=1.8, width=0.3, depth=0.3, height=3.0)

# 4. Assemble a level
ground = (
    Level(index=0, elevation=0.0, floor_height=3.0, name="Ground Floor")
    .add_room(living_room)
    .add_wall(north_wall)
    .add_wall(south_wall)
    .add_wall(west_wall)
    .add_column(col)
)

# 5. Build the building
building = (
    Building()
    .with_metadata(name="My House", architect="A. Architect")
    .add_level(ground)
)

print(building)
# Building(name='My House', levels=1, gross_area=24.0m²)

Export to SVG

from archit_app.io.svg import level_to_svg, save_level_svg

svg_str = level_to_svg(ground, pixels_per_meter=50)
save_level_svg(ground, "ground_floor.svg", pixels_per_meter=50)

Export to JSON and reload

from archit_app.io.json_schema import save_building, load_building

save_building(building, "my_house.archit_app.json")
restored = load_building("my_house.archit_app.json")

Export to GeoJSON

from archit_app.io.geojson import level_to_geojson
import json

fc = level_to_geojson(ground)
print(json.dumps(fc, indent=2))

Define a land parcel and get agent context

from archit_app import Land, Setbacks, ZoningInfo, Building

# 1. Define the parcel from GPS coordinates
land = Land.from_latlon([
    (37.7749, -122.4194),
    (37.7750, -122.4194),
    (37.7750, -122.4183),
    (37.7749, -122.4183),
], address="123 Main St, San Francisco, CA")

print(f"Lot area: {land.area_m2:.1f} m²")

# 2. Export a context dict — pass this to an AI agent to get zoning info
context = land.to_agent_context()
# {
#   "address": "123 Main St, San Francisco, CA",
#   "area_m2": 598.1,
#   "latlon_coords": [...],
#   "centroid_latlon": [...],
#   ...
# }

# 3. Enrich with zoning (manually or from an agent response)
land = (
    land
    .with_zoning(ZoningInfo(
        zone_code="RH-2",
        max_height_m=10.0,
        max_far=1.8,
        max_lot_coverage=0.6,
        allowed_uses=("residential",),
        source="SF Planning Code §207",
    ))
    .with_setbacks(Setbacks(front=3.0, back=6.0, left=1.5, right=1.5))
)

print(f"Buildable area: {land.buildable_area_m2:.1f} m²")
print(f"Max floor area: {land.max_floor_area_m2:.1f} m²")

# 4. Attach the land to a building
building = Building().with_land(land)

DXF round-trip (read + write)

# Requires: pip install "archit-app[io]"
from archit_app.io.dxf import (
    save_building_dxf, building_from_dxf,
    save_level_dxf, level_from_dxf,
)

# Export
save_building_dxf(building, "my_house.dxf")

# Import back — auto-detects archit-app's FP_* layer convention
restored = building_from_dxf("my_house.dxf")

# Import a single level, override defaults
level = level_from_dxf("floor_plan.dxf", wall_height=3.5, wall_thickness=0.25)

# Generic DXF with custom layer names
level = level_from_dxf(
    "autocad_drawing.dxf",
    layer_mapping={"A-WALL": "walls", "A-FLOR-PATT": "rooms"},
)

Recognised element types in layer_mapping: "walls", "rooms", "openings", "columns".

Export to PNG (raster)

# Requires: pip install "archit-app[image]"
from archit_app.io.image import save_level_png, save_building_pngs, level_to_png_bytes

# Single level at 100 px/m, 150 DPI
save_level_png(ground, "ground_floor.png", pixels_per_meter=100, dpi=150)

# All levels — one PNG per level — into a directory
save_building_pngs(building, "output/", pixels_per_meter=100, dpi=150)

# Raw bytes (e.g. for HTTP response or in-memory processing)
png_bytes = level_to_png_bytes(ground, pixels_per_meter=100)

Renders rooms, walls, columns, openings, room labels with area, and a 1 m scale bar. Uses 2× supersampling for clean anti-aliased edges at any scale.

Export to PDF

# Requires: pip install "archit-app[pdf]"
from archit_app.io.pdf import save_level_pdf, save_building_pdf, level_to_pdf_bytes

# Single level — auto-selects landscape if drawing is wider than tall
save_level_pdf(ground, "ground_floor.pdf", paper_size="A3")

# All levels in one multi-page PDF (one page per level)
save_building_pdf(building, "my_house.pdf", paper_size="A3")

# Force portrait, A4
save_level_pdf(ground, "ground_portrait.pdf", paper_size="A4", landscape=False)

# Raw bytes
pdf_bytes = level_to_pdf_bytes(ground, paper_size="A3")

Supported paper sizes: "A1", "A2", "A3" (default), "A4", "letter". Each level is centred and scaled to fill the page.

Export to IFC 4.x

# Requires: pip install "archit-app[ifc]"
from archit_app.io.ifc import building_to_ifc, save_building_ifc

# Get the ifcopenshell model object (for further manipulation)
model = building_to_ifc(building)
print(model.by_type("IfcWall"))       # list all exported walls
print(model.by_type("IfcSpace"))      # list all exported rooms
model.write("my_house.ifc")           # write to disk

# Or use the one-line convenience function
save_building_ifc(building, "my_house.ifc")

Exported to IFC: walls (IfcWall), rooms (IfcSpace), doors/windows (IfcDoor/IfcWindow), columns (IfcColumn), slabs (IfcSlab), staircases (IfcStair), ramps (IfcRamp), beams (IfcBeam), furniture (IfcFurnishingElement), elevators (IfcTransportElement) — all under the full IfcProject → IfcSite → IfcBuilding → IfcBuildingStorey hierarchy. The IFC file can be opened in Revit, ArchiCAD, FreeCAD, and any other IFC 4-compliant viewer.

# Import an IFC file back into archit_app
from archit_app.io.ifc import building_from_ifc, level_from_ifc

building = building_from_ifc("my_house.ifc")
print(len(building.levels))          # number of floors
print(len(building.levels[0].walls)) # walls on ground floor

# Import just one storey
ground = level_from_ifc("my_house.ifc", storey_index=0)

Imported element types: IfcWallWall, IfcSpaceRoom, IfcDoor/IfcWindowOpening, IfcColumnColumn, IfcSlabSlab, IfcStairStaircase, IfcRampRamp, IfcBeamBeam, IfcFurnishingElementFurniture, IfcTransportElementElevator.

Vertical circulation and structural elements

import math
from archit_app import (
    Staircase, Slab, Ramp, Elevator, ElevatorDoor,
    Beam, BeamSection, Level, Building,
)

# Staircase — 12 risers connecting ground floor to first floor
stair = Staircase.straight(
    x=6, y=0, width=1.2, rise_count=12,
    rise_height=0.175, run_depth=0.28,
    bottom_level_index=0, top_level_index=1,
)
print(f"Total rise: {stair.total_rise:.2f} m")   # 2.10 m

# Slab — 200 mm concrete floor deck
slab = Slab.rectangular(x=0, y=0, width=10, depth=8,
                         thickness=0.2, elevation=0.0)

# Ramp — 1:12 accessible ramp (≈ 4.8° slope)
ramp = Ramp.straight(x=0, y=0, width=1.5, length=3.6,
                      slope_angle=math.radians(4.76),
                      bottom_level_index=0, top_level_index=1)

# Elevator — 1.1 × 1.4 m cab, 0 → 3
elevator = Elevator.rectangular(x=8, y=0, cab_width=1.1, cab_depth=1.4,
                                 bottom_level_index=0, top_level_index=3)

# Structural beam — 300 mm wide, 500 mm deep, top at 3.5 m
beam = Beam.straight(x1=0, y1=0, x2=6, y2=0,
                     width=0.3, depth=0.5, elevation=3.5,
                     section=BeamSection.RECTANGULAR)
print(f"Span: {beam.span:.1f} m, soffit: {beam.soffit_elevation:.2f} m")

# Assemble
ground = (
    Level(index=0, elevation=0.0, floor_height=3.0)
    .add_staircase(stair)
    .add_slab(slab)
    .add_ramp(ramp)
    .add_beam(beam)
)
building = Building().add_level(ground).add_elevator(elevator)

Structural grid and wall joining

from archit_app import StructuralGrid, Wall, join_walls, miter_join, Point2D, WORLD

# Create a 3 × 3 regular grid at 6 m spacing
grid = StructuralGrid.regular(
    x_spacing=6.0, y_spacing=6.0,
    x_count=3, y_count=3,
)

# Query intersections
pt = grid.intersection("2", "B")   # grid point at column 2, row B
print(pt)                           # Point2D(x=6.0, y=6.0, ...)

# Snap a point to the nearest grid intersection
p = Point2D(x=5.95, y=6.1, crs=WORLD)
snapped = grid.snap_to_grid(p, tolerance=0.2)

# Attach grid to a building
building = Building().with_grid(grid)

# Clean up wall corners with miter joins
wall_h = Wall.straight(0, 0, 5, 0, thickness=0.2, height=3.0)
wall_v = Wall.straight(5, 0, 5, 4, thickness=0.2, height=3.0)

wall_h_trimmed, wall_v_trimmed = miter_join(wall_h, wall_v)

# Or join a whole room's worth of walls at once
walls = [
    Wall.straight(0, 0, 5, 0, thickness=0.2, height=3.0),
    Wall.straight(5, 0, 5, 4, thickness=0.2, height=3.0),
    Wall.straight(5, 4, 0, 4, thickness=0.2, height=3.0),
    Wall.straight(0, 4, 0, 0, thickness=0.2, height=3.0),
]
joined_walls = join_walls(walls)

NURBS curved walls

import math
from archit_app import Point2D, WORLD
from archit_app.geometry.curve import NURBSCurve
from archit_app import Wall

# Smooth cubic curve through 5 control points (clamped — passes through endpoints)
ctrl = (
    Point2D(0, 0, crs=WORLD),
    Point2D(1, 2, crs=WORLD),
    Point2D(2, 0, crs=WORLD),
    Point2D(3, 2, crs=WORLD),
    Point2D(4, 0, crs=WORLD),
)
curve = NURBSCurve.clamped_uniform(ctrl, degree=3)

# Sample 64 points along the curve (exact, not linear interpolation)
polyline = curve.to_polyline(resolution=64)
print(f"Arc length ≈ {curve.length():.3f} m")

# Exact quarter-circle arc via rational NURBS (w = cos(π/4) on the middle point)
w = math.cos(math.pi / 4)
circle_pts = (
    Point2D(1, 0, crs=WORLD),
    Point2D(1, 1, crs=WORLD),
    Point2D(0, 1, crs=WORLD),
)
quarter_circle = NURBSCurve.clamped_uniform(circle_pts, degree=2, weights=(1.0, w, 1.0))

# Use a NURBS curve as wall geometry
wall = Wall(geometry=curve.to_polygon(resolution=32), thickness=0.2, height=3.0)

Coordinate conversion

from archit_app import build_default_converter, SCREEN, WORLD, IMAGE, Point2D

# Build the standard viewport converter (800×600 canvas, 50 px/m)
conv = build_default_converter(
    viewport_height_px=600.0,
    pixels_per_meter=50.0,
    canvas_origin_world=(0.0, 0.0),   # world coords of canvas bottom-left
)

# Convert a screen click to a world position
screen_click = Point2D(x=400.0, y=300.0, crs=SCREEN)
world_pos = screen_click.to(WORLD, conv)
print(world_pos)   # Point2D(x=8.0, y=6.0, crs='world')

# Round-trip back to screen
back = world_pos.to(SCREEN, conv)

# IMAGE → WORLD works too — resolved via BFS through SCREEN
import numpy as np
world_pts = conv.convert(np.array([[400.0, 300.0]]), IMAGE, WORLD)

# Extend with custom spaces: register any transform and multi-hop paths
# are found automatically
from archit_app import CoordinateConverter, WGS84
conv.register(WORLD, WGS84, some_georeference_transform)
# Now IMAGE → WGS84 resolves as IMAGE → SCREEN → WORLD → WGS84

Layer visibility and material-linked rendering

from archit_app import Building, Level, Wall, Room, Polygon2D, WORLD
from archit_app.building.layer import Layer
from archit_app.io.svg import level_to_svg

# 1. Register layers on the building
building = (
    Building()
    .add_layer(Layer(name="structure", color_hex="#808080", visible=True))
    .add_layer(Layer(name="finishes",  color_hex="#C8A080", visible=False))
    .add_level(ground)
)

# 2. SVG only renders elements whose layer is in the visible set
svg = level_to_svg(ground, visible_layers={"structure"})   # "finishes" hidden

# 3. Material-linked colours in SVG
from archit_app.materials import default_library
svg = level_to_svg(ground, material_library=default_library)
# Walls use their material's colour; falls back to default palette

Unit conversion

from archit_app.units import (
    to_feet, from_feet, to_inches, from_inches,
    to_mm, from_mm, to_cm, from_cm,
    parse_dimension,
)

# Metric ↔ Imperial
print(to_feet(3.048))        # 10.0
print(from_feet(10.0))       # 3.048
print(to_inches(0.0254))     # 1.0
print(to_mm(1.0))            # 1000.0

# parse_dimension accepts mixed strings
print(parse_dimension("12'-6\""))   # 3.81  (meters)
print(parse_dimension("3800mm"))    # 3.8
print(parse_dimension("10ft"))      # 3.048
print(parse_dimension("36\""))      # 0.9144
print(parse_dimension("3.5"))       # 3.5   (bare float → meters)

Element transform utilities

from archit_app.elements.transform_utils import copy_element, mirror_element, array_element
from archit_app import Wall

wall = Wall.straight(0, 0, 5, 0, thickness=0.2, height=3.0)

# Translate — new element with fresh UUID
wall2 = copy_element(wall, dx=0, dy=3.0)

# Mirror about y = 2.5
mirrored = mirror_element(wall, axis_y=2.5)

# Linear array — returns [original, copy1, copy2, …]
row = array_element(wall, count=4, dx=6.0, dy=0)
# produces 4 walls spaced 6 m apart

Building validation and agent context

from archit_app import Building

building = Building().add_level(ground)

# Validate for common issues
report = building.validate()
if report.has_errors:
    for issue in report.issues:
        print(f"[{issue.severity.upper()}] {issue.message}")

# Export a flat dict for AI-agent handoffs (legacy)
ctx = building.to_agent_context()

# Preferred v0.4+: validated FloorplanSnapshot (Floorplan Agent Protocol v1)
snap = building.to_protocol_snapshot(mode="compact", building_revision=0)
print(snap.message_type)     # "floorplan_snapshot"
print(snap.total_rooms)      # int
agent_json = snap.model_dump_json(exclude_none=True)   # pass to LLM

# Duplicate a level (fresh UUIDs for all elements)
building2 = building.duplicate_level(src_index=0, new_index=1, new_elevation=3.0)

Floorplan Agent Protocol

from archit_app.protocol import (
    FloorplanSnapshot, AgentHandoff, MutationEnvelope, ProtocolReport,
    parse_message, dump_message, PROTOCOL_VERSION,
)
from archit_app.protocol.handoff import Decision
from archit_app.protocol import compliance_report_to_protocol

# 1. Compact snapshot — pass between agents as structured context
snap = building.to_protocol_snapshot(mode="compact")
print(PROTOCOL_VERSION)    # "1.0.0"

# 2. Agent emits a structured handoff
handoff = AgentHandoff(
    agent_role="architect",
    summary="Placed 6 rooms on level 0; total net area 87 m².",
    decisions=(Decision(title="Compact footprint", rationale="10×9 m fits the brief."),),
)

# 3. Analysis adapter — convert existing results to ProtocolReport
from archit_app.analysis.compliance import check_compliance
raw_report = check_compliance(building, land)
report = compliance_report_to_protocol(raw_report)
print(report.kind)          # "compliance"
print(report.summary)       # {"passed": 4, "failed": 0, "warnings": 0}

# 4. Discriminated-union parsing (all four types in one call)
msg = parse_message(handoff.model_dump_json())   # → AgentHandoff
raw = dump_message(msg)                           # → dict

# 5. Export JSON Schemas for all message types
from archit_app.protocol.schema_export import export_schemas
export_schemas("./schemas/")
# Writes FloorplanSnapshot.schema.json, AgentHandoff.schema.json, …

Spatial index

# Requires: pip install shapely
from shapely.geometry import box

tree, elements = ground.spatial_index()

# Fast rectangular query — returns matching elements
hits = tree.query(box(0, 0, 3, 3))
nearby = [elements[i] for i in hits]
print([type(e).__name__ for e in nearby])   # ["Wall", "Room", ...]

GeoJSON import

from archit_app.io.geojson import (
    level_to_geojson, level_from_geojson,
    level_to_geojson_str, level_from_geojson_str,
)

# Export
fc = level_to_geojson(ground)

# Import back — full round-trip
restored = level_from_geojson(fc, index=0, elevation=0.0, floor_height=3.0)
assert len(restored.rooms) == len(ground.rooms)

# From/to JSON string
json_str  = level_to_geojson_str(ground)
restored2 = level_from_geojson_str(json_str)

Wall geometry helpers (v0.3.4+)

from archit_app import Wall

wall = Wall.straight(0, 0, 6, 0, thickness=0.2, height=3.0)

# Centre-line endpoints (tuple[float, float])
print(wall.start_point)   # (0.0, 0.0)
print(wall.end_point)     # (6.0, 0.0)

# 8-point compass facing direction of the wall's left-hand normal
print(wall.facing_direction())   # "N"  (wall runs E–W → normal faces N)

Level batch creation and spatial query (v0.3.4+)

from archit_app import Wall, Room, Level, Polygon2D, WORLD

r1 = Room(boundary=Polygon2D.rectangle(0, 0, 6, 4, crs=WORLD), name="Living", program="living")
r2 = Room(boundary=Polygon2D.rectangle(6, 0, 4, 4, crs=WORLD), name="Kitchen", program="kitchen")

w1 = Wall.straight(0, 4, 10, 4, thickness=0.2, height=3.0)
w2 = Wall.straight(10, 0, 10, 4, thickness=0.2, height=3.0)

# Add many rooms / walls in one call — preferred over chained .add_room()/.add_wall()
ground = (
    Level(index=0, elevation=0.0, floor_height=3.0)
    .add_rooms([r1, r2])
    .add_walls([w1, w2])
)

# Spatial query — find all walls adjacent to a room (useful before add_opening)
adjacent = ground.walls_for_room(r1.id, tolerance_m=0.35)
for w in adjacent:
    print(f"{w.id}: facing={w.facing_direction()}, length≈{w.length:.2f}m")

Spatial analysis

from archit_app.analysis.topology import build_adjacency_graph, connected_components
from archit_app.analysis.circulation import egress_report
from archit_app.analysis.area import area_by_program, area_report, AreaTarget
from archit_app.analysis.compliance import check_compliance
from archit_app.analysis.daylighting import daylight_report
from archit_app.analysis.visibility import compute_isovist, mutual_visibility

# --- Room adjacency graph (requires pip install archit-app[analysis]) ---
G = build_adjacency_graph(ground)
# G.nodes[room.id] = {room, centroid, area_m2, program, level_index}
# G.edges[a, b]    = {shared_length_m, distance_m, opening_ids}

components = connected_components(G)   # list of connected room groups

# --- Egress compliance (v0.3.4+: structured result, auto-detects exits) ---
report = egress_report(ground)   # exit_ids auto-detected from "exit"/"lobby" programs
# {
#   "overall_compliant": True,
#   "max_distance_m": 24.5,
#   "exit_count": 2,
#   "rooms": [{"room_id", "egress_distance_m", "path", "compliant",
#              "issue", "suggested_fix"}, ...],
#   "failed_rooms": [...]
# }

# --- Area program validation ---
totals = area_by_program(building)
# {"bedroom": 32.0, "kitchen": 8.0, ...}

results = area_report(building, targets=[
    AreaTarget(program="bedroom", target_m2=30.0, tolerance_fraction=0.10),
    AreaTarget(program="kitchen", target_m2=10.0),
])
# [ProgramAreaResult(program, actual_m2, target_m2, deviation_fraction, compliant), ...]

# --- Zoning compliance ---
compliance = check_compliance(building, land)
print(compliance.summary())
# Compliance report — PASS
#   [PASS] Floor Area Ratio (FAR): 0.16  (limit: 0.5)
#   [PASS] Building height: 3.0 m  (limit: 10.0 m)
#   [PASS] Footprint within lot boundary: yes
#   [PASS] Footprint within setback envelope: yes

# --- Daylighting (v0.3.4+: compliant flag, issue, suggested_fix per room) ---
daylight = daylight_report(ground, north_angle_deg=15.0)
for r in daylight:
    print(f"{r.room_name}: WFR={r.window_to_floor_ratio:.2f}, "
          f"solar_score={r.avg_solar_score:.2f}, compliant={r.compliant}")
    if not r.compliant:
        print(f"  Issue: {r.issue}")
        print(f"  Fix:   {r.suggested_fix}")

# --- Isovist (visibility polygon) ---
from archit_app import Point2D, WORLD
vp = Point2D(x=5.0, y=3.0, crs=WORLD)
result = compute_isovist(vp, ground, resolution=360, max_range=20.0)
print(f"Visible area: {result.area_m2:.1f} m²")

# Line-of-sight check
can_see = mutual_visibility(Point2D(x=2, y=2, crs=WORLD),
                             Point2D(x=8, y=2, crs=WORLD), ground)

Coordinate System

archit_app uses a Y-up, meters world coordinate system — the standard for architecture. Screen and image layers are Y-down; the library handles the flip at export time so your geometry code never sees it.

Space Origin Y direction Unit Use
WORLD site datum up meters all architectural geometry
SCREEN top-left down pixels rendering, UI events
IMAGE top-left down pixels raster images, panoramas
WGS84 lat/lon up meters GIS / site georeferencing

Every Point2D and Vector2D carries its CRS. Arithmetic between mismatched spaces raises CRSMismatchError immediately.


Architecture

The library is structured in layers:

archit_app/
├── geometry/     Layer 1 — CRS, points, vectors, transforms, polygons, curves,
│                            Segment2D/Ray2D/Line2D/Polyline2D, CoordinateConverter
├── elements/     Layer 2 — Wall, Room, Opening, Column, Staircase, Slab,
│                            Ramp, Elevator, Beam, Furniture, TextAnnotation,
│                            DimensionLine, SectionMark, wall_join utilities
├── building/     Layer 3 — Land, Setbacks, ZoningInfo, Level, Building,
│                            BuildingMetadata, StructuralGrid, Layer registry,
│                            ValidationReport, duplicate_level, to_agent_context,
│                            to_protocol_snapshot, spatial_index
├── analysis/     Layer 6 — topology, circulation, area, compliance,
│                            daylighting, visibility
├── protocol/     Layer 7 — Floorplan Agent Protocol v1.0.0
│                            FloorplanSnapshot, AgentHandoff, MutationEnvelope,
│                            ProtocolReport, ElementRef, parse_message, dump_message,
│                            adapters (analysis → ProtocolReport), schema_export CLI
├── io/           Layer 5 — JSON, SVG, GeoJSON (read+write), DXF (read+write),
│                            IFC 4.x, PNG, PDF
├── core/         Registry — plugin/extension system
├── units.py      Unit conversion — parse_dimension, to/from feet/inches/mm/cm
└── utils/        Shared helpers; element transform utilities

All models are immutable. Every "mutation" method (e.g. level.add_wall(w), wall.add_opening(o)) returns a new object.


Documentation

Full API reference and guides are in the docs/ directory:


Roadmap

Phase Status Description
Layer 1 — Geometry Done CRS, Point, Vector, BBox, Polygon, Curve, Transform, Segment2D/Ray2D/Line2D/Polyline2D
Layer 2 — Elements (core) Done Wall, Room, Opening, Column, Furniture, TextAnnotation, DimensionLine, SectionMark
Layer 2 — Elements (circulation/structural) Done Staircase, Ramp, Elevator, Slab, Beam, StructuralGrid, wall joining
Layer 3 — Building Done Level, Building, Land, Setbacks, ZoningInfo
Layer 5 — I/O Done JSON, SVG, GeoJSON, DXF (read+write), IFC 4.x, PNG, PDF
Layer 6 — Analysis Done Topology graph, egress, area validation, zoning compliance, daylighting, isovist
CoordinateConverter Done Graph-based multi-CRS path-finding; Point2D.to()
NURBS evaluator Done Cox–de Boor; clamped_uniform() factory; exact conic sections
Layer 4 — App infrastructure Done ElementQuery (select/filter), History (undo/redo), Viewport (view state)
Material registry Done Material, MaterialLibrary, 12 builtin presets, default_library
Level / Building utilities Done Level.replace_element(), Building.stats()BuildingStats
Analysis completeness Done Accessibility checker, room-from-walls auto-detection
JSON migration Done migrate_json() — upgrades 0.1.0 → 0.2.0 snapshots
I/O completeness Done SVG/PDF/PNG render furniture, beams, ramps, annotations, dimensions, section marks, staircases, slabs, archways; material-linked SVG fill colours; DXF annotation/dimension/section-mark layers; IFC extended export (ramp, beam, furniture, elevator)
GeoJSON round-trip Done level_from_geojson() / level_from_geojson_str() import
IFC round-trip Done building_from_ifc() / level_from_ifc() import; full element type coverage
Layer registry Done Layer model; building-level layer registry; renderer visibility filtering
Unit conversion Done parse_dimension(), to_feet/inches/mm/cm, from_feet/inches/mm/cm
Element transforms Done copy_element, mirror_element, array_element
Building utilities Done validate()ValidationReport, to_agent_context(), duplicate_level()
Spatial index Done Level.spatial_index() → Shapely STRtree over all elements
Wall geometry helpers Done Wall.start_point, Wall.end_point, Wall.facing_direction() — 8-point compass
Level batch APIs Done Level.add_walls(list), Level.add_rooms(list), Level.walls_for_room(room_id)
Structured analysis findings Done egress_report() returns structured dict with issue/suggested_fix; daylight_report() adds compliant, issue, suggested_fix per room
Enriched agent context Done Building.to_detailed_agent_context(level_index, include_walls, include_furniture, include_columns) — scoped, with wall endpoints + facing
Architectural SVG furniture symbols Done 19 plan-view symbols per furniture category — beds, sofas, chairs, tables, bathroom fixtures, kitchen, storage — with parchment/wet-area colour scheme and internal detail lines
Extended building validation Done Building.validate() now detects room overlaps (Shapely intersection) and door connectivity gaps (networkx) — every room must be reachable through doors
Floorplan Agent Protocol v1.0.0 Done Strict Pydantic inter-agent message layer — FloorplanSnapshot, AgentHandoff, MutationEnvelope, ProtocolReport, discriminated-union parse, 5 analysis adapters, JSON Schema export CLI

Brand Colors

Token Hex Use
Void #0C1018 Night sky — deep shadows, primary text on light
Vellum #E8EDF5 Tracing paper — primary text on dark, light fills
Blueprint #3B82F6 Technical lines — accents, links
Datum #F59E0B Reference point — icon handles, highlights

Contributing

Contributions are welcome. See CONTRIBUTING for setup instructions and coding standards.


License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

archit_app-0.4.0.tar.gz (319.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

archit_app-0.4.0-py3-none-any.whl (184.2 kB view details)

Uploaded Python 3

File details

Details for the file archit_app-0.4.0.tar.gz.

File metadata

  • Download URL: archit_app-0.4.0.tar.gz
  • Upload date:
  • Size: 319.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for archit_app-0.4.0.tar.gz
Algorithm Hash digest
SHA256 434b0dc72aec036706cd99f13e85c5c4a8c2bab0b9287de1c0a0baeb129324bf
MD5 32665c941d891dbfa8cd5b27d84eb2bb
BLAKE2b-256 44a98d108d31c6704292061db78186f6f12872eb3a0412594adbda67e303301f

See more details on using hashes here.

File details

Details for the file archit_app-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: archit_app-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 184.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for archit_app-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8d2e16abe22c07fc9787dc94561bac91851e0b0359312580c9a7769626a02c58
MD5 84d1d4b0e21eda354bc20dac7eb0e72f
BLAKE2b-256 7d7cedca015fe7cc3e2796a53b20cc7761d1eb55abc9d1e75a4a761936e80764

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page