# IfcOpenShell - IFC toolkit and geometry engine # Copyright (C) 2025 Thomas Krijnen # # This file is part of IfcOpenShell. # # IfcOpenShell is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # IfcOpenShell is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . import numpy as np import ifcopenshell import ifcopenshell.geom from ifcopenshell import entity_instance, ifcopenshell_wrapper def evaluate_representation(shape_rep: entity_instance, dist_along: float) -> np.ndarray: """ Calculate the 4x4 geometric transform at a point on an alignment segment :param shape_rep: The representation shape (composite curve, gradient curve, or segmented reference curve) to evaluate :param dist_along: The distance along this representation at the point of interest (point to be calculated) """ supported_rep_types = ["IFCCOMPOSITECURVE", "IFCGRADIENTCURVE", "IFCSEGMENTEDREFERENCECURVE"] shape_rep_type = shape_rep.is_a().upper() if not shape_rep_type in supported_rep_types: raise NotImplementedError( f"Expected entity type to be one of {[_ for _ in supported_rep_types]}, got '{shape_rep_type}" ) # TODO: confirm point is not beyond limits of alignment s = ifcopenshell.geom.settings() function_item = ifcopenshell_wrapper.map_shape(s, shape_rep.wrapped_data) evaluator = ifcopenshell_wrapper.function_item_evaluator(s, function_item) trans_matrix = evaluator.evaluate(dist_along) return np.array(trans_matrix, dtype=np.float64).T def evaluate_segment(segment: entity_instance, dist_along: float) -> np.ndarray: """ Calculate the 4x4 geometric transform at a point on an alignment segment :param segment: The segment containing the point that we would like to :param dist_along: The distance along this segment at the point of interest (point to be calculated) """ supported_segment_types = ["IFCCURVESEGMENT"] segment_type = segment.is_a().upper() if not segment_type in supported_segment_types: raise NotImplementedError(f"Expected entity type 'IFCCURVESEGMENT', got '{segment_type}") if dist_along > segment.SegmentLength: raise ValueError(f"Provided value {dist_along=} is beyond the end of the segment ({segment.SegmentLength}).") s = ifcopenshell.geom.settings() function_item = ifcopenshell_wrapper.map_shape(s, segment.wrapped_data) evaluator = ifcopenshell_wrapper.function_item_evaluator(s, function_item) trans_matrix = evaluator.evaluate(dist_along) return np.array(trans_matrix, dtype=np.float64).T def generate_vertices(rep_curve: entity_instance, distance_interval: float = 5.0) -> np.ndarray: """ Generate vertices along an alignment :param rep_curve: The alignment's representation curve to use to generate vertices. :param distance_interval: The distance between points along the alignment at which to generate the points """ if rep_curve is None: raise ValueError("Alignment representation not found.") supported_rep_types = ["IFCCOMPOSITECURVE", "IFCGRADIENTCURVE", "IFCSEGMENTEDREFERENCECURVE"] shape_rep_type = rep_curve.is_a().upper() if not shape_rep_type in supported_rep_types: raise NotImplementedError( f"Expected entity type to be one of {[_ for _ in supported_rep_types]}, got '{shape_rep_type}" ) s = ifcopenshell.geom.settings() s.set("piecewise-step-type", 0) # 0 = step-size is maximum step size, 1 = step-size is mininimum number of steps s.set("piecewise-step-size", distance_interval) shape = ifcopenshell.geom.create_shape(s, rep_curve) vertices = shape.verts if len(vertices) == 0: msg = f"[ERROR] No vertices generated by ifcopenshell.geom.create_shape()." raise ValueError(msg) return np.array(vertices).reshape((-1, 3)) def print_alignment(alignment, indent=0): """ Debugging function to print alignment decomposition """ print(" " * indent, alignment) for rel in alignment.IsNestedBy: for child in rel.RelatedObjects: print_alignment(child, indent + 2) for agg in alignment.IsDecomposedBy: for child in agg.RelatedObjects: print_alignment(child, indent + 2) def print_alignment_deep(alignment, indent=0): """ Debugging function to print alignment decomposition, including layout segments """ print(" " * indent, alignment) for rel in alignment.IsNestedBy: for child in rel.RelatedObjects: print_alignment_deep(child, indent + 2) if child.is_a("IfcAlignmentSegment"): print(" " * (indent + 4), child.DesignParameters) for agg in alignment.IsDecomposedBy: for child in agg.RelatedObjects: print_alignment_deep(child, indent + 2) def print_composite_curve(curve): """ Debugging function to print composite curve segments """ print(str(curve)[0:100]) for segment in curve.Segments: print(" " * 2, segment) def print_composite_curve_deep(curve): """ Debugging function to print composite curve segments, including curve segment details """ print(str(curve)[0:100]) for segment in curve.Segments: print(" " * 2, segment) print(" " * 4, segment.ParentCurve) print(" " * 4, segment.Placement) print(" " * 4, segment.Placement.Location) print(" " * 4, segment.Placement.RefDirection) def print_positioned_products(file: ifcopenshell.file): referents = file.by_type("IfcReferent") for referent in referents: print(referent) for rel in referent.Positions: for product in rel.RelatedProducts: print(" " * 2, product)