In-memory IFC session (no FastMCP dependency). Designed to work in: - FastMCP server (single global session) - Embedded runtimes like Pyodide (one session per browser tab/worker)
| 212 | |
| 213 | @dataclass |
| 214 | class IfcSession: |
| 215 | """In-memory IFC session (no FastMCP dependency). |
| 216 | |
| 217 | Designed to work in: |
| 218 | - FastMCP server (single global session) |
| 219 | - Embedded runtimes like Pyodide (one session per browser tab/worker) |
| 220 | """ |
| 221 | |
| 222 | model: ifcopenshell.file | None = None |
| 223 | model_path: str | None = None |
| 224 | |
| 225 | # ----------------- |
| 226 | # Session lifecycle |
| 227 | # ----------------- |
| 228 | def _require_model(self) -> ifcopenshell.file: |
| 229 | if self.model is None: |
| 230 | raise IfcSessionError("No model loaded. Call ifc_load() or ifc_new() first.") |
| 231 | return self.model |
| 232 | |
| 233 | def ifc_new(self, schema: str = "IFC4") -> dict[str, Any]: |
| 234 | """Create a new empty IFC model in memory.""" |
| 235 | self.model = ifcopenshell.file(schema=schema) |
| 236 | self.model_path = None |
| 237 | return {"ok": True, "schema": self.model.schema, "entities": sum(1 for _ in self.model)} |
| 238 | |
| 239 | def ifc_load(self, path: str) -> str: |
| 240 | """Open an IFC file into memory. Returns confirmation string.""" |
| 241 | self.model = ifcopenshell.open(path) |
| 242 | self.model_path = path |
| 243 | count = sum(1 for _ in self.model) |
| 244 | return f"Loaded {path}: schema {self.model.schema}, {count} entities" |
| 245 | |
| 246 | def ifc_save(self, path: str = "") -> str: |
| 247 | """Write the in-memory model to disk. Empty path overwrites the original file.""" |
| 248 | model = self._require_model() |
| 249 | target = path if path else self.model_path |
| 250 | if not target: |
| 251 | raise IfcSessionError("No path specified and no original path available.") |
| 252 | model.write(target) |
| 253 | return f"Saved to {target}" |
| 254 | |
| 255 | def ifc_reset(self) -> dict[str, Any]: |
| 256 | """Drop the in-memory model.""" |
| 257 | self.model = None |
| 258 | self.model_path = None |
| 259 | return {"ok": True} |
| 260 | |
| 261 | # ------------- |
| 262 | # Query tools |
| 263 | # ------------- |
| 264 | def ifc_summary(self) -> dict[str, Any]: |
| 265 | """Model overview: schema, entity counts, project info.""" |
| 266 | return summary.summary(self._require_model()) |
| 267 | |
| 268 | def ifc_tree(self) -> dict[str, Any] | list[dict[str, Any]]: |
| 269 | """Full spatial hierarchy tree (Project -> Site -> Building -> Storeys -> Elements).""" |
| 270 | return tree.tree(self._require_model()) |
| 271 |
no outgoing calls