mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Fix #4057. Data objects for the API are now in util.data instead of mixed into the shape builder utility and only depends on numpy.
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.shape_builder import ClippingInfo
|
||||
from ifcopenshell.util.data import Clipping
|
||||
|
||||
|
||||
class Usecase:
|
||||
@@ -29,8 +29,8 @@ class Usecase:
|
||||
"profile": None,
|
||||
"depth": 1.0,
|
||||
"cardinal_point": 5,
|
||||
# Planes are defined either by ClippingInfo objects
|
||||
# or by dictionaries of arguments for `ClippingInfo.parse`
|
||||
# Planes are defined either by Clipping objects
|
||||
# or by dictionaries of arguments for `Clipping.parse`
|
||||
"clippings": [], # A list of planes that define clipping half space solids
|
||||
"placement_zx_axes": (None, None),
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class Usecase:
|
||||
|
||||
def execute(self):
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
self.settings["clippings"] = [ClippingInfo.parse(c) for c in self.settings["clippings"]]
|
||||
self.settings["clippings"] = [Clipping.parse(c) for c in self.settings["clippings"]]
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
@@ -71,7 +71,7 @@ class Usecase:
|
||||
new = ifcopenshell.util.element.copy(self.file, clipping)
|
||||
new.FirstOperand = first_operand
|
||||
first_operand = new
|
||||
else: # ClippingInfo
|
||||
else: # Clipping
|
||||
first_operand = clipping.apply(self.file, first_operand, self.settings["unit_scale"])
|
||||
return first_operand
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ class Usecase:
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
"depth": 0.2,
|
||||
"x_angle": 0, # Radians
|
||||
# Planes are defined either by ClippingInfo objects
|
||||
# or by dictionaries of arguments for `ClippingInfo.parse`
|
||||
# Planes are defined either by Clipping objects
|
||||
# or by dictionaries of arguments for `Clipping.parse`
|
||||
"clippings": [], # A list of planes that define clipping half space solids
|
||||
}
|
||||
for key, value in settings.items():
|
||||
@@ -83,7 +83,7 @@ class Usecase:
|
||||
new = ifcopenshell.util.element.copy(self.file, clipping)
|
||||
new.FirstOperand = first_operand
|
||||
first_operand = new
|
||||
else: # ClippingInfo
|
||||
else: # Clipping
|
||||
first_operand = clipping.apply(self.file, first_operand, self.settings["unit_scale"])
|
||||
return first_operand
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
import ifcopenshell.util.unit
|
||||
from math import sin, cos
|
||||
from ifcopenshell.util.shape_builder import ClippingInfo
|
||||
from ifcopenshell.util.data import Clipping
|
||||
|
||||
|
||||
class Usecase:
|
||||
@@ -32,8 +32,8 @@ class Usecase:
|
||||
"thickness": 0.2,
|
||||
# Sloped walls along the wall's X axis, provided in radians
|
||||
"x_angle": 0,
|
||||
# Planes are defined either by ClippingInfo objects
|
||||
# or by dictionaries of arguments for `ClippingInfo.parse`
|
||||
# Planes are defined either by Clipping objects
|
||||
# or by dictionaries of arguments for `Clipping.parse`
|
||||
"clippings": [], # A list of planes that define clipping half space solids
|
||||
"booleans": [], # Any existing IfcBooleanResults
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class Usecase:
|
||||
|
||||
def execute(self):
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
self.settings["clippings"] = [ClippingInfo.parse(c) for c in self.settings["clippings"]]
|
||||
self.settings["clippings"] = [Clipping.parse(c) for c in self.settings["clippings"]]
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
@@ -101,7 +101,7 @@ class Usecase:
|
||||
new = ifcopenshell.util.element.copy(self.file, clipping)
|
||||
new.FirstOperand = first_operand
|
||||
first_operand = new
|
||||
else: # ClippingInfo
|
||||
else: # Clipping
|
||||
first_operand = clipping.apply(self.file, first_operand, self.settings["unit_scale"])
|
||||
return first_operand
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>, @Andrej730
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
from typing import Any, Union
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Clipping:
|
||||
location: tuple[float, float, float]
|
||||
normal: tuple[float, float, float]
|
||||
type: str = "IfcBooleanClippingResult"
|
||||
operand_type: str = "IfcHalfSpaceSolid"
|
||||
|
||||
@classmethod
|
||||
def parse(cls, raw_data: Any) -> Union[ifcopenshell.entity_instance, Clipping, None]:
|
||||
"""Parse various formats into a clipping object
|
||||
|
||||
`raw_data` can be either:
|
||||
|
||||
- IfcBooleanResult IFC entity
|
||||
- `Clipping` instance
|
||||
- dictionary to define `Clipping` - either `location` and `normal`
|
||||
or a `matrix` where XY plane is the clipping boundary and +Z is removed.
|
||||
`matrix` method will be soon to be deprecated completely.
|
||||
"""
|
||||
if isinstance(raw_data, ifcopenshell.entity_instance):
|
||||
if not raw_data.is_a("IfcBooleanResult"):
|
||||
raise Exception(f"Provided clipping of unexpected IFC class: {raw_data}")
|
||||
return raw_data
|
||||
elif isinstance(raw_data, Clipping):
|
||||
return raw_data
|
||||
elif isinstance(raw_data, dict):
|
||||
if "matrix" in raw_data:
|
||||
raw_data = raw_data.copy()
|
||||
matrix = np.array(raw_data["matrix"])[:3]
|
||||
raw_data["normal"] = matrix[:, 2].tolist()
|
||||
raw_data["location"] = matrix[:, 3].tolist()
|
||||
del raw_data["matrix"]
|
||||
clipping_data = cls(**raw_data)
|
||||
if clipping_data.type != "IfcBooleanClippingResult":
|
||||
raise Exception(f'Provided clipping with unexpected result type "{clipping_data.type}"')
|
||||
if clipping_data.operand_type != "IfcHalfSpaceSolid":
|
||||
raise Exception(f'Provided clipping with unexpected operand type "{clipping_data.operand_type}"')
|
||||
return clipping_data
|
||||
raise Exception(f"Unexpected clipping type provided: {raw_data}")
|
||||
|
||||
def apply(
|
||||
self, ifc_file: ifcopenshell.file, first_operand: ifcopenshell.entity_instance, unit_scale: float
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Applies the clipping data as an IfcBooleanClippingResult to an operand
|
||||
|
||||
:param ifc_file: The model to create the entities in
|
||||
:param first_operand: The representation item to apply the boolean clipping to.
|
||||
:param unit_scale: The unit scale value to convert from the Clipping's SI units to project units
|
||||
:return: An IfcBooleanClippingResult which uses an IfcHalfSpaceSolid to clip the first operand
|
||||
"""
|
||||
|
||||
location = ifc_file.createIfcCartesianPoint([i / unit_scale for i in self.location])
|
||||
direction = ifc_file.createIfcDirection(self.normal)
|
||||
|
||||
normal = np.array(self.normal)
|
||||
if np.allclose(normal, np.array([0.0, 0.0, 1.0]), atol=1e-2):
|
||||
arbitrary_vector = np.array([0.0, 1.0, 0.0])
|
||||
else:
|
||||
arbitrary_vector = np.array([0.0, 0.0, 1.0])
|
||||
|
||||
x_axis = np.cross(normal, arbitrary_vector)
|
||||
x_axis /= np.linalg.norm(x_axis)
|
||||
x_axis = ifc_file.createIfcDirection(x_axis.tolist())
|
||||
|
||||
plane = ifc_file.createIfcPlane(ifc_file.createIfcAxis2Placement3D(location, direction, x_axis))
|
||||
|
||||
second_operand = ifc_file.createIfcHalfSpaceSolid(plane, False)
|
||||
return ifc_file.createIfcBooleanClippingResult("DIFFERENCE", first_operand, second_operand)
|
||||
@@ -16,16 +16,14 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import numpy as np
|
||||
import collections
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
from math import cos, sin, pi, tan, radians, degrees, atan, sqrt, ceil
|
||||
from typing import List, Any, Union
|
||||
from typing import List
|
||||
from itertools import chain
|
||||
from mathutils import Vector, Matrix
|
||||
from dataclasses import dataclass
|
||||
|
||||
V = lambda *x: Vector([float(i) for i in x])
|
||||
sign = lambda x: x and (1, -1)[x < 0]
|
||||
@@ -1476,50 +1474,3 @@ class ShapeBuilder:
|
||||
"main_profile_dimension": profile_dim[lateral_axis],
|
||||
}
|
||||
return rep, bend_data
|
||||
|
||||
|
||||
@dataclass
|
||||
class ClippingInfo:
|
||||
location: tuple[float, float, float]
|
||||
normal: tuple[float, float, float]
|
||||
type: str = "IfcBooleanClippingResult"
|
||||
operand_type: str = "IfcHalfSpaceSolid"
|
||||
|
||||
@classmethod
|
||||
def parse(cls, raw_data: Any) -> Union[ifcopenshell.entity_instance, ClippingInfo, None]:
|
||||
"""`raw_data` can be either:
|
||||
- IfcBooleanResult IFC entity
|
||||
- `ClippingInfo` instance
|
||||
- dictionary to define `ClippingInfo` - either `location` and `normal`
|
||||
or a `matrix` where XY plane is the clipping boundary and +Z is removed.
|
||||
`matrix` method will be soon to be deprecated completely.
|
||||
"""
|
||||
if isinstance(raw_data, ifcopenshell.entity_instance):
|
||||
if not raw_data.is_a("IfcBooleanResult"):
|
||||
raise Exception(f"Provided clipping of unexpected IFC class: {raw_data}")
|
||||
return raw_data
|
||||
elif isinstance(raw_data, ClippingInfo):
|
||||
return raw_data
|
||||
elif isinstance(raw_data, dict):
|
||||
if "matrix" in raw_data:
|
||||
raw_data = raw_data.copy()
|
||||
matrix = np.array(raw_data["matrix"])[:3]
|
||||
raw_data["normal"] = matrix[:, 2].tolist()
|
||||
raw_data["location"] = matrix[:, 3].tolist()
|
||||
del raw_data["matrix"]
|
||||
clipping_data = cls(**raw_data)
|
||||
if clipping_data.type != "IfcBooleanClippingResult":
|
||||
raise Exception(f'Provided clipping with unexpected result type "{clipping_data.type}"')
|
||||
if clipping_data.operand_type != "IfcHalfSpaceSolid":
|
||||
raise Exception(f'Provided clipping with unexpected operand type "{clipping_data.operand_type}"')
|
||||
return clipping_data
|
||||
raise Exception(f"Unexpected clipping type provided: {raw_data}")
|
||||
|
||||
def apply(
|
||||
self, file: ifcopenshell.file, first_operand: ifcopenshell.entity_instance, unit_scale: float
|
||||
) -> ifcopenshell.entity_instance:
|
||||
builder = ShapeBuilder(file)
|
||||
plane = builder.plane(location=Vector([i / unit_scale for i in self.location]), normal=Vector(self.normal))
|
||||
second_operand = file.createIfcHalfSpaceSolid(plane, False)
|
||||
first_operand = file.createIfcBooleanClippingResult("DIFFERENCE", first_operand, second_operand)
|
||||
return first_operand
|
||||
|
||||
Reference in New Issue
Block a user