Utility_Apps/Blender/simple scripts/Select objects with specific representation/Select objects with specific representations.py

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)