feat: inserting concrete architecture part 2
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user