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

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

325 lines
11 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-04-17 15:27:05 +02:00
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
2016-04-17 15:27:05 +02:00
import os
import sys
import operator
2016-04-17 15:27:05 +02:00
from .. import ifcopenshell_wrapper
from ..file import file
2016-06-22 15:03:18 +02:00
from ..entity_instance import entity_instance
2016-04-17 15:27:05 +02:00
2019-12-09 11:52:30 +01:00
from . import has_occ
2017-11-06 09:10:28 +01:00
2017-11-06 11:06:40 +01:00
def wrap_shape_creation(settings, shape):
return shape
2017-11-06 09:10:28 +01:00
2016-04-17 15:27:05 +02:00
if has_occ:
from . import occ_utils as utils
2017-11-06 09:10:28 +01:00
2019-12-08 16:57:36 +01:00
try:
from OCC.Core import TopoDS
except ImportError:
2019-12-08 16:57:36 +01:00
from OCC import TopoDS
2019-07-19 15:55:22 +02:00
def wrap_shape_creation(settings, shape):
2020-11-01 20:08:27 +07:00
if getattr(settings, "use_python_opencascade", False):
2019-07-19 15:55:22 +02:00
return utils.create_shape_from_serialization(shape)
else:
return shape
2017-11-06 11:06:40 +01:00
2023-11-15 10:32:20 +01:00
class missing_setting:
def __repr__(self): return '-'
class settings_mixin:
"""
Pythonic interface mixin to the settings modules and
to provide an additional setting to enable pythonOCC
when available
"""
def __init__(self, **kwargs):
super(settings_mixin, self).__init__()
for k, v in kwargs.items():
self.set(getattr(self, k), v)
def __repr__(self):
def safe_get(x):
try: return self.get_(x)
except: return missing_setting()
fmt_pair = lambda x: "%s = %r" % (self.rname(x), safe_get(x))
return "%s(%s)" % (
type(self).__name__,
", ".join(map(fmt_pair, self.setting_names()))
)
@staticmethod
def name(k):
return k.lower().replace('_', '-')
@staticmethod
def rname(k):
return k.upper().replace('-', '_')
def set(self, k, v):
if k == "USE_PYTHON_OPENCASCADE":
if not has_occ:
raise ArgumentError("Python OpenCASCADE is not installed")
if v:
self.set_("iterator-output", ifcopenshell_wrapper.SERIALIZED)
self.set_("use-world-coords", True)
self.use_python_opencascade = True
else:
self.set_(self.name(k), v)
def get(self, k):
return self.get_(self.name(k))
2020-11-01 20:08:27 +07:00
2017-11-06 09:10:28 +01:00
2023-11-15 10:32:20 +01:00
class serializer_settings(settings_mixin, ifcopenshell_wrapper.SerializerSettings):
pass
2017-11-06 09:10:28 +01:00
2023-11-15 10:32:20 +01:00
class settings(settings_mixin, ifcopenshell_wrapper.Settings):
pass
2017-11-06 09:10:28 +01:00
2016-04-17 15:27:05 +02:00
# Make sure people are able to use python's platform agnostic paths
2021-07-11 15:03:49 +02:00
class iterator(ifcopenshell_wrapper.Iterator):
def __init__(self, settings, file_or_filename, num_threads=1, include=None, exclude=None, geometry_library="opencascade"):
2016-04-17 15:27:05 +02:00
self.settings = settings
if isinstance(file_or_filename, file):
file_or_filename = file_or_filename.wrapped_data
else:
file_or_filename = os.path.abspath(file_or_filename)
2020-11-01 20:08:27 +07:00
if include is not None and exclude is not None:
raise ValueError("include and exclude cannot be specified simultaneously")
2020-11-01 20:08:27 +07:00
if include is not None or exclude is not None:
# Couldn't get the typemaps properly applied using %extend so we
# replicate the SWIG-generated __init__ call on the output of a
# free function.
# @todo verify this works with SWIG 4
2020-11-01 20:08:27 +07:00
include_or_exclude = include if exclude is None else exclude
include_or_exclude_type = set(x.__class__.__name__ for x in include_or_exclude)
2020-11-01 20:08:27 +07:00
if include_or_exclude_type == {"entity_instance"}:
if not all(inst.is_a("IfcProduct") for inst in include_or_exclude):
raise ValueError("include and exclude need to be an aggregate of IfcProduct")
2020-11-01 20:08:27 +07:00
2021-09-14 11:36:19 +02:00
initializer = ifcopenshell_wrapper.construct_iterator_with_include_exclude_id
2020-11-01 20:08:27 +07:00
2021-09-14 11:36:19 +02:00
include_or_exclude = [i.id() for i in include_or_exclude]
else:
2021-07-11 15:03:49 +02:00
initializer = ifcopenshell_wrapper.construct_iterator_with_include_exclude
2020-11-01 20:08:27 +07:00
self.this = initializer(
geometry_library, self.settings, file_or_filename, include_or_exclude, include is not None, num_threads
2020-11-01 20:08:27 +07:00
)
else:
ifcopenshell_wrapper.Iterator.__init__(self, geometry_library, settings, file_or_filename, num_threads)
2017-11-06 11:06:40 +01:00
2016-04-17 15:27:05 +02:00
if has_occ:
2020-11-01 20:08:27 +07:00
2016-04-17 15:27:05 +02:00
def get(self):
2021-07-11 15:03:49 +02:00
return wrap_shape_creation(self.settings, ifcopenshell_wrapper.Iterator.get(self))
2020-11-01 20:08:27 +07:00
2018-01-01 11:22:12 +01:00
def __iter__(self):
if self.initialize():
while True:
yield self.get()
2020-11-01 20:08:27 +07:00
if not self.next():
break
2016-04-17 15:27:05 +02:00
2017-11-06 09:10:28 +01:00
2020-11-01 20:08:27 +07:00
class tree(ifcopenshell_wrapper.tree):
2021-03-26 17:51:35 +01:00
def __init__(self, file=None, settings=None):
args = [self]
2017-11-06 09:10:28 +01:00
if file is not None:
args.append(file.wrapped_data)
if settings is not None:
args.append(settings)
ifcopenshell_wrapper.tree.__init__(*args)
2017-11-06 09:10:28 +01:00
def add_file(self, file, settings):
ifcopenshell_wrapper.tree.add_file(self, file.wrapped_data, settings)
2021-04-24 15:23:36 +02:00
def add_iterator(self, iterator):
ifcopenshell_wrapper.tree.add_file(self, iterator)
2017-11-06 09:10:28 +01:00
def select(self, value, **kwargs):
def unwrap(value):
if isinstance(value, entity_instance):
return value.wrapped_data
elif all(map(lambda v: hasattr(value, v), "XYZ")):
return value.X(), value.Y(), value.Z()
return value
2017-11-06 11:06:40 +01:00
args = [self, unwrap(value)]
2021-11-07 19:49:37 +01:00
if isinstance(value, (entity_instance, ifcopenshell_wrapper.BRepElement)):
args.append(kwargs.get("completely_within", False))
2021-05-10 11:47:16 +02:00
if "extend" in kwargs:
2021-07-01 11:09:15 +02:00
args.append(kwargs["extend"])
elif isinstance(value, (list, tuple)) and len(value) == 3 and set(map(type, value)) == {float}:
if "extend" in kwargs:
2021-05-10 11:47:16 +02:00
args.append(kwargs["extend"])
elif has_occ:
2019-12-08 16:57:36 +01:00
if isinstance(value, TopoDS.TopoDS_Shape):
args[1] = utils.serialize_shape(value)
2021-05-10 11:47:16 +02:00
args.append(kwargs.get("completely_within", False))
if "extend" in kwargs:
args.append(kwargs["extend"])
return [entity_instance(e) for e in ifcopenshell_wrapper.tree.select(*args)]
2017-11-06 09:10:28 +01:00
def select_box(self, value, **kwargs):
def unwrap(value):
if isinstance(value, entity_instance):
return value.wrapped_data
elif hasattr(value, "Get"):
return value.Get()[:3], value.Get()[3:]
return value
2017-11-06 11:06:40 +01:00
args = [self, unwrap(value)]
if "extend" in kwargs or "completely_within" in kwargs:
args.append(kwargs.get("completely_within", False))
if "extend" in kwargs:
2020-11-01 20:08:27 +07:00
args.append(kwargs.get("extend", -1.0e-5))
2017-12-04 12:21:42 -08:00
return [entity_instance(e) for e in ifcopenshell_wrapper.tree.select_box(*args)]
2017-11-06 09:10:28 +01:00
def create_shape(settings, inst, repr=None):
2017-12-04 09:16:30 -08:00
"""
Return a geometric representation from STEP-based IFCREPRESENTATIONSHAPE
or
Return an OpenCASCADE BRep if settings.USE_PYTHON_OPENCASCADE == True
Note that in Python, you must store a reference to the element returned by this function to prevent garbage
collection when you access its children. See #1124.
2017-12-04 09:16:30 -08:00
example:
settings = ifcopenshell.geom.settings()
settings.set(settings.USE_PYTHON_OPENCASCADE, True)
ifc_file = ifcopenshell.open(file_path)
products = ifc_file.by_type("IfcProduct")
for i, product in enumerate(products):
if product.Representation is not None:
try:
created_shape = geom.create_shape(settings, inst=product)
shape = created_shape.geometry # see #1124
2017-12-04 09:16:30 -08:00
shape_gpXYZ = shape.Location().Transformation().TranslationPart() # These are methods of the TopoDS_Shape class from pythonOCC
print(shape_gpXYZ.X(), shape_gpXYZ.Y(), shape_gpXYZ.Z()) # These are methods of the gpXYZ class from pythonOCC
except:
print("Shape creation failed")
2017-12-04 09:16:30 -08:00
"""
2016-04-17 15:27:05 +02:00
return wrap_shape_creation(
settings,
2020-11-01 20:08:27 +07:00
ifcopenshell_wrapper.create_shape(settings, inst.wrapped_data, repr.wrapped_data if repr is not None else None),
)
2016-04-17 15:27:05 +02:00
def consume_iterator(it, with_progress=False):
2016-04-17 15:27:05 +02:00
if it.initialize():
while True:
if with_progress:
yield it.progress(), it.get()
else:
yield it.get()
2017-11-06 09:10:28 +01:00
if not it.next():
break
2022-02-27 12:56:16 +01:00
def iterate(settings, file_or_filename, num_threads=1, include=None, exclude=None, with_progress=False, cache=None):
2021-10-23 21:34:25 +02:00
it = iterator(settings, file_or_filename, num_threads, include, exclude)
2022-02-27 12:56:16 +01:00
if cache:
hdf5_cache = serializers.hdf5(cache, settings)
it.set_cache(hdf5_cache)
yield from consume_iterator(it, with_progress=with_progress)
2021-10-23 21:34:25 +02:00
2016-06-22 15:03:18 +02:00
def make_shape_function(fn):
2017-11-06 11:06:40 +01:00
def entity_instance_or_none(e):
return None if e is None else entity_instance(e)
2016-06-22 15:03:18 +02:00
if has_occ:
2020-11-01 20:08:27 +07:00
def _(schema, string_or_shape, *args):
2019-12-08 16:57:36 +01:00
if isinstance(string_or_shape, TopoDS.TopoDS_Shape):
2016-06-22 15:03:18 +02:00
string_or_shape = utils.serialize_shape(string_or_shape)
return entity_instance_or_none(fn(schema, string_or_shape, *args))
2020-11-01 20:08:27 +07:00
2016-06-22 15:03:18 +02:00
else:
2020-11-01 20:08:27 +07:00
def _(schema, string, *args):
return entity_instance_or_none(fn(schema, string, *args))
2020-11-01 20:08:27 +07:00
2016-06-22 15:03:18 +02:00
return _
2017-11-06 09:10:28 +01:00
2016-06-22 15:03:18 +02:00
serialise = make_shape_function(ifcopenshell_wrapper.serialise)
tesselate = make_shape_function(ifcopenshell_wrapper.tesselate)
2021-07-11 15:03:49 +02:00
2022-01-10 15:42:24 +11:00
2021-07-11 15:03:49 +02:00
def wrap_buffer_creation(fn):
2022-01-10 15:42:24 +11:00
"""
2021-07-11 15:03:49 +02:00
Python does not have automatic casts. The C++ serializers accept a stream_or_filename
which in C++ can be automatically constructed from a filename string. In Python we
have to implement this cast/construction explicitly.
"""
2022-01-10 15:42:24 +11:00
2021-07-11 15:03:49 +02:00
def transform_string(v):
if isinstance(v, str):
return ifcopenshell_wrapper.buffer(v)
else:
return v
2022-01-10 15:42:24 +11:00
2021-07-11 15:03:49 +02:00
def inner(*args):
return fn(*map(transform_string, args))
2022-01-10 15:42:24 +11:00
2021-07-11 15:03:49 +02:00
return inner
2022-02-26 14:52:58 +01:00
# Hdf- Xml- and glTF- serializers don't support writing to a buffer, only to filename
# so no wrap_buffer_creation() for these serializers
2021-10-29 10:39:32 +02:00
serializer_dict = {}
2022-01-10 15:42:24 +11:00
serializer_dict["obj"] = wrap_buffer_creation(ifcopenshell_wrapper.WaveFrontOBJSerializer)
serializer_dict["svg"] = wrap_buffer_creation(ifcopenshell_wrapper.SvgSerializer)
2022-02-26 14:52:58 +01:00
serializer_dict["xml"] = ifcopenshell_wrapper.XmlSerializer
2022-01-10 15:42:24 +11:00
serializer_dict["buffer"] = ifcopenshell_wrapper.buffer
2022-02-26 14:52:58 +01:00
# gltf and hdf5 availability depend on IfcOpenShell configuration settings
try:
serializer_dict["gltf"] = ifcopenshell_wrapper.GltfSerializer
except: pass
2021-10-29 10:39:32 +02:00
try:
2022-01-10 15:42:24 +11:00
serializer_dict["hdf5"] = ifcopenshell_wrapper.HdfSerializer
except:
pass
2021-10-29 10:39:32 +02:00
2022-01-10 15:42:24 +11:00
serializers = type("serializers", (), serializer_dict)