Skip to content

Commit 74cdc71

Browse files
committed
add simple two room example
1 parent f4655f8 commit 74cdc71

File tree

13 files changed

+843
-9
lines changed

13 files changed

+843
-9
lines changed

src/facility_layout/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Copyright 2025, Battelle Energy Alliance, LLC All Rights Reserved
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Copyright 2025, Battelle Energy Alliance, LLC All Rights Reserved
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright 2025, Battelle Energy Alliance, LLC All Rights Reserved
2+
3+
from dataclasses import dataclass
4+
from typing import Optional, List, Tuple
5+
6+
7+
@dataclass
8+
class Connection:
9+
"""Represents a connection requirement between equipment."""
10+
11+
id: str
12+
from_equipment_id: str
13+
to_equipment_id: str
14+
connection_type: str # "pipe", "cable", "duct"
15+
from_port_index: int # Index into from_equipment.ports
16+
to_port_index: int # Index into to_equipment.ports
17+
path: Optional[List[Tuple[float, float]]] = None # Computed path points
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2025, Battelle Energy Alliance, LLC All Rights Reserved
2+
3+
from dataclasses import dataclass
4+
from typing import List, Tuple, Optional
5+
from enum import Enum
6+
7+
8+
class PortType(Enum):
9+
"""Types of connections that can be made between equipment."""
10+
11+
PIPE_IN = "pipe_in"
12+
PIPE_OUT = "pipe_out"
13+
ELECTRICAL = "electrical"
14+
CONTROL = "control"
15+
16+
17+
@dataclass
18+
class Port:
19+
"""Connection point on equipment."""
20+
21+
port_type: PortType
22+
side: str # "north", "south", "east", "west"
23+
relative_position: float # 0.0 to 1.0 along the side
24+
25+
26+
@dataclass
27+
class Equipment:
28+
"""Represents a piece of equipment to be placed."""
29+
30+
id: str
31+
equipment_type: str
32+
dimensions: Tuple[float, float] # (width, height) in meters
33+
clearance_required: float # meters of clear space needed around equipment
34+
ports: List[Port]
35+
assigned_space: Optional[str] = None # ID of space this must be placed in
36+
37+
@property
38+
def width(self) -> float:
39+
return self.dimensions[0]
40+
41+
@property
42+
def height(self) -> float:
43+
return self.dimensions[1]
44+
45+
@property
46+
def area(self) -> float:
47+
return self.width * self.height
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Copyright 2025, Battelle Energy Alliance, LLC All Rights Reserved
2+
3+
from dataclasses import dataclass, field
4+
from typing import Dict, List, Tuple, Optional
5+
from .equipment import Equipment
6+
from .spatial import Space, Building, SpaceConstraint
7+
from .connections import Connection
8+
9+
10+
@dataclass
11+
class Placement:
12+
"""Represents the placement of equipment within a space."""
13+
14+
equipment_id: str
15+
space_id: str
16+
position: Tuple[float, float] # (x, y) relative to space origin
17+
rotation: float = 0.0 # degrees
18+
19+
20+
@dataclass
21+
class Layout:
22+
"""Complete facility layout solution."""
23+
24+
buildings: List[Building]
25+
spaces: List[Space]
26+
equipment: List[Equipment]
27+
placements: Dict[str, Placement] # equipment_id -> Placement
28+
connections: List[Connection]
29+
space_constraints: List[SpaceConstraint] = field(default_factory=list)
30+
score: Optional[float] = None
31+
metadata: Dict = field(default_factory=dict)
32+
33+
def get_equipment_absolute_position(
34+
self, equipment_id: str
35+
) -> Optional[Tuple[float, float]]:
36+
"""Calculate absolute position of equipment considering all hierarchy levels."""
37+
if equipment_id not in self.placements:
38+
return None
39+
40+
placement = self.placements[equipment_id]
41+
space = next((s for s in self.spaces if s.id == placement.space_id), None)
42+
if not space:
43+
return None
44+
45+
# Start with position within space
46+
x, y = placement.position
47+
48+
# Add space position within building
49+
if space.position:
50+
x += space.position[0]
51+
y += space.position[1]
52+
53+
# Add building position on site
54+
if space.parent_building:
55+
building = next(
56+
(b for b in self.buildings if b.id == space.parent_building), None
57+
)
58+
if building and building.position:
59+
x += building.position[0]
60+
y += building.position[1]
61+
62+
return (x, y)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2025, Battelle Energy Alliance, LLC All Rights Reserved
2+
3+
from dataclasses import dataclass, field
4+
from typing import List, Tuple, Optional, Dict
5+
from enum import Enum
6+
7+
8+
class SpaceRelation(Enum):
9+
"""How two spaces relate to each other."""
10+
11+
ADJACENT_NORTH = "adjacent_north"
12+
ADJACENT_SOUTH = "adjacent_south"
13+
ADJACENT_EAST = "adjacent_east"
14+
ADJACENT_WEST = "adjacent_west"
15+
SEPARATED = "separated"
16+
17+
18+
@dataclass
19+
class Space:
20+
"""Represents a room or area within a building."""
21+
22+
id: str
23+
dimensions: Tuple[float, float] # (width, height) in meters
24+
space_type: str # "room", "corridor", "outdoor", etc.
25+
parent_building: Optional[str] = None # ID of containing building
26+
position: Optional[Tuple[float, float]] = None # (x, y) relative to building origin
27+
28+
@property
29+
def width(self) -> float:
30+
return self.dimensions[0]
31+
32+
@property
33+
def height(self) -> float:
34+
return self.dimensions[1]
35+
36+
@property
37+
def area(self) -> float:
38+
return self.width * self.height
39+
40+
41+
@dataclass
42+
class Building:
43+
"""Represents a building containing multiple spaces."""
44+
45+
id: str
46+
position: Optional[Tuple[float, float]] = None # (x, y) on site
47+
spaces: List[Space] = field(default_factory=list)
48+
49+
def add_space(self, space: Space) -> None:
50+
space.parent_building = self.id
51+
self.spaces.append(space)
52+
53+
54+
@dataclass
55+
class SpaceConstraint:
56+
"""Defines a relationship constraint between two spaces."""
57+
58+
space1_id: str
59+
space2_id: str
60+
relation: SpaceRelation
61+
gap: float = 0.0 # meters of gap between spaces (for corridors, etc.)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Copyright 2025, Battelle Energy Alliance, LLC All Rights Reserved

0 commit comments

Comments
 (0)