fix: model groups

→ Model groups being a pain
→ Accessing some objects too shallow
This commit is contained in:
Björn Steinhagen
2025-02-09 23:05:55 +01:00
parent 7f49d0db3b
commit 25bd0093af
6 changed files with 104 additions and 63 deletions
+22 -9
View File
@@ -53,17 +53,30 @@ def automate_function(
processor.process_elements(model_root)
# Report compliance issues
compliance_summary = logger.get_summary()
for missing_property, elements in compliance_summary.items():
automate_context.attach_warning_to_objects(
category="Missing Revit Material Property",
object_ids=elements,
message=(
f"Missing {missing_property} on the object, preventing mass calculation. "
f"Update Revit object to contain the necessary properties if element is critical."
),
logger_warnings = logger.get_warnings_summary()
if logger_warnings:
for missing_property, elements in logger_warnings.items():
automate_context.attach_warning_to_objects(
category="Missing Required Revit Properties",
object_ids=elements,
message=(
f"Property '{missing_property}' is missing, which prevents carbon "
f"calculations. If this element is critical to your analysis, please "
f"update its Revit properties."
),
)
logger_successes = logger.get_successful_summary()
if logger_successes:
automate_context.attach_success_to_objects(
category="Successfully Processed",
object_ids=logger_successes,
message="Carbon calculations completed successfully for this element.",
)
# TODO: Create new version
# automate_context.create_new_version_in_project(model_root, "dev", "")
automate_context.mark_run_success("Processing completed successfully.")
except Exception as e:
+13 -3
View File
@@ -1,5 +1,5 @@
from abc import ABC, abstractmethod
from typing import Dict
from typing import Dict, List
class Logger(ABC):
@@ -14,6 +14,16 @@ class Logger(ABC):
pass
@abstractmethod
def get_summary(self) -> Dict:
"""Get summary of logged messages"""
def log_success(self, object_id: str, **kwargs) -> None:
"""Log a success message"""
pass
@abstractmethod
def get_warnings_summary(self) -> Dict:
"""Get summary of logged warning messages"""
pass
@abstractmethod
def get_successful_summary(self) -> List:
"""Get list of successfully processed elements"""
pass
+11 -1
View File
@@ -8,6 +8,7 @@ from src.interfaces.logger import Logger # Import the interface
class ComplianceLogger(Logger): # Explicitly implement Logger interface
def __init__(self):
self.missing_properties: DefaultDict[str, set] = defaultdict(set)
self.successful_elements: set = set()
self._structlog = structlog.get_logger()
def log_error(self, message: str, **kwargs) -> None:
@@ -32,8 +33,17 @@ class ComplianceLogger(Logger): # Explicitly implement Logger interface
if object_id and missing_property:
self.missing_properties[missing_property].add(object_id)
def get_summary(self) -> Dict[str, list]:
def log_success(self, object_id: str) -> None:
"""Log a successful element processing"""
self._structlog.info(f"Successfully processed element", object_id=object_id)
self.successful_elements.add(object_id)
def get_warnings_summary(self) -> Dict[str, list]:
"""Get summary of logged messages"""
return {
prop: list(elements) for prop, elements in self.missing_properties.items()
}
def get_successful_summary(self) -> list:
"""Get list of successfully processed elements"""
return list(self.successful_elements)
+53 -49
View File
@@ -4,10 +4,12 @@ from src.interfaces.material_processor import MaterialProcessor
from src.interfaces.compliance_checker import ComplianceChecker
from src.interfaces.logger import Logger
from src.utils.constants import (
APPLICATION_ID,
ELEMENTS,
NAME,
PROPERTIES,
MATERIAL_QUANTITIES,
SPECKLE_TYPE,
ID,
VOLUME,
STRUCTURAL_ASSET,
@@ -51,68 +53,70 @@ class RevitModelProcessor(ModelProcessor):
def _process_type_group(self, type_group: Any, level_name: str) -> None:
"""Process a group of elements of the same type"""
revit_objects = getattr(type_group, ELEMENTS, None)
if not revit_objects:
groups = getattr(type_group, ELEMENTS, None)
if not groups:
type_name = getattr(type_group, NAME, "Unknown")
raise ValueError(f"Invalid type structure: missing elements in {type_name}")
type_name = getattr(type_group, NAME)
for revit_object in revit_objects:
self.process_element(level_name, type_name, revit_object)
for group in groups:
revit_objects = getattr(group, ELEMENTS, None)
if not revit_objects:
raise ValueError(
f"Invalid type structure: missing elements in "
f"{getattr(group, NAME, None)}"
)
for revit_object in revit_objects:
self.process_element(level_name, type_name, revit_object)
def process_element(self, level: str, type_name: str, revit_object: Any) -> None:
"""Process a single element following original logic exactly"""
# TODO: We can probably straight up skip Line and Arc. Logging it as a warning is dumb
element_id = getattr(revit_object, ID, None)
if not element_id:
return
# First check elements
elements = getattr(revit_object, ELEMENTS, None)
if not elements:
# Check Material Quantities
properties = getattr(revit_object, PROPERTIES, None)
if not properties:
self._logger.log_warning(
f"Missing elements", object_id=element_id, missing_property=ELEMENTS
f"Missing Material Quantities",
object_id=element_id,
missing_property=PROPERTIES,
)
return
material_quantities = properties.get(MATERIAL_QUANTITIES, None)
if not material_quantities:
self._logger.log_warning(
f"Missing Material Quantities",
object_id=element_id,
missing_property=MATERIAL_QUANTITIES,
)
return
# Process each element
for element in elements:
# Check properties
properties = getattr(element, PROPERTIES, None)
if not properties:
self._logger.log_warning(
f"Missing properties",
object_id=element_id,
missing_property=PROPERTIES,
)
return
# Check Material Quantities
material_quantities = properties.get(MATERIAL_QUANTITIES, None)
if not material_quantities:
self._logger.log_warning(
f"Missing Material Quantities",
object_id=element_id,
missing_property=MATERIAL_QUANTITIES,
)
return
# Process each material
for material_name, material_data in material_quantities.items():
# Check required material properties
for required_prop in [VOLUME, STRUCTURAL_ASSET, DENSITY]:
if required_prop not in material_data:
self._logger.log_warning(
f"Missing {required_prop}",
object_id=element_id,
missing_property=required_prop,
)
return
try:
self._material_processor.process_material(
material_data, level, type_name
)
except Exception as e:
self._logger.log_error(
f"Failed to process element {element_id}", error=str(e)
# Process each material
# TODO: Project 2427 is an interesting one with compound materials
# TODO: Checkout object fefcc95c2f0ecd28a49ecdd7764e2d79. Worth skipping if volume = 0?
for material_name, material_data in material_quantities.items():
# Check required material properties
for required_prop in [VOLUME, STRUCTURAL_ASSET, DENSITY]:
if required_prop not in material_data:
self._logger.log_warning(
f"Missing {required_prop}",
object_id=element_id,
missing_property=required_prop,
)
return
try:
self._material_processor.process_material(
material_data, level, type_name
)
self._logger.log_success(element_id)
except Exception as e:
self._logger.log_error(
f"Failed to process element {element_id}", error=str(e)
)
+2
View File
@@ -1,6 +1,7 @@
REQUIRED_PROPERTIES = ["volume", "density", "structuralAsset"]
# Keys
APPLICATION_ID = "applicationId"
DENSITY = "density"
ELEMENTS = "elements"
ID = "id"
@@ -9,6 +10,7 @@ MATERIAL_QUANTITIES = "Material Quantities"
NAME = "name"
PROPERTIES = "properties"
SOURCE_APPLICATION = "sourceApplication"
SPECKLE_TYPE = "speckle_type"
STRUCTURAL_ASSET = "structuralAsset"
UNITS = "units"
VALUE = "value"
+3 -1
View File
@@ -1,3 +1,5 @@
# pytest: skip-file
from src.processors.material import RevitMaterialProcessor
from src.processors.compliance import RevitComplianceChecker
from src.processors.model import RevitModelProcessor
@@ -73,7 +75,7 @@ try:
processor.process_elements(model_root)
# Report compliance issues
compliance_summary = logger.get_summary()
compliance_summary = logger.get_warnings_summary()
print("Processing completed successfully.")