Merge pull request #6 from bjoernsteinhagen/charles/carbon_processor

Charles/carbon processor
This commit is contained in:
Björn Steinhagen
2025-02-23 21:27:54 +01:00
committed by GitHub
12 changed files with 244 additions and 21 deletions
+6 -10
View File
@@ -6,26 +6,20 @@ from speckle_automate import (
)
from src.applications.revit.revit_material_processor import RevitMaterialProcessor
from src.applications.revit.revit_carbon_processor import RevitCarbonProcessor
from src.applications.revit.revit_compliance import RevitCompliance
from src.applications.revit.revit_model import RevitModel
from src.applications.revit.revit_source_validator import RevitSourceValidator
from src.carbon.aggregator import MassAggregator
from src.applications.revit.revit_logger import RevitLogger
from src.core.base import Model
from src.carbon import WoodSupplier
# TODO: Function inputs
class FunctionInputs(AutomateBase):
"""User-defined function inputs."""
whisper_message: SecretStr = Field(title="This is a secret message")
forbidden_speckle_type: str = Field(
title="Forbidden speckle type",
description=(
"If a object has the following speckle_type,"
" it will be marked with an error."
),
)
wood_supplier: WoodSupplier = WoodSupplier.INDUSTRY_AVERAGE
def automate_function(
@@ -77,7 +71,7 @@ def automate_function(
automate_context.attach_success_to_objects(
category=category,
object_ids=object_ids,
message="Carbon calculations completed successfully for these elements.",
message="Carbon calculations completed successfully for these elements!",
)
for category, object_ids in logger_infos.items():
@@ -133,11 +127,13 @@ def configure_components() -> Model:
material_processor = RevitMaterialProcessor(
mass_aggregator, logger
) # Material handler to "inject"
carbon_processor = RevitCarbonProcessor()
compliance_checker = RevitCompliance(logger) # Compliance checker to "inject"
# Create and return the main processor with dependencies "injected"
return RevitModel(
material_processor=material_processor,
carbon_processor=carbon_processor,
compliance_checker=compliance_checker,
logger=logger,
)
@@ -0,0 +1,48 @@
from src.core.base import CarbonProcessor
from src.core.types import ( MetalClass, WoodClass )
from src.carbon.types import CarbonData
from src.carbon.data import ( wood_factors, metal_factors )
from src.applications.revit.utils.material_type_handler import (
MaterialType,
)
from src.applications.revit.utils.constants import (
PROPERTIES,
)
import json
class RevitCarbonProcessor(CarbonProcessor):
"""Implementation of CarbonProcessor for Revit context."""
def process(
self, model_object
) -> CarbonData:
"""Compute embodied carbon per-element based previously asserted material properties.
Args:
model_object (Any): Model object to process
"""
material_type = model_object[PROPERTIES]["Embodied Carbon Data"]["type"]
print(material_type)
match material_type:
case MaterialType.CONCRETE.value:
# TODO
pass
case MaterialType.WOOD.value:
# TODO
pass
case MaterialType.METAL.value:
material_quantities = model_object[PROPERTIES]["Material Quantities"]["FE_Steel"]
volume, density = material_quantities.volume.value, material_quantities.density.value
mass = volume * density
# Default to hot rolled
factor = metal_factors[MetalClass.HOT_ROLLED]
embodied_carbon = mass * factor
model_object[PROPERTIES]["Embodied Carbon Data"]["category"] = MetalClass.HOT_ROLLED
model_object[PROPERTIES]["Embodied Carbon Data"]["factor"] = factor
model_object[PROPERTIES]["Embodied Carbon Data"]["embodied_carbon"] = embodied_carbon
pass
+14 -6
View File
@@ -2,6 +2,7 @@ from typing import Any, Tuple, Dict, List
from src.core.base import Model
from src.core.base import MaterialProcessor
from src.core.base import Compliance
from src.core.base import CarbonProcessor
from src.core.base.logger import Logger
from src.applications.revit.utils.constants import (
ELEMENTS,
@@ -20,10 +21,12 @@ class RevitModel(Model):
def __init__(
self,
material_processor: MaterialProcessor,
carbon_processor: CarbonProcessor,
compliance_checker: Compliance,
logger: Logger,
):
self._material_processor = material_processor
self._carbon_processor = carbon_processor
self._compliance_checker = compliance_checker
self._logger = logger
@@ -84,12 +87,6 @@ class RevitModel(Model):
object_id, material_data, level, type_name
)
if processed_material: # If processing was successful
self._logger.log_success(
object_id=object_id,
category="Successfully Processed",
message="Carbon calculations completed successfully for this element.",
)
model_object[PROPERTIES]["Embodied Carbon Data"] = vars(
processed_material
)
@@ -99,6 +96,17 @@ class RevitModel(Model):
"element"
] = self._categorize(type_name)
processed_carbon = self._carbon_processor.process(model_object)
if processed_carbon:
self._logger.log_success(
object_id=object_id,
category="Successfully Processed",
message="Carbon calculations completed successfully for this element.",
)
model_object[PROPERTIES]["Embodied Carbon Calculations"] = vars(processed_carbon)
except Exception as e:
# Log any processing errors that occur
self._logger.log_error(object_id, "Material Processing Error", str(e))
+9
View File
@@ -0,0 +1,9 @@
from .data import ( metal_factors, wood_factors )
from .types import ( CarbonData, WoodSupplier )
__all__ = [
"metal_factors",
"wood_factors",
"CarbonData",
"WoodSupplier"
]
+98
View File
@@ -0,0 +1,98 @@
from enum import Enum
from .types import WoodSupplier
from src.core.types import ( MetalClass, WoodClass )
"""kgCO2e/kg"""
metal_factors = {
MetalClass.HOT_ROLLED: 1.22,
MetalClass.HSS: 1.99,
MetalClass.PLATE: 1.73,
MetalClass.REBAR: 0.854,
MetalClass.OWSJ: 1.380,
MetalClass.FASTENERS: 1.730,
MetalClass.METAL_DECK: 2.370,
}
"""kgCO2e/m3"""
wood_factors = {
WoodSupplier.INDUSTRY_AVERAGE: {
WoodClass.GLULAM: 113,
WoodClass.CLT: 135,
WoodClass.LVL: 265,
WoodClass.SOFTWOOD_LUMBER: 56,
WoodClass.SOFTWOOD_PLYWOOD: 142,
WoodClass.WOOD_JOISTS: 2,
WoodClass.REDWOOD_LUMBER: 38,
WoodClass.ORIENTED_STRAND_BOARD: 212,
WoodClass.GLT_NLT_DLT: 123
},
WoodSupplier.ATHENA: {
WoodClass.GLULAM: 107,
WoodClass.CLT: 69,
WoodClass.LVL: 169,
WoodClass.SOFTWOOD_LUMBER: 48,
WoodClass.SOFTWOOD_PLYWOOD: 65,
WoodClass.WOOD_JOISTS: None,
WoodClass.REDWOOD_LUMBER: None,
WoodClass.ORIENTED_STRAND_BOARD: 182,
WoodClass.GLT_NLT_DLT: 0
},
WoodSupplier.STRUCTURLAM: {
WoodClass.GLULAM: 115,
WoodClass.CLT: 124,
WoodClass.LVL: None,
WoodClass.SOFTWOOD_LUMBER: None,
WoodClass.SOFTWOOD_PLYWOOD: None,
WoodClass.WOOD_JOISTS: None,
WoodClass.REDWOOD_LUMBER: None,
WoodClass.ORIENTED_STRAND_BOARD: None,
WoodClass.GLT_NLT_DLT: 0
},
WoodSupplier.AWC_CWC: {
WoodClass.GLULAM: 137,
WoodClass.CLT: 0,
WoodClass.LVL: 361,
WoodClass.SOFTWOOD_LUMBER: 63,
WoodClass.SOFTWOOD_PLYWOOD: 219,
WoodClass.WOOD_JOISTS: 2,
WoodClass.REDWOOD_LUMBER: 38,
WoodClass.ORIENTED_STRAND_BOARD: 243,
WoodClass.GLT_NLT_DLT: 0
},
WoodSupplier.KATERRA: {
WoodClass.GLULAM: None,
WoodClass.CLT: 158,
WoodClass.LVL: None,
WoodClass.SOFTWOOD_LUMBER: None,
WoodClass.SOFTWOOD_PLYWOOD: None,
WoodClass.WOOD_JOISTS: None,
WoodClass.REDWOOD_LUMBER: None,
WoodClass.ORIENTED_STRAND_BOARD: None,
WoodClass.GLT_NLT_DLT: 0
},
WoodSupplier.NORDIC_STRUCTURES: {
WoodClass.GLULAM: 100,
WoodClass.CLT: 122,
WoodClass.LVL: None,
WoodClass.SOFTWOOD_LUMBER: None,
WoodClass.SOFTWOOD_PLYWOOD: None,
WoodClass.WOOD_JOISTS: None,
WoodClass.REDWOOD_LUMBER: None,
WoodClass.ORIENTED_STRAND_BOARD: None,
WoodClass.GLT_NLT_DLT: 0
},
WoodSupplier.BINDERHOLZ: {
WoodClass.GLULAM: 118,
WoodClass.CLT: 200,
WoodClass.LVL: None,
WoodClass.SOFTWOOD_LUMBER: None,
WoodClass.SOFTWOOD_PLYWOOD: None,
WoodClass.WOOD_JOISTS: None,
WoodClass.REDWOOD_LUMBER: None,
WoodClass.ORIENTED_STRAND_BOARD: None,
WoodClass.GLT_NLT_DLT: 0
}
}
+19
View File
@@ -0,0 +1,19 @@
from enum import Enum
from dataclasses import dataclass
class WoodSupplier(str, Enum):
INDUSTRY_AVERAGE = "Industry Average"
ATHENA = "Athena, 2021"
STRUCTURLAM = "Structurlam, 2020"
AWC_CWC = "AWC, CWC, 2018"
KATERRA = "Katerra, 2020"
NORDIC_STRUCTURES = "Nordic Structures, 2018"
BINDERHOLZ = "Binderholz, 2019"
@dataclass
class CarbonData:
"""Data class for embodied carbon data"""
factor: float
embodied_carbon: float
+2
View File
@@ -3,6 +3,7 @@ from .model import Model
from .source_validator import SourceApplicationValidator
from .material_processor import MaterialProcessor
from .compliance import Compliance
from .carbon_processor import CarbonProcessor
__all__ = [
"Logger",
@@ -10,4 +11,5 @@ __all__ = [
"SourceApplicationValidator",
"MaterialProcessor",
"Compliance",
"CarbonProcessor"
]
+17
View File
@@ -0,0 +1,17 @@
from abc import ABC, abstractmethod
from typing import Any
class CarbonProcessor(ABC):
"""Interface for processing embodied carbon on an object."""
@abstractmethod
def process(
self, modeL_object: Any
) -> None:
"""Compute embodied carbon per-element based previously asserted material properties.
Args:
model_object (Any): Model object to process
"""
pass
+6 -1
View File
@@ -1,3 +1,8 @@
from .material_class import ( MetalClass, WoodClass )
from .material_data import MaterialData
__all__ = ["MaterialData"]
__all__ = [
"MaterialData",
"MetalClass",
"WoodClass"
]
+21
View File
@@ -0,0 +1,21 @@
from enum import Enum
class MetalClass(Enum):
HOT_ROLLED = "Hot Rolled"
HSS = "HSS"
PLATE = "Plate"
REBAR = "Rebar"
OWSJ = "OWSJ"
FASTENERS = "Fasteners"
METAL_DECK = "Metal Deck"
class WoodClass(Enum):
GLULAM = "Glulam"
CLT = "CLT"
LVL = "LVL"
SOFTWOOD_LUMBER = "Softwood Lumber"
SOFTWOOD_PLYWOOD = "Softwood Plywood"
WOOD_JOISTS = "Wood Joists"
REDWOOD_LUMBER = "Redwood Lumber"
ORIENTED_STRAND_BOARD = "Oriented Strand Board"
GLT_NLT_DLT = "GLT/NLT/DLT"
+3
View File
@@ -1,6 +1,7 @@
# pytest: skip-file
from src.applications.revit.revit_material_processor import RevitMaterialProcessor
from src.applications.revit.revit_carbon_processor import RevitCarbonProcessor
from src.applications.revit.revit_compliance import RevitCompliance
from src.applications.revit.revit_model import RevitModel
from src.applications.revit.revit_source_validator import RevitSourceValidator
@@ -43,12 +44,14 @@ def create_processor_chain() -> tuple[RevitModel, RevitLogger]:
# Create processors
material_processor = RevitMaterialProcessor(mass_aggregator, logger)
carbon_processor = RevitCarbonProcessor()
compliance_checker = RevitCompliance(logger)
# Create and return the main processor with logger
return (
RevitModel(
material_processor=material_processor,
carbon_processor=carbon_processor,
compliance_checker=compliance_checker,
logger=logger,
),
+1 -4
View File
@@ -22,10 +22,7 @@ def test_function_run(test_automation_run_data: AutomationRunData, test_automati
automate_sdk = run_function(
automation_context,
automate_function,
FunctionInputs(
forbidden_speckle_type="None",
whisper_message=SecretStr("testing automatically"),
),
FunctionInputs(),
)
assert automate_sdk.run_status == AutomationStatus.SUCCEEDED