Merge pull request #6 from bjoernsteinhagen/charles/carbon_processor
Charles/carbon processor
This commit is contained in:
@@ -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
|
||||
@@ -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))
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from .data import ( metal_factors, wood_factors )
|
||||
from .types import ( CarbonData, WoodSupplier )
|
||||
|
||||
__all__ = [
|
||||
"metal_factors",
|
||||
"wood_factors",
|
||||
"CarbonData",
|
||||
"WoodSupplier"
|
||||
]
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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
|
||||
@@ -1,3 +1,8 @@
|
||||
from .material_class import ( MetalClass, WoodClass )
|
||||
from .material_data import MaterialData
|
||||
|
||||
__all__ = ["MaterialData"]
|
||||
__all__ = [
|
||||
"MaterialData",
|
||||
"MetalClass",
|
||||
"WoodClass"
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user