444 lines
19 KiB
Python
444 lines
19 KiB
Python
import bpy
|
|
import ifcopenshell
|
|
from collections import defaultdict
|
|
|
|
try:
|
|
# Try new Bonsai import structure
|
|
import bonsai.tool as tool
|
|
except ImportError:
|
|
try:
|
|
# Fallback to BlenderBIM import structure
|
|
import blenderbim.tool as tool
|
|
except ImportError:
|
|
# Manual fallback - access IFC file directly
|
|
tool = None
|
|
print("Warning: Neither bonsai.tool nor blenderbim.tool found. Using direct access method.")
|
|
|
|
# Available IFC representation types
|
|
AVAILABLE_REPRESENTATION_TYPES = [
|
|
'Point', 'PointCloud', 'Curve', 'Curve2D', 'Curve3D', 'Surface', 'Surface2D', 'Surface3D',
|
|
'SectionedSurfaces', 'FillArea', 'Text', 'AdvancedSurface', 'GeometricSet', 'GeometricCurveSet',
|
|
'Annotation2D', 'SurfaceModel', 'Tessellation', 'Segment', 'SolidModel', 'SweptSolid',
|
|
'AdvancedSweptSolid', 'Brep', 'AdvancedBrep', 'CSG', 'Clipping', 'BoundingBox',
|
|
'SectionedSpine', 'LightSource', 'MappedRepresentation'
|
|
]
|
|
|
|
def get_ifc_file():
|
|
"""
|
|
Get the IFC file using available methods.
|
|
"""
|
|
if tool:
|
|
try:
|
|
return tool.Ifc.get()
|
|
except:
|
|
pass
|
|
|
|
# Fallback method - check scene properties
|
|
if hasattr(bpy.context.scene, 'BIMProperties') and bpy.context.scene.BIMProperties.ifc_file:
|
|
return bpy.context.scene.BIMProperties.ifc_file
|
|
|
|
# Check if there's an active IFC file in the scene
|
|
for obj in bpy.context.scene.objects:
|
|
if hasattr(obj, 'BIMObjectProperties') and obj.BIMObjectProperties.ifc_definition_id:
|
|
# Try to find the IFC file through object relationships
|
|
try:
|
|
# This is a more direct approach
|
|
if 'ifc_file' in bpy.context.scene:
|
|
return bpy.context.scene['ifc_file']
|
|
except:
|
|
pass
|
|
break
|
|
|
|
return None
|
|
|
|
def select_objects_by_representation(representation_types, include_mapped=True, search_mapped_only=False):
|
|
"""
|
|
Select all objects in the Blender scene that have the specified IFC representation type(s).
|
|
|
|
Args:
|
|
representation_types (list or str): Single representation type or list of types to search for
|
|
include_mapped (bool): Whether to recursively search in mapped representations
|
|
search_mapped_only (bool): If True, only select objects that have the target type within mapped representations
|
|
"""
|
|
# Ensure representation_types is a list
|
|
if isinstance(representation_types, str):
|
|
representation_types = [representation_types]
|
|
|
|
# Validate representation types
|
|
invalid_types = [rt for rt in representation_types if rt not in AVAILABLE_REPRESENTATION_TYPES]
|
|
if invalid_types:
|
|
print(f"Warning: Invalid representation type(s): {invalid_types}")
|
|
print(f"Available types: {AVAILABLE_REPRESENTATION_TYPES}")
|
|
|
|
# Filter to only valid types
|
|
valid_types = [rt for rt in representation_types if rt in AVAILABLE_REPRESENTATION_TYPES]
|
|
if not valid_types:
|
|
print("No valid representation types provided.")
|
|
return
|
|
|
|
print(f"Searching for representation types: {valid_types}")
|
|
if include_mapped:
|
|
print("Including mapped representations in search.")
|
|
if search_mapped_only:
|
|
print("Only selecting objects with target types found within mapped representations.")
|
|
|
|
# Clear current selection
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
|
|
# Get the IFC file from the current project
|
|
ifc_file = get_ifc_file()
|
|
if not ifc_file:
|
|
print("No IFC file found in the current project")
|
|
print("Make sure you have an IFC file loaded in Bonsai/BlenderBIM")
|
|
return
|
|
|
|
selected_count = 0
|
|
representation_counts = {rep_type: 0 for rep_type in valid_types}
|
|
mapped_counts = {rep_type: 0 for rep_type in valid_types}
|
|
|
|
# Iterate through all objects in the scene
|
|
for obj in bpy.context.scene.objects:
|
|
# Check if object has IFC data (try different property structures)
|
|
ifc_id = None
|
|
if hasattr(obj, 'BIMObjectProperties') and obj.BIMObjectProperties.ifc_definition_id:
|
|
ifc_id = obj.BIMObjectProperties.ifc_definition_id
|
|
elif hasattr(obj, 'data') and hasattr(obj.data, 'BIMObjectProperties') and obj.data.BIMObjectProperties.ifc_definition_id:
|
|
ifc_id = obj.data.BIMObjectProperties.ifc_definition_id
|
|
|
|
if not ifc_id:
|
|
continue
|
|
|
|
# Get the IFC element
|
|
try:
|
|
element = ifc_file.by_id(ifc_id)
|
|
except:
|
|
continue
|
|
|
|
# Check if element has representation
|
|
if not hasattr(element, 'Representation') or not element.Representation:
|
|
continue
|
|
|
|
found_representation = None
|
|
is_from_mapped = False
|
|
|
|
# Check all representations of the element
|
|
for representation in element.Representation.Representations:
|
|
# First check direct representation type
|
|
if not search_mapped_only and representation.RepresentationType in valid_types:
|
|
found_representation = representation.RepresentationType
|
|
is_from_mapped = False
|
|
break
|
|
|
|
# Then check mapped representations if requested
|
|
if include_mapped:
|
|
found_type = check_representation_recursive(representation, valid_types)
|
|
if found_type:
|
|
found_representation = found_type
|
|
# Check if this was found inside a mapped representation
|
|
is_from_mapped = representation.RepresentationType == 'MappedRepresentation'
|
|
break
|
|
|
|
# Apply search_mapped_only filter
|
|
if search_mapped_only and not is_from_mapped:
|
|
continue
|
|
|
|
if found_representation:
|
|
# Select the object
|
|
obj.select_set(True)
|
|
selected_count += 1
|
|
representation_counts[found_representation] += 1
|
|
if is_from_mapped:
|
|
mapped_counts[found_representation] += 1
|
|
|
|
status = " (in MappedRepresentation)" if is_from_mapped else ""
|
|
print(f"Selected: {obj.name} (IFC ID: {ifc_id}) - Representation: {found_representation}{status}")
|
|
|
|
# Set active object to the last selected object if any
|
|
if selected_count > 0:
|
|
bpy.context.view_layer.objects.active = obj
|
|
print(f"\n=== SELECTION SUMMARY ===")
|
|
print(f"Total objects selected: {selected_count}")
|
|
for rep_type, count in representation_counts.items():
|
|
if count > 0:
|
|
mapped_count = mapped_counts[rep_type]
|
|
direct_count = count - mapped_count
|
|
print(f" {rep_type}: {count} objects (direct: {direct_count}, mapped: {mapped_count})")
|
|
else:
|
|
print(f"No objects with representation types {valid_types} found")
|
|
|
|
def check_representation_recursive(representation, target_types, visited=None):
|
|
"""
|
|
Recursively check if a representation or its mapped contents include any target types.
|
|
|
|
Args:
|
|
representation: IFC representation to check
|
|
target_types (list): List of representation types to match
|
|
visited (set): Prevents circular references
|
|
|
|
Returns:
|
|
str or None: Matching representation type or None
|
|
"""
|
|
if visited is None:
|
|
visited = set()
|
|
|
|
# Prevent circular references
|
|
if representation.id() in visited:
|
|
return None
|
|
visited.add(representation.id())
|
|
|
|
# Direct match
|
|
if representation.RepresentationType in target_types:
|
|
return representation.RepresentationType
|
|
|
|
# Dive into Items for MappedRepresentation or others
|
|
for item in getattr(representation, "Items", []):
|
|
# Case 1: Item is a MappedItem -> check its MappingSource recursively
|
|
if hasattr(item, "MappingSource") and item.MappingSource:
|
|
nested_rep = item.MappingSource.MappedRepresentation
|
|
found_type = check_representation_recursive(nested_rep, target_types, visited)
|
|
if found_type:
|
|
return found_type
|
|
|
|
# Case 2: Item is a representation (rare, but possible with nested IfcShapeRepresentation)
|
|
if hasattr(item, "RepresentationType"):
|
|
if item.RepresentationType in target_types:
|
|
return item.RepresentationType
|
|
|
|
return None
|
|
|
|
|
|
def analyze_model_representations(include_mapped_analysis=True):
|
|
"""
|
|
Analyze and report what representation types exist in the current model.
|
|
|
|
Args:
|
|
include_mapped_analysis (bool): Whether to analyze representations within mapped items
|
|
"""
|
|
print("\n" + "="*60)
|
|
print("ANALYZING REPRESENTATION TYPES IN MODEL")
|
|
print("="*60)
|
|
|
|
# Get the IFC file
|
|
ifc_file = get_ifc_file()
|
|
if not ifc_file:
|
|
print("No IFC file found")
|
|
return
|
|
|
|
representation_counts = defaultdict(int)
|
|
mapped_representation_counts = defaultdict(int)
|
|
objects_with_representations = 0
|
|
objects_without_representations = 0
|
|
|
|
# Analyze all objects
|
|
for obj in bpy.context.scene.objects:
|
|
if not hasattr(obj, 'BIMObjectProperties') or not obj.BIMObjectProperties.ifc_definition_id:
|
|
continue
|
|
|
|
ifc_id = obj.BIMObjectProperties.ifc_definition_id
|
|
try:
|
|
element = ifc_file.by_id(ifc_id)
|
|
except:
|
|
continue
|
|
|
|
# Check if element has representation
|
|
if not hasattr(element, 'Representation') or not element.Representation:
|
|
objects_without_representations += 1
|
|
continue
|
|
|
|
objects_with_representations += 1
|
|
|
|
# Count representation types
|
|
for representation in element.Representation.Representations:
|
|
rep_type = representation.RepresentationType
|
|
representation_counts[rep_type] += 1
|
|
|
|
# If this is a mapped representation, also analyze what's inside
|
|
if include_mapped_analysis and rep_type == 'MappedRepresentation':
|
|
inner_types = get_all_representation_types_recursive(representation)
|
|
for inner_type in inner_types:
|
|
mapped_representation_counts[inner_type] += 1
|
|
|
|
# Display results
|
|
print(f"Objects with representations: {objects_with_representations}")
|
|
print(f"Objects without representations: {objects_without_representations}")
|
|
print(f"\nDirect representation types found:")
|
|
print("-" * 40)
|
|
|
|
# Sort by count (most common first)
|
|
sorted_counts = sorted(representation_counts.items(), key=lambda x: x[1], reverse=True)
|
|
|
|
for rep_type, count in sorted_counts:
|
|
if objects_with_representations > 0:
|
|
percentage = (count / objects_with_representations) * 100
|
|
print(f"{rep_type:25} {count:6} objects ({percentage:5.1f}%)")
|
|
else:
|
|
print(f"{rep_type:25} {count:6} objects")
|
|
|
|
if include_mapped_analysis and mapped_representation_counts:
|
|
print(f"\nRepresentation types found WITHIN mapped representations:")
|
|
print("-" * 50)
|
|
sorted_mapped = sorted(mapped_representation_counts.items(), key=lambda x: x[1], reverse=True)
|
|
for rep_type, count in sorted_mapped:
|
|
print(f"{rep_type:25} {count:6} instances")
|
|
|
|
print("\n" + "="*60)
|
|
return representation_counts, mapped_representation_counts
|
|
|
|
def get_all_representation_types_recursive(representation, visited=None):
|
|
"""
|
|
Get all representation types found recursively within a representation.
|
|
|
|
Args:
|
|
representation: IFC representation to analyze
|
|
visited (set): Prevents circular references
|
|
|
|
Returns:
|
|
list: All representation types found
|
|
"""
|
|
if visited is None:
|
|
visited = set()
|
|
|
|
found_types = []
|
|
|
|
# Prevent circular references
|
|
if representation.id() in visited:
|
|
return found_types
|
|
visited.add(representation.id())
|
|
|
|
# Add current representation type
|
|
if hasattr(representation, 'RepresentationType'):
|
|
found_types.append(representation.RepresentationType)
|
|
|
|
# Dive into Items for MappedRepresentation or others
|
|
for item in getattr(representation, "Items", []):
|
|
# Case 1: Item is a MappedItem -> check its MappingSource recursively
|
|
if hasattr(item, "MappingSource") and item.MappingSource:
|
|
nested_rep = item.MappingSource.MappedRepresentation
|
|
nested_types = get_all_representation_types_recursive(nested_rep, visited)
|
|
found_types.extend(nested_types)
|
|
|
|
# Case 2: Item is a representation
|
|
if hasattr(item, "RepresentationType"):
|
|
found_types.append(item.RepresentationType)
|
|
|
|
return found_types
|
|
|
|
|
|
|
|
# Updated predefined helper functions - now include mapped by default
|
|
def select_solid_representations(include_mapped=True):
|
|
"""Select all solid-type representations"""
|
|
select_objects_by_representation(['SolidModel', 'AdvancedBrep', 'Brep', 'SweptSolid', 'AdvancedSweptSolid'], include_mapped=include_mapped)
|
|
|
|
def select_curve_representations(include_mapped=True):
|
|
"""Select all curve-type representations"""
|
|
select_objects_by_representation(['Curve', 'Curve2D', 'Curve3D'], include_mapped=include_mapped)
|
|
|
|
def select_surface_representations(include_mapped=True):
|
|
"""Select all surface-type representations"""
|
|
select_objects_by_representation(['Surface', 'Surface2D', 'Surface3D', 'AdvancedSurface', 'SurfaceModel'], include_mapped=include_mapped)
|
|
|
|
def select_mapped_representations():
|
|
"""Select all mapped representations"""
|
|
select_objects_by_representation(['MappedRepresentation'])
|
|
|
|
def select_tessellated_representations(include_mapped=True):
|
|
"""Select all tessellated representations"""
|
|
select_objects_by_representation(['Tessellation'], include_mapped=include_mapped)
|
|
|
|
# Quick selection functions based on your specific model - now include mapped by default
|
|
def select_main_building_elements(include_mapped=True):
|
|
"""Select main building elements (SweptSolid, AdvancedBrep, SolidModel, Brep)"""
|
|
select_objects_by_representation(['SweptSolid', 'AdvancedBrep', 'SolidModel', 'Brep', 'AdvancedSweptSolid'], include_mapped=include_mapped)
|
|
|
|
def select_complex_geometry(include_mapped=True):
|
|
"""Select complex geometry (AdvancedBrep, Tessellation, Clipping)"""
|
|
select_objects_by_representation(['AdvancedBrep', 'Tessellation', 'Clipping'], include_mapped=include_mapped)
|
|
|
|
def select_linear_elements(include_mapped=True):
|
|
"""Select linear elements (Curve2D, Curve3D)"""
|
|
select_objects_by_representation(['Curve2D', 'Curve3D'], include_mapped=include_mapped)
|
|
|
|
# New functions for mapped-specific selection
|
|
def select_solid_from_mapped_only():
|
|
"""Select objects that have solid representations ONLY within mapped representations"""
|
|
select_objects_by_representation(['SolidModel', 'AdvancedBrep', 'Brep', 'SweptSolid', 'AdvancedSweptSolid'],
|
|
include_mapped=True, search_mapped_only=True)
|
|
|
|
def select_curves_from_mapped_only():
|
|
"""Select objects that have curve representations ONLY within mapped representations"""
|
|
select_objects_by_representation(['Curve', 'Curve2D', 'Curve3D'],
|
|
include_mapped=True, search_mapped_only=True)
|
|
|
|
print("\n" + "="*60)
|
|
print("IFC REPRESENTATION SELECTOR SCRIPT - ENHANCED")
|
|
print("="*60)
|
|
print("Available functions:")
|
|
print("- analyze_model_representations() - Analyze your model (includes mapped analysis)")
|
|
print("- show_available_types() - Show all representation types")
|
|
print("- select_objects_by_representation([types], include_mapped=True) - Select by type(s)")
|
|
print("\nQuick selection functions (now include mapped by default):")
|
|
print("- select_solid_representations() - All solid geometry")
|
|
print("- select_curve_representations() - All curves")
|
|
print("- select_surface_representations() - All surfaces")
|
|
print("- select_mapped_representations() - All mapped items")
|
|
print("- select_main_building_elements() - Main building elements")
|
|
print("- select_complex_geometry() - Complex/advanced geometry")
|
|
print("- select_linear_elements() - Linear elements")
|
|
print("\nMapped-specific functions:")
|
|
print("- select_solid_from_mapped_only() - Solids found ONLY in mapped representations")
|
|
print("- select_curves_from_mapped_only() - Curves found ONLY in mapped representations")
|
|
print("\nExample usage:")
|
|
print("select_objects_by_representation(['SweptSolid', 'AdvancedBrep']) # includes mapped")
|
|
print("select_objects_by_representation(['SweptSolid'], include_mapped=False) # direct only")
|
|
print("select_objects_by_representation(['SweptSolid'], search_mapped_only=True) # mapped only")
|
|
print("="*60)
|
|
|
|
|
|
def show_available_types():
|
|
"""
|
|
Display all available IFC representation types with descriptions.
|
|
"""
|
|
type_descriptions = {
|
|
'Point': '2 or 3 dimensional point(s)',
|
|
'PointCloud': '3 dimensional points represented by a point list (DEPRECATED - use Point)',
|
|
'Curve': '2 or 3 dimensional curve(s)',
|
|
'Curve2D': '2 dimensional curve(s)',
|
|
'Curve3D': '3 dimensional curve(s)',
|
|
'Surface': '2 or 3 dimensional surface(s)',
|
|
'Surface2D': '2 dimensional surface(s) (region on ground view)',
|
|
'Surface3D': '3 dimensional surface(s)',
|
|
'SectionedSurfaces': 'swept surface(s) created by sweeping open profiles along a directrix',
|
|
'FillArea': '2D region(s) represented as filled area (hatching)',
|
|
'Text': 'text defined as text literals',
|
|
'AdvancedSurface': '3 dimensional b-spline surface(s)',
|
|
'GeometricSet': 'points, curves, surfaces (2 or 3 dimensional)',
|
|
'GeometricCurveSet': 'points, curves (2 or 3 dimensional)',
|
|
'Annotation2D': 'points, curves (2 or 3 dimensional), hatches and text (2 dimensional)',
|
|
'SurfaceModel': 'face based and shell based surface model(s), or tessellated surface model(s)',
|
|
'Tessellation': 'tessellated surface representation(s) only',
|
|
'Segment': 'partial geometry of curves that shall not be rendered separately',
|
|
'SolidModel': 'swept solid, Boolean results and Brep bodies',
|
|
'SweptSolid': 'swept area solids, by extrusion and revolution, excluding tapered sweeps',
|
|
'AdvancedSweptSolid': 'swept area solids created by sweeping profile along directrix, and tapered sweeps',
|
|
'Brep': 'faceted Brep\'s with and without voids',
|
|
'AdvancedBrep': 'Brep\'s based on advanced faces, with b-spline surface geometry',
|
|
'CSG': 'Boolean results of operations between solid models, half spaces and Boolean results',
|
|
'Clipping': 'Boolean differences between swept area solids, half spaces and Boolean results',
|
|
'BoundingBox': 'simplistic 3D representation by a bounding box',
|
|
'SectionedSpine': 'cross section based representation of spine curve and planar cross sections',
|
|
'LightSource': 'light source with position, orientation, light colour, intensity and attenuation',
|
|
'MappedRepresentation': 'representation based on mapped item(s), referring to representation map'
|
|
}
|
|
|
|
print("\n=== AVAILABLE IFC REPRESENTATION TYPES ===")
|
|
for rep_type in AVAILABLE_REPRESENTATION_TYPES:
|
|
description = type_descriptions.get(rep_type, 'No description available')
|
|
print(f"{rep_type:20} - {description}")
|
|
|
|
|
|
# Updated main call to include mapped representations by default
|
|
select_objects_by_representation(['SweptSolid', 'AdvancedBrep', 'SolidModel', 'Brep', 'AdvancedSweptSolid'], include_mapped=True)
|
|
|
|
|
|
|