diff --git a/main.py b/main.py index fb0c5c2..f5d7d5d 100644 --- a/main.py +++ b/main.py @@ -156,7 +156,7 @@ class RevitCarbonAnalyzer: results = { "processed_elements": [], "skipped_elements": [], - "warning_elements": [], # For invalid elements + "warning_elements": [], "errors": [], "total_carbon": 0.0, "missing_factors": {"timber": [], "steel": [], "concrete": []}, @@ -243,7 +243,19 @@ class RevitCarbonAnalyzer: # Calculate carbon try: - carbon_results = self.carbon_calculator.calculate_carbon(processed_element) + carbon_results, material_errors = self.carbon_calculator.calculate_carbon( + processed_element + ) + + if not carbon_results: + error_details = "; ".join( + [f"{e['material']}: {e['error']}" for e in material_errors] + ) + return { + "id": element_id, + "status": "error", + "reason": f"No carbon could be calculated: {error_details}", + } # Initialize Embodied Carbon Calculation dictionary embodied_carbon_data = {} @@ -265,13 +277,13 @@ class RevitCarbonAnalyzer: "value": result.database, "units": None, }, - "ecf": { - "name": "ecf", + "embodiedCarbonFactor": { + "name": "embodiedCarbonFactor", "value": result.factor, "units": "kgCO₂e/m³", }, - "embodied carbon": { - "name": "embodied carbon", + "embodiedCarbon": { + "name": "embodiedCarbon", "value": result.total_carbon, "units": "kgCO₂e", }, @@ -279,8 +291,8 @@ class RevitCarbonAnalyzer: elif result.category == "Concrete": # For concrete (include both concrete and reinforcement) material_data = { - "volume": { - "name": "volume", + "concreteVolume": { + "name": "concreteVolume", "value": result.concrete_volume, "units": "m³", }, @@ -289,38 +301,38 @@ class RevitCarbonAnalyzer: "value": result.database, "units": None, }, - "ecf": { - "name": "ecf", + "concreteEmbodiedCarbonFactor": { + "name": "concreteEmbodiedCarbonFactor", "value": result.factor, "units": "kgCO₂e/m³", }, - "concrete carbon": { - "name": "concrete carbon", + "concreteEmbodiedCarbon": { + "name": "concreteEmbodiedCarbon", "value": result.concrete_carbon, "units": "kgCO₂e", }, - "reinforcement mass": { - "name": "reinforcement mass", + "reinforcementMass": { + "name": "reinforcementMass", "value": result.reinforcement_mass, "units": "kg", }, - "reinforcement rate": { - "name": "reinforcement rate", + "reinforcementRate": { + "name": "reinforcementRate", "value": result.reinforcement_rate, "units": "kg/m³", }, - "reinforcement ecf": { - "name": "reinforcement ecf", + "reinforcementEmbodiedCarbonFactor": { + "name": "reinforcementEmbodiedCarbonFactor", "value": result.reinforcement_factor, "units": "kgCO₂e/kg", }, - "reinforcement carbon": { - "name": "reinforcement carbon", + "reinforcementEmbodiedCarbon": { + "name": "reinforcementEmbodiedCarbon", "value": result.reinforcement_carbon, "units": "kgCO₂e", }, - "embodied carbon": { - "name": "embodied carbon", + "embodiedCarbon": { + "name": "embodiedCarbon", "value": result.total_carbon, "units": "kgCO₂e", }, @@ -338,13 +350,13 @@ class RevitCarbonAnalyzer: "value": result.database, "units": None, }, - "ecf": { - "name": "ecf", + "embodiedCarbonFactor": { + "name": "embodiedCarbonFactor", "value": result.factor, "units": "kgCO₂e/kg", }, - "embodied carbon": { - "name": "embodied carbon", + "embodiedCarbon": { + "name": "embodiedCarbon", "value": result.total_carbon, "units": "kgCO₂e", }, @@ -357,9 +369,9 @@ class RevitCarbonAnalyzer: if hasattr(element, "properties"): element.properties["Embodied Carbon Calculation"] = embodied_carbon_data - return { + element_result = { "id": element_id, - "status": "processed", + "status": "processed" if not material_errors else "warning", "level": processed_element.level, "category": processed_element.category, "materials": [ @@ -373,11 +385,21 @@ class RevitCarbonAnalyzer: "carbon_results": carbon_results, "total_carbon": sum(r.total_carbon for r in carbon_results.values()), } + + # If there were material errors, include them in the result + if material_errors: + element_result["material_errors"] = material_errors + element_result[ + "reason" + ] = f"Issues with {len(material_errors)} materials" + + return element_result + except Exception as e: return { "id": element_id, "status": "error", - "error": f"Carbon calculation failed: {str(e)}", + "reason": f"Carbon calculation failed: {str(e)}", } @staticmethod @@ -485,8 +507,8 @@ def automate_function( element_id, key, "{:0.2f} {}".format( - value["embodied carbon"]["value"], - value["embodied carbon"]["units"], + value["embodiedCarbon"]["value"], + value["embodiedCarbon"]["units"], ), ] ) @@ -550,9 +572,9 @@ def automate_function( ) # Upload mutated model - # automate_context.create_new_version_in_project( - # model_root, f"{commit_root.branchName}_embodied_carbon" - # ) + automate_context.create_new_version_in_project( + model_root, f"{commit_root.branchName}_embodied_carbon" + ) # Mark success with detailed message automate_context.mark_run_success(success_message) @@ -574,10 +596,21 @@ def _process_automation_results( """Process results and attach them to the automation context.""" # Process each category and attach to objects - # Successes + # Successes with gradient metadata if results["processed_elements"]: + # Create a dictionary mapping element IDs to their total carbon values + embodied_carbon_values = {} + + # Extract the total carbon for each element + for element in results["processed_elements"]: + element_id = element["id"] + # The total carbon is already calculated and stored in each element result + total_carbon = element["total_carbon"] + embodied_carbon_values[element_id] = {"gradientValue": total_carbon} + automate_context.attach_success_to_objects( category="Carbon Analysis", + metadata={"gradient": True, "gradientValues": embodied_carbon_values}, object_ids=[e["id"] for e in results["processed_elements"]], message="Carbon calculations completed successfully for these elements!", ) diff --git a/src/domain/carbon/material_alias_service.py b/src/domain/carbon/material_alias_service.py index 033a8bd..3cfa99e 100644 --- a/src/domain/carbon/material_alias_service.py +++ b/src/domain/carbon/material_alias_service.py @@ -12,9 +12,27 @@ class MaterialAliasService: "glue laminated timber", "glued laminated timber", "glulam beam", + "GL36h", + "GL36h(1)", + "GL24h", + "GL28h", + "GL30h", + "GL32h", + "GL36c", + "GL36c(1)", + "GL24c", + "GL28c", + "GL30c", + "GL32c", + "softwood", ], "lvl": ["laminated veneer lumber"], - "softwood lumber": ["dimensional lumber", "sawn lumber", "softwood"], + "softwood lumber": [ + "dimensional lumber", + "sawn lumber", + "softwood", + "FE_Wood - Dimensional Lumber", + ], "softwood plywood": ["plywood", "softwood ply"], "oriented strand board": ["osb", "osb board"], "glt/nlt/dlt": [ @@ -41,7 +59,7 @@ class MaterialAliasService: "rebar": ["reinforcing bar", "reinforcement"], "owsj": ["open web steel joist", "steel joist"], "fasteners": ["bolts", "screws", "nails", "rivets"], - "metal deck": ["deck", "decking"], + "metal deck": ["deck", "decking", "metal - decking"], } self._concrete_aliases = { diff --git a/src/services/carbon_calculator.py b/src/services/carbon_calculator.py index cb0db15..d3ad384 100644 --- a/src/services/carbon_calculator.py +++ b/src/services/carbon_calculator.py @@ -45,9 +45,12 @@ class CarbonCalculator: self._missing_steel_factors = set() self._missing_concrete_factors = set() - def calculate_carbon(self, element: BuildingElement) -> Dict[str, CarbonResult]: - """Calculate carbon emissions for an element's materials.""" + def calculate_carbon( + self, element: BuildingElement + ) -> tuple[Dict[str, CarbonResult], List[Dict[str, str]]]: + """Calculate carbon emissions for an element's materials and return results and errors.""" results = {} + errors = [] for material in element.materials: try: @@ -77,11 +80,13 @@ class CarbonCalculator: ) self._missing_concrete_factors.add(f"{strength}_{element_type}") - print( - f"Error calculating carbon for {material.properties.name}: {str(e)}" + # Store error with material name instead of just printing + error_msg = ( + f"No emission factor found for {material.properties.name}: {str(e)}" ) + errors.append({"material": material.properties.name, "error": str(e)}) - return results + return results, errors def _calculate_material_carbon( self, material: Material, element_category: Optional[ElementCategory] = None @@ -102,8 +107,6 @@ class CarbonCalculator: def _calculate_metal_carbon(self, material: Material) -> CarbonResult: """Calculate carbon emissions for metal.""" - if not material.mass: - raise ValueError("Mass required for metal carbon calculation") # Get factor from cache or registry if material.grade not in self._steel_factors_cache: diff --git a/src/services/element_processor.py b/src/services/element_processor.py index 0d1b377..f4ca8d3 100644 --- a/src/services/element_processor.py +++ b/src/services/element_processor.py @@ -14,7 +14,7 @@ class ElementProcessor: "Objects.Geometry.Circle", ] - SKIP_FAMILIES = ["Grid", "JS_SF_Centerline Only"] + SKIP_FAMILIES = ["Grid", "JS_SF_Centerline Only", "none"] def __init__(self, material_processor: MaterialProcessor, logger: Logging): self.material_processor = material_processor @@ -108,7 +108,7 @@ class ElementProcessor: properties = getattr(element, "properties") material_quantities = properties["Material Quantities"] - for material_data in material_quantities.values(): # Added .values() + for material_data in material_quantities.values(): try: material = self.material_processor.process_material(material_data) materials.append(material) diff --git a/src/services/material_processor.py b/src/services/material_processor.py index f47e39f..c1935f4 100644 --- a/src/services/material_processor.py +++ b/src/services/material_processor.py @@ -35,9 +35,13 @@ class MaterialProcessor: """Process materials with structural assets.""" if "concrete" in props.name.lower(): return self._process_concrete(props) - elif "steel" in props.name.lower(): + elif "steel" in props.name.lower() or "metal" in props.name.lower(): return self._process_steel(props) - elif "clt" in props.name.lower() or "timber" in props.name.lower(): + elif ( + "clt" in props.name.lower() + or "timber" in props.name.lower() + or "glulam" in props.name.lower() + ): return Material(type=MaterialType.WOOD, properties=props) else: raise ValueError(f"Unknown high-grade material: {props.name}") @@ -60,7 +64,7 @@ class MaterialProcessor: mass=mass, grade="default_steel", ) - elif "clt" in name or "timber" in name: + elif "clt" in name or "timber" in name or "wood" in name: return Material(type=MaterialType.WOOD, properties=props) else: raise ValueError(f"Unknown material type: {props.name}")