feat: inserting concrete architecture part 2

This commit is contained in:
Björn Steinhagen
2025-02-25 10:20:35 +01:00
parent 6ea1ff65e2
commit 2116e3afb7
2 changed files with 79 additions and 41 deletions
+51 -16
View File
@@ -9,7 +9,11 @@ from speckle_automate import (
from typing import Dict, Generator, Any, List from typing import Dict, Generator, Any, List
from src.domain.carbon.databases.enums import SteelDatabase, TimberDatabase from src.domain.carbon.databases.enums import (
SteelDatabase,
TimberDatabase,
ConcreteDatabase,
)
from src.infrastructure.logging import Logging from src.infrastructure.logging import Logging
from src.services.carbon_calculator import CarbonCalculator from src.services.carbon_calculator import CarbonCalculator
from src.services.element_processor import ElementProcessor from src.services.element_processor import ElementProcessor
@@ -42,7 +46,7 @@ class FunctionInputs(AutomateBase):
) )
concrete_database: str = Field( concrete_database: str = Field(
default=ConcreteDatabase.GUL_LOW_AIR.value, default=ConcreteDatabase.GulLowAir.value,
title="Concrete Database", title="Concrete Database",
description="Database used for the GWP of concrete objects", description="Database used for the GWP of concrete objects",
json_schema_extra={"oneOf": create_one_of_enum(ConcreteDatabase)}, json_schema_extra={"oneOf": create_one_of_enum(ConcreteDatabase)},
@@ -125,18 +129,24 @@ class FunctionInputs(AutomateBase):
class RevitCarbonAnalyzer: class RevitCarbonAnalyzer:
"""Main application for analyzing carbon in Revit models.""" """Main application for analyzing carbon in Revit models."""
def __init__(self, steel_database: str, timber_database: str): def __init__(
self,
steel_database: str,
timber_database: str,
concrete_database: str,
country: str,
reinforcement_rates: Dict[str, float],
):
self.material_processor = MaterialProcessor() self.material_processor = MaterialProcessor()
self.element_processor = ElementProcessor( self.element_processor = ElementProcessor(
material_processor=self.material_processor, logger=Logging() material_processor=self.material_processor, logger=Logging()
) )
self.carbon_calculator = CarbonCalculator( self.carbon_calculator = CarbonCalculator(
steel_database=steel_database.value steel_database=steel_database,
if isinstance(steel_database, SteelDatabase) timber_database=timber_database,
else steel_database, concrete_database=concrete_database,
timber_database=timber_database.value country=country,
if isinstance(timber_database, SteelDatabase) custom_reinforcement_rates=reinforcement_rates,
else timber_database,
) )
def analyze_model(self, model_root) -> dict: def analyze_model(self, model_root) -> dict:
@@ -146,7 +156,7 @@ class RevitCarbonAnalyzer:
"skipped_elements": [], "skipped_elements": [],
"errors": [], "errors": [],
"total_carbon": 0.0, "total_carbon": 0.0,
"missing_factors": {"timber": [], "steel": []}, "missing_factors": {"timber": [], "steel": [], "concrete": []},
} }
# Process each element # Process each element
@@ -170,9 +180,14 @@ class RevitCarbonAnalyzer:
) )
# Get missing factors # Get missing factors
missing_timber, missing_steel = self.carbon_calculator.get_missing_factors() (
missing_timber,
missing_steel,
missing_concrete,
) = self.carbon_calculator.get_missing_factors()
results["missing_factors"]["timber"] = missing_timber results["missing_factors"]["timber"] = missing_timber
results["missing_factors"]["steel"] = missing_steel results["missing_factors"]["steel"] = missing_steel
results["missing_factors"]["concrete"] = missing_concrete
# Log missing factors # Log missing factors
if missing_timber: if missing_timber:
@@ -185,6 +200,11 @@ class RevitCarbonAnalyzer:
for item in missing_steel: for item in missing_steel:
print(f" - {item}") print(f" - {item}")
if missing_concrete:
print(f"Missing concrete factors ({len(missing_concrete)}):")
for item in missing_concrete:
print(f" - {item}")
return results return results
def _process_single_element(self, element: Dict) -> Dict: def _process_single_element(self, element: Dict) -> Dict:
@@ -246,17 +266,32 @@ def automate_function(
# Get string values from enums if needed # Get string values from enums if needed
steel_db = function_inputs.steel_database steel_db = function_inputs.steel_database
timber_db = function_inputs.timber_database timber_db = function_inputs.timber_database
concrete_db = function_inputs.concrete_database
country = function_inputs.country
# Ensure we're working with string values # Create custom reinforcement rates dictionary
if hasattr(steel_db, "value"): custom_reinforcement_rates = {
steel_db = steel_db.value "Grade Beam": function_inputs.reinforcement_grade_beam,
if hasattr(timber_db, "value"): "Slab on Grade": function_inputs.reinforcement_slab_on_grade,
timber_db = timber_db.value "Pad Footing": function_inputs.reinforcement_pad_footing,
"Pile": function_inputs.reinforcement_pile,
"Strip Footing": function_inputs.reinforcement_strip_footing,
"Pile Cap": function_inputs.reinforcement_pile_cap,
"Walls - wind/gravity": function_inputs.reinforcement_gravity_wall,
"Column": function_inputs.reinforcement_column,
"Shear Walls": function_inputs.reinforcement_shear_wall,
"Concrete Slabs": function_inputs.reinforcement_concrete_slab,
"Beams": function_inputs.reinforcement_beam,
"Topping Slabs": function_inputs.reinforcement_topping_slab,
}
# Initialize analyzer # Initialize analyzer
analyzer = RevitCarbonAnalyzer( analyzer = RevitCarbonAnalyzer(
steel_database=steel_db, steel_database=steel_db,
timber_database=timber_db, timber_database=timber_db,
concrete_database=concrete_db,
country=country,
reinforcement_rates=custom_reinforcement_rates,
) )
# Get commit root # Get commit root
+28 -25
View File
@@ -51,7 +51,10 @@ class CarbonCalculator:
for material in element.materials: for material in element.materials:
try: try:
result = self._calculate_material_carbon(material) if material.type == MaterialType.CONCRETE:
result = self._calculate_concrete_carbon(material, element.category)
else:
result = self._calculate_material_carbon(material)
results[material.properties.name] = result results[material.properties.name] = result
except Exception as e: except Exception as e:
# Track missing factors # Track missing factors
@@ -66,6 +69,13 @@ class CarbonCalculator:
self._missing_steel_factors.add( self._missing_steel_factors.add(
material.grade or material.properties.name material.grade or material.properties.name
) )
elif material.type == MaterialType.CONCRETE:
# Track missing concrete factors
strength = str(int(material.properties.compressive_strength))
element_type = self._map_element_category_to_concrete_type(
element.category
)
self._missing_concrete_factors.add(f"{strength}_{element_type}")
print( print(
f"Error calculating carbon for {material.properties.name}: {str(e)}" f"Error calculating carbon for {material.properties.name}: {str(e)}"
@@ -73,14 +83,20 @@ class CarbonCalculator:
return results return results
def _calculate_material_carbon(self, material: Material) -> CarbonResult: def _calculate_material_carbon(
self, material: Material, element_category: Optional[ElementCategory] = None
) -> CarbonResult:
"""Calculate carbon emissions for a single material.""" """Calculate carbon emissions for a single material."""
if material.type == MaterialType.METAL: if material.type == MaterialType.METAL:
return self._calculate_metal_carbon(material) return self._calculate_metal_carbon(material)
elif material.type == MaterialType.WOOD: elif material.type == MaterialType.WOOD:
return self._calculate_wood_carbon(material) return self._calculate_wood_carbon(material)
elif material.type == MaterialType.CONCRETE: elif material.type == MaterialType.CONCRETE:
return self._calculate_concrete_carbon(material) if element_category is None:
raise ValueError(
"Element category is required for concrete carbon calculation"
)
return self._calculate_concrete_carbon(material, element_category)
else: else:
raise ValueError(f"Unsupported material type: {material.type}") raise ValueError(f"Unsupported material type: {material.type}")
@@ -135,7 +151,7 @@ class CarbonCalculator:
) )
def _calculate_concrete_carbon( def _calculate_concrete_carbon(
self, material: Material, element: BuildingElement self, material: Material, element_category: ElementCategory
) -> CarbonResult: ) -> CarbonResult:
"""Calculate carbon emissions for concrete, including reinforcement.""" """Calculate carbon emissions for concrete, including reinforcement."""
if not material.properties.compressive_strength: if not material.properties.compressive_strength:
@@ -157,7 +173,7 @@ class CarbonCalculator:
strength = str(strength_mpa) strength = str(strength_mpa)
# Map element category to concrete element type for the database # Map element category to concrete element type for the database
element_type = self._map_element_category_to_concrete_type(element) element_type = self._map_element_category_to_concrete_type(element_category)
# Create cache key for concrete factors # Create cache key for concrete factors
concrete_cache_key = f"{strength}_{element_type}" concrete_cache_key = f"{strength}_{element_type}"
@@ -218,12 +234,10 @@ class CarbonCalculator:
return result return result
@staticmethod @staticmethod
def _map_element_category_to_concrete_type(element: BuildingElement) -> str: def _map_element_category_to_concrete_type(
element_category: ElementCategory,
) -> str:
"""Map BuildingElement category to concrete element type for database lookup.""" """Map BuildingElement category to concrete element type for database lookup."""
# Start with the element name
element_name = (
getattr(element, "name", "").lower() if hasattr(element, "name") else ""
)
# Default mappings # Default mappings
category_mapping = { category_mapping = {
@@ -234,24 +248,13 @@ class CarbonCalculator:
ElementCategory.FOUNDATION: "Foundation", ElementCategory.FOUNDATION: "Foundation",
} }
# Check for specific element names in BuildingElement # Return the mapped type or default to "Beam" if unknown
if element_name: return category_mapping.get(element_category, "Beam")
if "grade beam" in element_name:
return "Grade Beam"
elif "slab on grade" in element_name:
return "Slab on Grade"
elif "pad footing" in element_name:
return "Foundation" # Or more specific if needed
# Add other specific mappings as needed
# Fall back to category mapping def get_missing_factors(self) -> Tuple[List[str], List[str], List[str]]:
return category_mapping.get(
element.category, "Beam"
) # Default to Beam if unknown
def get_missing_factors(self) -> Tuple[List[str], List[str]]:
"""Return lists of materials that had no emission factor.""" """Return lists of materials that had no emission factor."""
return ( return (
sorted(list(self._missing_timber_factors)), sorted(list(self._missing_timber_factors)),
sorted(list(self._missing_steel_factors)), sorted(list(self._missing_steel_factors)),
sorted(list(self._missing_concrete_factors)),
) )