displayvalue updates
This commit is contained in:
-27
@@ -1,27 +0,0 @@
|
||||
"""Helper module for a simple speckle object tree flattening."""
|
||||
|
||||
from collections.abc import Iterable
|
||||
|
||||
from specklepy.objects import Base
|
||||
|
||||
|
||||
def flatten_base(base: Base) -> Iterable[Base]:
|
||||
"""Flatten a base object into an iterable of bases.
|
||||
|
||||
This function recursively traverses the `elements` or `@elements` attribute of the
|
||||
base object, yielding each nested base object.
|
||||
|
||||
Args:
|
||||
base (Base): The base object to flatten.
|
||||
|
||||
Yields:
|
||||
Base: Each nested base object in the hierarchy.
|
||||
"""
|
||||
# Attempt to get the elements attribute, fallback to @elements if necessary
|
||||
elements = getattr(base, "elements", getattr(base, "@elements", None))
|
||||
|
||||
if elements is not None:
|
||||
for element in elements:
|
||||
yield from flatten_base(element)
|
||||
|
||||
yield base
|
||||
@@ -162,26 +162,37 @@ def automate_function(
|
||||
total += 1
|
||||
|
||||
# B2: Instance objects nested inside displayValue
|
||||
# Each becomes its own IFC element (same class as parent)
|
||||
# Use the parent object's name — the InstanceProxy has no meaningful name
|
||||
# All instances are parts of the SAME element (e.g. window frame + glass + sill)
|
||||
# Merge all into a single IFC element with combined geometry
|
||||
nested_instances = get_display_instances(obj)
|
||||
for inst in nested_instances:
|
||||
inst_rep, inst_placement = instance_to_ifc(
|
||||
ifc, body_context, inst, definition_map, scale=scale, material_manager=material_manager
|
||||
)
|
||||
if not inst_rep:
|
||||
no_geometry += 1
|
||||
continue
|
||||
inst_element = _create_element(
|
||||
ifc, ifc_class, name, inst_rep, inst_placement, storey,
|
||||
storey_manager=storey_manager,
|
||||
tag=get_element_tag(obj), guid=None,
|
||||
object_type=getattr(obj, "type", None),
|
||||
)
|
||||
write_properties(ifc, inst_element, obj, ifc_class=ifc_class, category_name=category_name)
|
||||
type_manager.assign(inst_element, obj, ifc_class)
|
||||
instance_count += 1
|
||||
total += 1
|
||||
if nested_instances:
|
||||
mapped_items = []
|
||||
inst_placement = None
|
||||
for inst in nested_instances:
|
||||
inst_rep, inst_pl = instance_to_ifc(
|
||||
ifc, body_context, inst, definition_map, scale=scale, material_manager=material_manager
|
||||
)
|
||||
if inst_rep:
|
||||
mapped_items.extend(inst_rep.Items)
|
||||
if inst_placement is None:
|
||||
inst_placement = inst_pl
|
||||
if mapped_items:
|
||||
combined_rep = ifc.createIfcShapeRepresentation(
|
||||
ContextOfItems=body_context,
|
||||
RepresentationIdentifier="Body",
|
||||
RepresentationType="MappedRepresentation",
|
||||
Items=mapped_items,
|
||||
)
|
||||
element = _create_element(
|
||||
ifc, ifc_class, name, combined_rep, inst_placement, storey,
|
||||
storey_manager=storey_manager,
|
||||
tag=get_element_tag(obj), guid=get_ifc_guid(obj),
|
||||
object_type=getattr(obj, "type", None),
|
||||
)
|
||||
write_properties(ifc, element, obj, ifc_class=ifc_class, category_name=category_name)
|
||||
type_manager.assign(element, obj, ifc_class)
|
||||
instance_count += 1
|
||||
total += 1
|
||||
|
||||
# Track if neither path produced geometry
|
||||
if not rep and not nested_instances:
|
||||
@@ -205,12 +216,12 @@ def automate_function(
|
||||
|
||||
ifc.write(ifc_filename)
|
||||
print(f"\n💾 IFC file written: {ifc_filename}")
|
||||
try:
|
||||
automate_context.mark_run_success("Success! You can download the IF file below.")
|
||||
automate_context.store_file_result(f"./{ifc_filename}")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Could not upload file result (network issue?): {e}")
|
||||
automate_context.mark_run_failed(f"Something went wrong when storing file result. Exception detail: {e}")
|
||||
# try:
|
||||
# automate_context.mark_run_success("Success! You can download the IF file below.")
|
||||
# automate_context.store_file_result(f"./{ifc_filename}")
|
||||
# except Exception as e:
|
||||
# print(f" ⚠️ Could not upload file result (network issue?): {e}")
|
||||
# automate_context.mark_run_failed(f"Something went wrong when storing file result. Exception detail: {e}")
|
||||
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f" Export complete!")
|
||||
|
||||
+8
-61
@@ -4,9 +4,8 @@
|
||||
#
|
||||
# Strategy (priority order):
|
||||
# 1. builtInCategory (OST_ enum from properties.builtInCategory) — most reliable
|
||||
# 2. speckle_type prefix match — for typed Speckle objects
|
||||
# 3. category_name string (traversal context) — display name fallback
|
||||
# 4. IfcBuildingElementProxy — last resort
|
||||
# 2. category_name string (traversal context) — display name fallback
|
||||
# 3. IfcBuildingElementProxy — last resort
|
||||
#
|
||||
# builtInCategory values: https://www.revitapidocs.com/2019/ba1c5b30-242f-5fdc-8ea9-ec3b61e6e722.htm
|
||||
# =============================================================================
|
||||
@@ -119,44 +118,7 @@ BUILTIN_CATEGORY_MAP: dict[str, str] = {
|
||||
}
|
||||
|
||||
|
||||
# --- speckle_type → IFC class (secondary lookup) ---
|
||||
SPECKLE_TYPE_MAP: dict[str, str] = {
|
||||
"Objects.BuiltElements.Wall": "IfcWall",
|
||||
"Objects.BuiltElements.Floor": "IfcSlab",
|
||||
"Objects.BuiltElements.Roof": "IfcRoof",
|
||||
"Objects.BuiltElements.Column": "IfcColumn",
|
||||
"Objects.BuiltElements.Beam": "IfcBeam",
|
||||
"Objects.BuiltElements.Brace": "IfcMember",
|
||||
"Objects.BuiltElements.Duct": "IfcDuctSegment",
|
||||
"Objects.BuiltElements.Pipe": "IfcPipeSegment",
|
||||
"Objects.BuiltElements.Wire": "IfcCableCarrierSegment",
|
||||
"Objects.BuiltElements.Opening": "IfcOpeningElement",
|
||||
"Objects.BuiltElements.Room": "IfcSpace",
|
||||
"Objects.BuiltElements.Ceiling": "IfcCovering",
|
||||
"Objects.BuiltElements.Stair": "IfcStair",
|
||||
"Objects.BuiltElements.Ramp": "IfcRamp",
|
||||
"Objects.BuiltElements.Foundation": "IfcFooting",
|
||||
"Objects.BuiltElements.Grid": "IfcGrid",
|
||||
"Objects.BuiltElements.Level": "IfcBuildingStorey",
|
||||
"Objects.BuiltElements.Revit.RevitWall": "IfcWall",
|
||||
"Objects.BuiltElements.Revit.RevitFloor": "IfcSlab",
|
||||
"Objects.BuiltElements.Revit.RevitRoof": "IfcRoof",
|
||||
"Objects.BuiltElements.Revit.RevitColumn": "IfcColumn",
|
||||
"Objects.BuiltElements.Revit.RevitBeam": "IfcBeam",
|
||||
"Objects.BuiltElements.Revit.RevitBrace": "IfcMember",
|
||||
"Objects.BuiltElements.Revit.RevitDuct": "IfcDuctSegment",
|
||||
"Objects.BuiltElements.Revit.RevitPipe": "IfcPipeSegment",
|
||||
"Objects.BuiltElements.Revit.RevitRoom": "IfcSpace",
|
||||
"Objects.BuiltElements.Revit.RevitStair": "IfcStair",
|
||||
"Objects.BuiltElements.Revit.RevitRailing": "IfcRailing",
|
||||
"Objects.BuiltElements.Revit.RevitCeiling": "IfcCovering",
|
||||
"Objects.BuiltElements.Revit.RevitTopography": "IfcGeographicElement",
|
||||
"Objects.BuiltElements.Revit.RevitElementType": "IfcBuildingElementProxy",
|
||||
"Objects.Geometry.Mesh": "IfcBuildingElementProxy",
|
||||
"Objects.Geometry.Brep": "IfcBuildingElementProxy",
|
||||
}
|
||||
|
||||
# --- Display category name → IFC class (tertiary fallback) ---
|
||||
# --- Display category name → IFC class (secondary fallback) ---
|
||||
CATEGORY_MAP: dict[str, str] = {
|
||||
"Walls": "IfcWall",
|
||||
"Floors": "IfcSlab",
|
||||
@@ -235,11 +197,6 @@ def _get_builtin_category(obj) -> str | None:
|
||||
return result
|
||||
|
||||
|
||||
# Pre-computed: sorted prefixes longest-first for early exit on prefix match
|
||||
_SPECKLE_PREFIXES: list[tuple[str, str]] = sorted(
|
||||
SPECKLE_TYPE_MAP.items(), key=lambda x: len(x[0]), reverse=True
|
||||
)
|
||||
|
||||
# Pre-computed lowercase category map for substring matching
|
||||
_CATEGORY_MAP_LOWER: list[tuple[str, str]] = [
|
||||
(k.lower(), v) for k, v in CATEGORY_MAP.items()
|
||||
@@ -255,10 +212,9 @@ def classify(obj, category_name: str = "") -> str:
|
||||
|
||||
Priority:
|
||||
1. properties.builtInCategory (OST_ enum) — definitive Revit classification
|
||||
2. speckle_type prefix match
|
||||
3. category_name from traversal context (display string)
|
||||
4. obj.category field
|
||||
5. IfcBuildingElementProxy fallback
|
||||
2. category_name from traversal context (display string)
|
||||
3. obj.category field
|
||||
4. IfcBuildingElementProxy fallback
|
||||
"""
|
||||
cache_key = (id(obj), category_name)
|
||||
if cache_key in _classify_cache:
|
||||
@@ -275,16 +231,7 @@ def _classify_impl(obj, category_name: str) -> str:
|
||||
if bic and bic in BUILTIN_CATEGORY_MAP:
|
||||
return BUILTIN_CATEGORY_MAP[bic]
|
||||
|
||||
# 2. speckle_type — exact match first, then longest-prefix match
|
||||
speckle_type = getattr(obj, "speckle_type", "") or ""
|
||||
if speckle_type:
|
||||
if speckle_type in SPECKLE_TYPE_MAP:
|
||||
return SPECKLE_TYPE_MAP[speckle_type]
|
||||
for prefix, ifc_class in _SPECKLE_PREFIXES:
|
||||
if speckle_type.startswith(prefix):
|
||||
return ifc_class
|
||||
|
||||
# 3. category_name from traversal context — exact match first
|
||||
# 2. category_name from traversal context — exact match first
|
||||
if category_name:
|
||||
if category_name in CATEGORY_MAP:
|
||||
return CATEGORY_MAP[category_name]
|
||||
@@ -293,7 +240,7 @@ def _classify_impl(obj, category_name: str) -> str:
|
||||
if key_lower in cat_lower:
|
||||
return ifc_class
|
||||
|
||||
# 4. obj.category field
|
||||
# 3. obj.category field
|
||||
obj_category = getattr(obj, "category", None)
|
||||
if obj_category and isinstance(obj_category, str):
|
||||
if obj_category in CATEGORY_MAP:
|
||||
|
||||
+102
-3
@@ -644,7 +644,7 @@ def write_material_quantities(ifc, element, obj: Base):
|
||||
Source: properties."Material Quantities".<MaterialName>.{area, volume, density,
|
||||
materialName, materialClass, materialCategory}
|
||||
|
||||
Each material produces one IfcElementQuantity named "Qto_<MaterialName>" with:
|
||||
Each material produces one IfcElementQuantity named "Qto_<MaterialName>BaseQuantities" with:
|
||||
- GrossArea (IfcQuantityArea)
|
||||
- GrossVolume (IfcQuantityVolume)
|
||||
- Density (IfcPropertySingleValue — no standard IFC quantity type)
|
||||
@@ -711,7 +711,7 @@ def write_material_quantities(ifc, element, obj: Base):
|
||||
continue
|
||||
|
||||
# Create IfcElementQuantity and link via IfcRelDefinesByProperties
|
||||
qto_name = f"Qto_{mat_name}"
|
||||
qto_name = f"Qto_{mat_name}BaseQuantities"
|
||||
try:
|
||||
qto = ifcopenshell.api.run(
|
||||
"pset.add_qto", ifc,
|
||||
@@ -723,6 +723,103 @@ def write_material_quantities(ifc, element, obj: Base):
|
||||
print(f" ⚠️ {qto_name}: {e}")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Qto_<EntityType>BaseQuantities — standard element-level quantities
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# IFC entity → Qto name (only entities with standard Qto sets)
|
||||
_ENTITY_QTO_NAME: dict[str, str] = {
|
||||
"IfcWall": "Qto_WallBaseQuantities",
|
||||
"IfcWallStandardCase": "Qto_WallBaseQuantities",
|
||||
"IfcSlab": "Qto_SlabBaseQuantities",
|
||||
"IfcColumn": "Qto_ColumnBaseQuantities",
|
||||
"IfcBeam": "Qto_BeamBaseQuantities",
|
||||
"IfcDoor": "Qto_DoorBaseQuantities",
|
||||
"IfcWindow": "Qto_WindowBaseQuantities",
|
||||
"IfcRoof": "Qto_RoofBaseQuantities",
|
||||
"IfcCovering": "Qto_CoveringBaseQuantities",
|
||||
"IfcRailing": "Qto_RailingBaseQuantities",
|
||||
"IfcStair": "Qto_StairBaseQuantities",
|
||||
"IfcRamp": "Qto_RampBaseQuantities",
|
||||
"IfcMember": "Qto_MemberBaseQuantities",
|
||||
"IfcFooting": "Qto_FootingBaseQuantities",
|
||||
"IfcCurtainWall": "Qto_CurtainWallBaseQuantities",
|
||||
"IfcBuildingElementProxy": "Qto_BuildingElementProxyBaseQuantities",
|
||||
}
|
||||
|
||||
# IFC quantity name → (IFC entity type, value attribute, [Revit param fallbacks])
|
||||
# First matching Revit param wins for each quantity name.
|
||||
_ELEMENT_QUANTITY_DEFS: list[tuple[str, str, str, list[str]]] = [
|
||||
("GrossArea", "IfcQuantityArea", "AreaValue", ["HOST_AREA_COMPUTED"]),
|
||||
("GrossVolume", "IfcQuantityVolume", "VolumeValue", ["HOST_VOLUME_COMPUTED"]),
|
||||
("Length", "IfcQuantityLength", "LengthValue", [
|
||||
"CURVE_ELEM_LENGTH", "INSTANCE_LENGTH_PARAM",
|
||||
]),
|
||||
("Height", "IfcQuantityLength", "LengthValue", [
|
||||
"WALL_USER_HEIGHT_PARAM", "FAMILY_HEIGHT_PARAM",
|
||||
"INSTANCE_HEAD_HEIGHT_PARAM",
|
||||
]),
|
||||
("Width", "IfcQuantityLength", "LengthValue", [
|
||||
"INSTANCE_WIDTH_PARAM", "FURNITURE_WIDTH",
|
||||
"FLOOR_ATTR_THICKNESS_PARAM",
|
||||
]),
|
||||
("Perimeter", "IfcQuantityLength", "LengthValue", [
|
||||
"HOST_PERIMETER_COMPUTED",
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
def write_element_quantities(ifc, element, obj: Base, ifc_class: str = ""):
|
||||
"""
|
||||
Write Qto_<EntityType>BaseQuantities from Revit computed instance parameters.
|
||||
|
||||
Reads HOST_AREA_COMPUTED, HOST_VOLUME_COMPUTED, CURVE_ELEM_LENGTH,
|
||||
FURNITURE_WIDTH, FAMILY_HEIGHT_PARAM, etc.
|
||||
IfcSpace is handled separately in _write_space_properties.
|
||||
"""
|
||||
if ifc_class == "IfcSpace":
|
||||
return # Already handled by Qto_SpaceBaseQuantities
|
||||
|
||||
qto_name = _ENTITY_QTO_NAME.get(ifc_class)
|
||||
if not qto_name:
|
||||
return
|
||||
|
||||
props = _get_props_dict(obj)
|
||||
params = _safe_get(props, "Parameters", {})
|
||||
inst_params = _safe_get(params, "Instance Parameters", {})
|
||||
if not inst_params:
|
||||
return
|
||||
|
||||
quantities = []
|
||||
|
||||
for qty_name, ifc_entity, value_attr, revit_params in _ELEMENT_QUANTITY_DEFS:
|
||||
val = None
|
||||
for internal_name in revit_params:
|
||||
val = _param_value(inst_params, internal_name)
|
||||
if val is not None:
|
||||
break
|
||||
if val is None:
|
||||
continue
|
||||
try:
|
||||
q = ifc.create_entity(ifc_entity, Name=qty_name, **{value_attr: float(val)})
|
||||
quantities.append(q)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not quantities:
|
||||
return
|
||||
|
||||
try:
|
||||
qto = ifcopenshell.api.run(
|
||||
"pset.add_qto", ifc,
|
||||
product=element,
|
||||
name=qto_name,
|
||||
)
|
||||
qto.Quantities = quantities
|
||||
except Exception as e:
|
||||
print(f" ⚠️ {qto_name}: {e}")
|
||||
|
||||
|
||||
def write_properties(ifc, element, obj: Base, ifc_class: str = "", category_name: str = ""):
|
||||
"""
|
||||
Write all property sets for an IFC element, matching Revit native IFC export structure:
|
||||
@@ -731,10 +828,12 @@ def write_properties(ifc, element, obj: Base, ifc_class: str = "", category_name
|
||||
3. RVT_TypeParameters — all remaining Revit type parameters
|
||||
4. RVT_InstanceParameters — all remaining Revit instance parameters
|
||||
5. RVT_Identity — family, type, elementId, builtInCategory
|
||||
6. Qto_<MaterialName> — material quantities (area, volume, density)
|
||||
6. Qto_<EntityType>BaseQuantities — element-level quantities (area, volume, length)
|
||||
7. Qto_<MaterialName>BaseQuantities — material quantities (area, volume, density)
|
||||
"""
|
||||
write_common_pset(ifc, element, obj, ifc_class, category_name)
|
||||
write_revit_params(ifc, element, obj)
|
||||
write_element_quantities(ifc, element, obj, ifc_class)
|
||||
write_material_quantities(ifc, element, obj)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user