mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Add geometry.clip_solid API and fix half_space_solid docstring
clip_solid(file, item, location, normal) wraps Clipping.apply() to clip any solid with a half-space plane. Normal points toward the removed material — same convention as add_wall_representation clippings. Clarifies that agreement_flag=False means the normal direction is the void (removed) side.
This commit is contained in:
@@ -26,6 +26,7 @@ geometry extrusions).
|
||||
from .. import wrap_usecases
|
||||
from .add_axis_representation import add_axis_representation
|
||||
from .add_boolean import add_boolean
|
||||
from .clip_solid import clip_solid
|
||||
from .add_door_representation import add_door_representation
|
||||
from .add_footprint_representation import add_footprint_representation
|
||||
from .add_mesh_representation import add_mesh_representation
|
||||
@@ -61,6 +62,7 @@ wrap_usecases(__path__, __name__)
|
||||
__all__ = [
|
||||
"add_axis_representation",
|
||||
"add_boolean",
|
||||
"clip_solid",
|
||||
"add_door_representation",
|
||||
"add_footprint_representation",
|
||||
"add_mesh_representation",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2026 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# 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
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.data import Clipping
|
||||
|
||||
|
||||
def clip_solid(
|
||||
file: ifcopenshell.file,
|
||||
item: ifcopenshell.entity_instance,
|
||||
location: Sequence[float],
|
||||
normal: Sequence[float],
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Clip a solid with a half-space plane, returning an IfcBooleanClippingResult.
|
||||
|
||||
Convenience wrapper around :class:`ifcopenshell.util.data.Clipping` for
|
||||
use with any solid. The ``normal`` points toward the **removed** material
|
||||
(the void side); the kept region is on the opposite side. This is the same
|
||||
convention used by the ``clippings`` parameter of :func:`add_wall_representation`.
|
||||
|
||||
After clipping, set the parent ``IfcShapeRepresentation``
|
||||
``RepresentationType`` to ``"Clipping"``.
|
||||
|
||||
Example — trim an extruded solid to a lean-to slope (removed material is
|
||||
above the slope)::
|
||||
|
||||
bcr = ifcopenshell.api.run(
|
||||
"geometry.clip_solid", model,
|
||||
item=extrusion,
|
||||
location=[0.0, 0.0, 3.26],
|
||||
normal=[0.419, 0.0, 0.908], # points UP toward removed material
|
||||
)
|
||||
|
||||
:param item: The solid to clip (``IfcSweptAreaSolid``, ``IfcSweptDiskSolid``,
|
||||
or ``IfcBooleanClippingResult``).
|
||||
:param location: A point on the clipping plane in the representation's
|
||||
local coordinate system.
|
||||
:param normal: Plane normal pointing toward the material to be removed.
|
||||
:return: The resulting ``IfcBooleanClippingResult``.
|
||||
"""
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
|
||||
clipping = Clipping(location=tuple(location), normal=tuple(normal))
|
||||
return clipping.apply(file, item, unit_scale)
|
||||
@@ -1038,7 +1038,7 @@ class ShapeBuilder:
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""
|
||||
:param plane: The IfcPlane representing the half space.
|
||||
:param agreement_flag: False if +Z represents the void
|
||||
:param agreement_flag: If False (default), the plane normal points toward the **removed** material (the void). The kept region is on the opposite side from the normal.
|
||||
:return: IfcHalfSpaceSolid
|
||||
"""
|
||||
return self.file.createIfcHalfSpaceSolid(plane, AgreementFlag=agreement_flag)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2026 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.util.shape_builder
|
||||
import test.bootstrap
|
||||
|
||||
|
||||
class TestClipSolid(test.bootstrap.IFC4):
|
||||
def make_extrusion(self):
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(self.file)
|
||||
rect = builder.rectangle(size=(1.0, 1.0))
|
||||
return builder.extrude(rect, magnitude=4.0)
|
||||
|
||||
def test_returns_boolean_clipping_result(self):
|
||||
extrusion = self.make_extrusion()
|
||||
result = ifcopenshell.api.geometry.clip_solid(
|
||||
self.file,
|
||||
item=extrusion,
|
||||
location=[0.0, 0.0, 3.0],
|
||||
normal=[0.0, 0.0, 1.0],
|
||||
)
|
||||
assert result.is_a("IfcBooleanClippingResult")
|
||||
assert result.Operator == "DIFFERENCE"
|
||||
|
||||
def test_first_operand_is_the_item(self):
|
||||
extrusion = self.make_extrusion()
|
||||
result = ifcopenshell.api.geometry.clip_solid(
|
||||
self.file,
|
||||
item=extrusion,
|
||||
location=[0.0, 0.0, 3.0],
|
||||
normal=[0.0, 0.0, 1.0],
|
||||
)
|
||||
assert result.FirstOperand == extrusion
|
||||
|
||||
def test_second_operand_is_half_space_solid(self):
|
||||
extrusion = self.make_extrusion()
|
||||
result = ifcopenshell.api.geometry.clip_solid(
|
||||
self.file,
|
||||
item=extrusion,
|
||||
location=[0.0, 0.0, 3.0],
|
||||
normal=[0.0, 0.0, 1.0],
|
||||
)
|
||||
assert result.SecondOperand.is_a("IfcHalfSpaceSolid")
|
||||
|
||||
def test_clip_plane_location_matches(self):
|
||||
extrusion = self.make_extrusion()
|
||||
result = ifcopenshell.api.geometry.clip_solid(
|
||||
self.file,
|
||||
item=extrusion,
|
||||
location=[0.0, 0.0, 3.0],
|
||||
normal=[0.0, 0.0, 1.0],
|
||||
)
|
||||
plane = result.SecondOperand.BaseSurface
|
||||
coords = plane.Position.Location.Coordinates
|
||||
assert list(coords) == [0.0, 0.0, 3.0]
|
||||
|
||||
def test_chaining_two_clips(self):
|
||||
extrusion = self.make_extrusion()
|
||||
first_clip = ifcopenshell.api.geometry.clip_solid(
|
||||
self.file,
|
||||
item=extrusion,
|
||||
location=[0.0, 0.0, 3.0],
|
||||
normal=[0.0, 0.0, 1.0],
|
||||
)
|
||||
second_clip = ifcopenshell.api.geometry.clip_solid(
|
||||
self.file,
|
||||
item=first_clip,
|
||||
location=[0.0, 0.0, 1.0],
|
||||
normal=[0.0, 0.0, -1.0],
|
||||
)
|
||||
assert second_clip.is_a("IfcBooleanClippingResult")
|
||||
assert second_clip.FirstOperand == first_clip
|
||||
assert first_clip.FirstOperand == extrusion
|
||||
|
||||
def test_angled_clip_plane(self):
|
||||
extrusion = self.make_extrusion()
|
||||
result = ifcopenshell.api.geometry.clip_solid(
|
||||
self.file,
|
||||
item=extrusion,
|
||||
location=[0.0, 0.0, 3.26],
|
||||
normal=[0.419, 0.0, 0.908],
|
||||
)
|
||||
assert result.is_a("IfcBooleanClippingResult")
|
||||
assert result.SecondOperand.is_a("IfcHalfSpaceSolid")
|
||||
|
||||
|
||||
class TestClipSolidIFC2X3(test.bootstrap.IFC2X3, TestClipSolid):
|
||||
pass
|
||||
Reference in New Issue
Block a user