mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 22:33:33 +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.geom
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
from ifcopenshell.util.shape_builder import ClippingInfo
|
from ifcopenshell.util.data import Clipping
|
||||||
|
|
||||||
|
|
||||||
class Usecase:
|
class Usecase:
|
||||||
@@ -29,8 +29,8 @@ class Usecase:
|
|||||||
"profile": None,
|
"profile": None,
|
||||||
"depth": 1.0,
|
"depth": 1.0,
|
||||||
"cardinal_point": 5,
|
"cardinal_point": 5,
|
||||||
# Planes are defined either by ClippingInfo objects
|
# Planes are defined either by Clipping objects
|
||||||
# or by dictionaries of arguments for `ClippingInfo.parse`
|
# or by dictionaries of arguments for `Clipping.parse`
|
||||||
"clippings": [], # A list of planes that define clipping half space solids
|
"clippings": [], # A list of planes that define clipping half space solids
|
||||||
"placement_zx_axes": (None, None),
|
"placement_zx_axes": (None, None),
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ class Usecase:
|
|||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
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(
|
return self.file.createIfcShapeRepresentation(
|
||||||
self.settings["context"],
|
self.settings["context"],
|
||||||
self.settings["context"].ContextIdentifier,
|
self.settings["context"].ContextIdentifier,
|
||||||
@@ -71,7 +71,7 @@ class Usecase:
|
|||||||
new = ifcopenshell.util.element.copy(self.file, clipping)
|
new = ifcopenshell.util.element.copy(self.file, clipping)
|
||||||
new.FirstOperand = first_operand
|
new.FirstOperand = first_operand
|
||||||
first_operand = new
|
first_operand = new
|
||||||
else: # ClippingInfo
|
else: # Clipping
|
||||||
first_operand = clipping.apply(self.file, first_operand, self.settings["unit_scale"])
|
first_operand = clipping.apply(self.file, first_operand, self.settings["unit_scale"])
|
||||||
return first_operand
|
return first_operand
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ class Usecase:
|
|||||||
"context": None, # IfcGeometricRepresentationContext
|
"context": None, # IfcGeometricRepresentationContext
|
||||||
"depth": 0.2,
|
"depth": 0.2,
|
||||||
"x_angle": 0, # Radians
|
"x_angle": 0, # Radians
|
||||||
# Planes are defined either by ClippingInfo objects
|
# Planes are defined either by Clipping objects
|
||||||
# or by dictionaries of arguments for `ClippingInfo.parse`
|
# or by dictionaries of arguments for `Clipping.parse`
|
||||||
"clippings": [], # A list of planes that define clipping half space solids
|
"clippings": [], # A list of planes that define clipping half space solids
|
||||||
}
|
}
|
||||||
for key, value in settings.items():
|
for key, value in settings.items():
|
||||||
@@ -83,7 +83,7 @@ class Usecase:
|
|||||||
new = ifcopenshell.util.element.copy(self.file, clipping)
|
new = ifcopenshell.util.element.copy(self.file, clipping)
|
||||||
new.FirstOperand = first_operand
|
new.FirstOperand = first_operand
|
||||||
first_operand = new
|
first_operand = new
|
||||||
else: # ClippingInfo
|
else: # Clipping
|
||||||
first_operand = clipping.apply(self.file, first_operand, self.settings["unit_scale"])
|
first_operand = clipping.apply(self.file, first_operand, self.settings["unit_scale"])
|
||||||
return first_operand
|
return first_operand
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
from math import sin, cos
|
from math import sin, cos
|
||||||
from ifcopenshell.util.shape_builder import ClippingInfo
|
from ifcopenshell.util.data import Clipping
|
||||||
|
|
||||||
|
|
||||||
class Usecase:
|
class Usecase:
|
||||||
@@ -32,8 +32,8 @@ class Usecase:
|
|||||||
"thickness": 0.2,
|
"thickness": 0.2,
|
||||||
# Sloped walls along the wall's X axis, provided in radians
|
# Sloped walls along the wall's X axis, provided in radians
|
||||||
"x_angle": 0,
|
"x_angle": 0,
|
||||||
# Planes are defined either by ClippingInfo objects
|
# Planes are defined either by Clipping objects
|
||||||
# or by dictionaries of arguments for `ClippingInfo.parse`
|
# or by dictionaries of arguments for `Clipping.parse`
|
||||||
"clippings": [], # A list of planes that define clipping half space solids
|
"clippings": [], # A list of planes that define clipping half space solids
|
||||||
"booleans": [], # Any existing IfcBooleanResults
|
"booleans": [], # Any existing IfcBooleanResults
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ class Usecase:
|
|||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
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(
|
return self.file.createIfcShapeRepresentation(
|
||||||
self.settings["context"],
|
self.settings["context"],
|
||||||
self.settings["context"].ContextIdentifier,
|
self.settings["context"].ContextIdentifier,
|
||||||
@@ -101,7 +101,7 @@ class Usecase:
|
|||||||
new = ifcopenshell.util.element.copy(self.file, clipping)
|
new = ifcopenshell.util.element.copy(self.file, clipping)
|
||||||
new.FirstOperand = first_operand
|
new.FirstOperand = first_operand
|
||||||
first_operand = new
|
first_operand = new
|
||||||
else: # ClippingInfo
|
else: # Clipping
|
||||||
first_operand = clipping.apply(self.file, first_operand, self.settings["unit_scale"])
|
first_operand = clipping.apply(self.file, first_operand, self.settings["unit_scale"])
|
||||||
return first_operand
|
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
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import collections
|
import collections
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.api
|
import ifcopenshell.api
|
||||||
from math import cos, sin, pi, tan, radians, degrees, atan, sqrt, ceil
|
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 itertools import chain
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
V = lambda *x: Vector([float(i) for i in x])
|
V = lambda *x: Vector([float(i) for i in x])
|
||||||
sign = lambda x: x and (1, -1)[x < 0]
|
sign = lambda x: x and (1, -1)[x < 0]
|
||||||
@@ -1476,50 +1474,3 @@ class ShapeBuilder:
|
|||||||
"main_profile_dimension": profile_dim[lateral_axis],
|
"main_profile_dimension": profile_dim[lateral_axis],
|
||||||
}
|
}
|
||||||
return rep, bend_data
|
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