Files
IfcOpenShell/src/ifcopenshell-python/ifcopenshell/geom/occ_utils.py
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

291 lines
10 KiB
Python
Raw Normal View History

# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Thomas Krijnen <thomas@aecgeeks.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/>.
2016-01-08 08:38:21 +01:00
2024-07-15 15:00:59 +05:00
from __future__ import annotations
2025-12-19 18:53:04 +05:00
import inspect
import operator
2025-12-19 18:53:04 +05:00
import random
2019-12-08 16:57:36 +01:00
import warnings
2024-05-07 12:17:46 +10:00
from collections.abc import Iterable
from typing import NamedTuple, Union
2026-03-13 18:28:51 +05:00
import OCC # pyright: ignore[reportMissingImports]
2025-12-19 18:53:04 +05:00
from typing_extensions import assert_never
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
2019-12-08 16:57:36 +01:00
try:
2026-03-13 18:28:51 +05:00
from OCC.Core import AIS, BRepTools, Graphic3d, Quantity, TopoDS, V3d, gp # pyright: ignore[reportMissingImports]
2020-11-01 20:08:27 +07:00
2020-06-15 11:36:13 +02:00
USE_OCCT_HANDLE = False
except ImportError:
2026-03-13 18:28:51 +05:00
from OCC import AIS, BRepTools, Graphic3d, Quantity, TopoDS, V3d, gp # pyright: ignore[reportMissingImports]
2020-11-01 20:08:27 +07:00
2020-06-15 11:36:13 +02:00
USE_OCCT_HANDLE = True
2019-12-08 16:57:36 +01:00
2024-07-15 15:00:59 +05:00
class shape_tuple(NamedTuple):
2024-07-27 12:27:23 +05:00
"""A tuple containing IfcOpenShell serialized element/shape and pythonOCC shape."""
2024-07-30 12:41:52 +02:00
2024-07-15 15:00:59 +05:00
data: Union[ifcopenshell_wrapper.SerializedElement, ifcopenshell_wrapper.Serialization]
geometry: TopoDS.TopoDS_Shape
styles: tuple[tuple[float, float, float, float], ...]
style_ids: tuple[int, ...]
handle, main_loop, add_menu, add_function_to_menu = None, None, None, None
DEFAULT_STYLES = {
2020-11-01 20:08:27 +07:00
"DEFAULT": (0.7, 0.7, 0.7),
"IfcSite": (0.75, 0.8, 0.65),
"IfcSlab": (0.4, 0.4, 0.4),
"IfcWallStandardCase": (0.9, 0.9, 0.9),
"IfcWall": (0.9, 0.9, 0.9),
"IfcWindow": (0.75, 0.8, 0.75, 0.3),
"IfcDoor": (0.55, 0.3, 0.15),
"IfcBeam": (0.75, 0.7, 0.7),
"IfcRailing": (0.65, 0.6, 0.6),
"IfcMember": (0.65, 0.6, 0.6),
"IfcPlate": (0.8, 0.8, 0.8),
}
2017-11-06 09:10:28 +01:00
def initialize_display():
2026-03-13 18:28:51 +05:00
import OCC.Display.SimpleGui # pyright: ignore[reportMissingImports]
2017-11-06 09:10:28 +01:00
global handle, main_loop, add_menu, add_function_to_menu
handle, main_loop, add_menu, add_function_to_menu = OCC.Display.SimpleGui.init_display()
def setup():
viewer_handle = handle.GetViewer()
2020-04-29 15:51:13 +02:00
viewer = viewer_handle.GetObject() if hasattr(viewer_handle, "GetObject") else viewer_handle
2017-11-06 09:10:28 +01:00
def lights():
viewer.InitActiveLights()
2020-04-29 15:51:13 +02:00
for _ in range(2):
2017-11-06 09:10:28 +01:00
try:
active_light = viewer.ActiveLight()
2017-11-06 11:06:40 +01:00
except BaseException:
2017-11-06 09:10:28 +01:00
break
yield active_light
viewer.NextActiveLights()
2017-11-06 09:10:28 +01:00
lights = list(lights())
for l in lights:
viewer.DelLight(l)
2017-11-06 09:10:28 +01:00
2020-11-01 20:08:27 +07:00
if hasattr(V3d, "V3d_TypeOfOrientation_Yup_AxoRight"):
2020-06-15 11:36:13 +02:00
dirs = [[V3d.V3d_TypeOfOrientation_Yup_AxoRight], [V3d.V3d_TypeOfOrientation_Zup_AxoRight]]
else:
dirs = [(3, 2, 1), (-1, -2, -3)]
for dir in dirs:
if OCC.VERSION < "7.5":
light = V3d.V3d_DirectionalLight(viewer_handle)
light.SetDirection(*dir)
else:
light = V3d.V3d_DirectionalLight(*dir)
2020-06-15 11:36:13 +02:00
viewer.SetLightOn(light.GetHandle() if USE_OCCT_HANDLE else light)
setup()
return handle
2017-11-06 09:10:28 +01:00
def yield_subshapes(shape):
2019-12-08 16:57:36 +01:00
it = TopoDS.TopoDS_Iterator(shape)
while it.More():
yield it.Value()
it.Next()
2017-11-06 09:10:28 +01:00
2016-04-17 15:27:05 +02:00
def display_shape(shape, clr=None, viewer_handle=None):
2017-11-06 09:10:28 +01:00
if viewer_handle is None:
viewer_handle = handle
if isinstance(shape, shape_tuple):
shape, representation = shape.geometry, shape
2017-11-06 09:10:28 +01:00
else:
representation = None
2019-12-08 16:57:36 +01:00
material = Graphic3d.Graphic3d_MaterialAspect(Graphic3d.Graphic3d_NOM_PLASTER)
2017-11-06 09:10:28 +01:00
if representation and not clr:
if len(set(representation.styles)) == 1:
clr = representation.styles[0]
2020-11-01 20:08:27 +07:00
if min(clr) < 0.0 or max(clr) > 1.0:
clr = DEFAULT_STYLES.get(representation.data.type, DEFAULT_STYLES["DEFAULT"])
2017-11-06 09:10:28 +01:00
if clr:
2019-12-08 16:57:36 +01:00
ais = AIS.AIS_Shape(shape)
ais.SetMaterial(material)
2017-11-06 09:10:28 +01:00
if isinstance(clr, str):
2020-11-01 20:08:27 +07:00
qclr = getattr(
Quantity, "Quantity_NOC_%s" % clr.upper(), getattr(Quantity, "Quantity_NOC_%s1" % clr.upper(), None)
)
if qclr is None:
raise Exception("No color named '%s'" % clr.upper())
elif isinstance(clr, Iterable):
clr = tuple(clr)
2019-10-17 14:06:53 +02:00
if len(clr) < 3 or len(clr) > 4:
raise Exception("Need 3 or 4 color components. Got '%r'." % len(clr))
2019-12-08 16:57:36 +01:00
qclr = Quantity.Quantity_Color(clr[0], clr[1], clr[2], Quantity.Quantity_TOC_RGB)
elif isinstance(clr, Quantity.Quantity_Color):
qclr = clr
else:
raise Exception("Object of type %r cannot be used as a color." % type(clr))
2017-11-06 09:10:28 +01:00
ais.SetColor(qclr)
2020-11-01 20:08:27 +07:00
if isinstance(clr, tuple) and len(clr) == 4 and clr[3] < 1.0:
ais.SetTransparency(1.0 - clr[3])
2019-12-08 16:57:36 +01:00
elif representation and hasattr(AIS, "AIS_MultipleConnectedShape"):
default_style_applied = None
2017-11-06 09:10:28 +01:00
2019-12-08 16:57:36 +01:00
ais = AIS.AIS_MultipleConnectedShape(shape)
2017-11-06 09:10:28 +01:00
subshapes = list(yield_subshapes(shape))
lens = len(representation.styles), len(subshapes)
if lens[0] != lens[1]:
warnings.warn("Unable to assign styles to subshapes. Encountered %d styles for %d shapes." % lens)
else:
for shp, stl in zip(subshapes, representation.styles):
2019-12-08 16:57:36 +01:00
subshape = AIS.AIS_Shape(shp)
2020-11-01 20:08:27 +07:00
if min(stl) < 0.0 or max(stl) > 1.0:
default_style_applied = stl = DEFAULT_STYLES.get(
representation.data.type, DEFAULT_STYLES["DEFAULT"]
)
2019-12-08 16:57:36 +01:00
subshape.SetColor(Quantity.Quantity_Color(stl[0], stl[1], stl[2], Quantity.Quantity_TOC_RGB))
subshape.SetMaterial(material)
2020-11-01 20:08:27 +07:00
if len(stl) == 4 and stl[3] < 1.0:
subshape.SetTransparency(1.0 - stl[3])
ais.Connect(subshape.GetHandle())
2017-11-06 09:10:28 +01:00
# For some reason it is necessary to set transparency here again
# in order for transparency to be rendered on the subshape.
applied_styles = representation.styles
if default_style_applied:
2017-11-06 09:10:28 +01:00
if len(default_style_applied) == 3:
2020-11-01 20:08:27 +07:00
default_style_applied += (1.0,)
applied_styles += (default_style_applied,)
2017-11-06 09:10:28 +01:00
2016-04-17 15:27:05 +02:00
if len(applied_styles):
# The only way for this not to be true if is the entire shape is NULL
min_transp = min(map(operator.itemgetter(3), applied_styles))
2020-11-01 20:08:27 +07:00
if min_transp < 1.0:
ais.SetTransparency(1.0)
else:
2019-12-08 16:57:36 +01:00
ais = AIS.AIS_Shape(shape)
ais.SetMaterial(material)
2017-11-06 09:10:28 +01:00
2017-11-06 11:06:40 +01:00
def r():
return random.random() * 0.3 + 0.7
2019-12-08 16:57:36 +01:00
clr = Quantity.Quantity_Color(r(), r(), r(), Quantity.Quantity_TOC_RGB)
ais.SetColor(clr)
2017-11-06 09:10:28 +01:00
2020-06-15 11:36:13 +02:00
ais_handle = ais.GetHandle() if USE_OCCT_HANDLE else ais
2016-04-17 15:27:05 +02:00
viewer_handle.Context.Display(ais_handle, False)
2017-11-06 09:10:28 +01:00
return ais_handle
2023-12-01 12:42:09 +01:00
def set_shape_transparency(ais, t, update_viewer=True):
handle.Context.SetTransparency(ais, t, update_viewer)
def get_bounding_box_center(bbox):
2020-11-01 20:08:27 +07:00
bbmin = [0.0] * 3
bbmax = [0.0] * 3
bbmin[0], bbmin[1], bbmin[2], bbmax[0], bbmax[1], bbmax[2] = bbox.Get()
2020-11-01 20:08:27 +07:00
return gp.gp_Pnt(*map(lambda xy: (xy[0] + xy[1]) / 2.0, zip(bbmin, bbmax)))
2017-11-06 09:10:28 +01:00
2016-06-22 15:03:18 +02:00
def serialize_shape(shape):
2019-12-08 16:57:36 +01:00
shapes = BRepTools.BRepTools_ShapeSet()
2023-05-01 20:16:39 +02:00
# @todo provide method to get ifcopenshell's built-in occt version to
# see whether this is necessary
shapes.SetFormatNb(2)
2016-06-22 15:03:18 +02:00
shapes.Add(shape)
2025-08-28 23:35:50 +02:00
# Check if WriteToString method exists and has the correct signature
# In PythonOCC >= 7.8.0, WriteToString signature changed and requires additional arguments
if hasattr(shapes, "WriteToString"):
try:
# Try to get the method signature
sig = inspect.signature(shapes.WriteToString)
# If WriteToString has no parameters (just self), use it
# This works for PythonOCC < 7.8.0
if len(sig.parameters) == 0:
return shapes.WriteToString()
except (ValueError, TypeError):
# If signature inspection fails, fall through to Write() method
pass
2025-08-28 23:35:50 +02:00
# Fall back to Write() method for newer PythonOCC versions (>= 7.8.0)
# or when WriteToString is not available/compatible
return shapes.Write()
2017-11-06 09:10:28 +01:00
2024-07-15 15:00:59 +05:00
def create_shape_from_serialization(
2025-02-18 13:37:53 +05:00
brep_object: Union[ifcopenshell_wrapper.SerializedElement, ifcopenshell_wrapper.Serialization],
2024-07-15 15:00:59 +05:00
) -> Union[shape_tuple, TopoDS.TopoDS_Shape]:
2021-02-13 16:03:57 +01:00
brep_data, occ_shape, styles, style_ids = None, None, (), ()
2017-11-06 09:10:28 +01:00
is_product_shape = True
2024-07-15 15:00:59 +05:00
if isinstance(brep_object, ifcopenshell_wrapper.SerializedElement):
brep_data = brep_object.geometry.brep_data
styles = brep_object.geometry.surface_styles
2021-02-13 16:03:57 +01:00
style_ids = brep_object.geometry.surface_style_ids
2024-07-15 15:00:59 +05:00
elif isinstance(brep_object, ifcopenshell_wrapper.Serialization):
2017-11-06 09:10:28 +01:00
try:
brep_data = brep_object.brep_data
styles = brep_object.surface_styles
2021-02-13 16:03:57 +01:00
style_ids = brep_object.surface_style_ids
is_product_shape = False
2024-07-15 15:00:59 +05:00
except BaseException as e:
print("Error occurred creating a shape:", e)
else:
assert_never(brep_object)
2017-11-06 09:10:28 +01:00
2020-11-01 20:08:27 +07:00
styles = tuple(styles[i : i + 4] for i in range(0, len(styles), 4))
2017-11-06 09:10:28 +01:00
if not brep_data:
2021-02-13 16:03:57 +01:00
return shape_tuple(brep_object, None, styles, style_ids)
2017-11-06 09:10:28 +01:00
try:
2024-07-15 15:00:36 +05:00
if OCC.VERSION < "7.8":
ss = BRepTools.BRepTools_ShapeSet()
ss.ReadFromString(brep_data)
occ_shape = ss.Shape(ss.NbShapes())
else:
ss = BRepTools.breptools()
occ_shape = ss.ReadFromString(brep_data)
except BaseException as e:
print("Error occurred parsing a shape from a string:", e)
2017-11-06 09:10:28 +01:00
if is_product_shape:
2021-02-13 16:03:57 +01:00
return shape_tuple(brep_object, occ_shape, styles, style_ids)
else:
return occ_shape