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 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.services.carbon_calculator import CarbonCalculator
from src.services.element_processor import ElementProcessor
@@ -42,7 +46,7 @@ class FunctionInputs(AutomateBase):
)
concrete_database: str = Field(
default=ConcreteDatabase.GUL_LOW_AIR.value,
default=ConcreteDatabase.GulLowAir.value,
title="Concrete Database",
description="Database used for the GWP of concrete objects",
json_schema_extra={"oneOf": create_one_of_enum(ConcreteDatabase)},
@@ -125,18 +129,24 @@ class FunctionInputs(AutomateBase):
class RevitCarbonAnalyzer:
"""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.element_processor = ElementProcessor(
material_processor=self.material_processor, logger=Logging()
)
self.carbon_calculator = CarbonCalculator(
steel_database=steel_database.value
if isinstance(steel_database, SteelDatabase)
else steel_database,
timber_database=timber_database.value
if isinstance(timber_database, SteelDatabase)
else timber_database,
steel_database=steel_database,
timber_database=timber_database,
concrete_database=concrete_database,
country=country,
custom_reinforcement_rates=reinforcement_rates,
)
def analyze_model(self, model_root) -> dict:
@@ -146,7 +156,7 @@ class RevitCarbonAnalyzer:
"skipped_elements": [],
"errors": [],
"total_carbon": 0.0,
"missing_factors": {"timber": [], "steel": []},
"missing_factors": {"timber": [], "steel": [], "concrete": []},
}
# Process each element
@@ -170,9 +180,14 @@ class RevitCarbonAnalyzer:
)
# 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"]["steel"] = missing_steel
results["missing_factors"]["concrete"] = missing_concrete
# Log missing factors
if missing_timber:
@@ -185,6 +200,11 @@ class RevitCarbonAnalyzer:
for item in missing_steel:
print(f" - {item}")
if missing_concrete:
print(f"Missing concrete factors ({len(missing_concrete)}):")
for item in missing_concrete:
print(f" - {item}")
return results
def _process_single_element(self, element: Dict) -> Dict:
@@ -246,17 +266,32 @@ def automate_function(
# Get string values from enums if needed
steel_db = function_inputs.steel_database
timber_db = function_inputs.timber_database
concrete_db = function_inputs.concrete_database
country = function_inputs.country
# Ensure we're working with string values
if hasattr(steel_db, "value"):
steel_db = steel_db.value
if hasattr(timber_db, "value"):
timber_db = timber_db.value
# Create custom reinforcement rates dictionary
custom_reinforcement_rates = {
"Grade Beam": function_inputs.reinforcement_grade_beam,
"Slab on Grade": function_inputs.reinforcement_slab_on_grade,
"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
analyzer = RevitCarbonAnalyzer(
steel_database=steel_db,
timber_database=timber_db,
concrete_database=concrete_db,
country=country,
reinforcement_rates=custom_reinforcement_rates,
)
# Get commit root
+27 -24
View File
@@ -51,6 +51,9 @@ class CarbonCalculator:
for material in element.materials:
try:
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
except Exception as e:
@@ -66,6 +69,13 @@ class CarbonCalculator:
self._missing_steel_factors.add(
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(
f"Error calculating carbon for {material.properties.name}: {str(e)}"
@@ -73,14 +83,20 @@ class CarbonCalculator:
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."""
if material.type == MaterialType.METAL:
return self._calculate_metal_carbon(material)
elif material.type == MaterialType.WOOD:
return self._calculate_wood_carbon(material)
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:
raise ValueError(f"Unsupported material type: {material.type}")
@@ -135,7 +151,7 @@ class CarbonCalculator:
)
def _calculate_concrete_carbon(
self, material: Material, element: BuildingElement
self, material: Material, element_category: ElementCategory
) -> CarbonResult:
"""Calculate carbon emissions for concrete, including reinforcement."""
if not material.properties.compressive_strength:
@@ -157,7 +173,7 @@ class CarbonCalculator:
strength = str(strength_mpa)
# 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
concrete_cache_key = f"{strength}_{element_type}"
@@ -218,12 +234,10 @@ class CarbonCalculator:
return result
@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."""
# Start with the element name
element_name = (
getattr(element, "name", "").lower() if hasattr(element, "name") else ""
)
# Default mappings
category_mapping = {
@@ -234,24 +248,13 @@ class CarbonCalculator:
ElementCategory.FOUNDATION: "Foundation",
}
# Check for specific element names in BuildingElement
if element_name:
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
# Return the mapped type or default to "Beam" if unknown
return category_mapping.get(element_category, "Beam")
# Fall back to category mapping
return category_mapping.get(
element.category, "Beam"
) # Default to Beam if unknown
def get_missing_factors(self) -> Tuple[List[str], List[str]]:
def get_missing_factors(self) -> Tuple[List[str], List[str], List[str]]:
"""Return lists of materials that had no emission factor."""
return (
sorted(list(self._missing_timber_factors)),
sorted(list(self._missing_steel_factors)),
sorted(list(self._missing_concrete_factors)),
)