mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 10:28:37 +00:00
Clean branch
This commit is contained in:
@@ -1,78 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# #
|
|
||||||
# This file is part of IfcOpenShell. #
|
|
||||||
# #
|
|
||||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
|
||||||
# it under the terms of the Lesser GNU General Public License as published by #
|
|
||||||
# the Free Software Foundation, either version 3.0 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 #
|
|
||||||
# Lesser GNU General Public License for more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the Lesser GNU General Public License #
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
|
||||||
# #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if hasattr(os, 'uname'):
|
|
||||||
platform_system = os.uname()[0].lower()
|
|
||||||
else:
|
|
||||||
platform_system = 'windows'
|
|
||||||
|
|
||||||
if sys.maxsize == (1 << 31) - 1:
|
|
||||||
platform_architecture = '32bit'
|
|
||||||
else:
|
|
||||||
platform_architecture = '64bit'
|
|
||||||
|
|
||||||
python_version_tuple = tuple(sys.version.split(' ')[0].split('.'))
|
|
||||||
|
|
||||||
python_distribution = os.path.join(platform_system,
|
|
||||||
platform_architecture,
|
|
||||||
'python%s.%s' % python_version_tuple[:2])
|
|
||||||
sys.path.append(os.path.abspath(os.path.join(
|
|
||||||
os.path.dirname(__file__),
|
|
||||||
'lib', python_distribution)))
|
|
||||||
|
|
||||||
try:
|
|
||||||
from . import ifcopenshell_wrapper
|
|
||||||
except Exception as e:
|
|
||||||
if int(python_version_tuple[0]) == 2:
|
|
||||||
# Only for py2, as py3 has exception chaining
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
traceback.print_exc()
|
|
||||||
print('-' * 64)
|
|
||||||
raise ImportError("IfcOpenShell not built for '%s'" % python_distribution)
|
|
||||||
|
|
||||||
from . import guid
|
|
||||||
from .file import file
|
|
||||||
from .entity_instance import entity_instance
|
|
||||||
|
|
||||||
|
|
||||||
def open(fn):
|
|
||||||
f = ifcopenshell_wrapper.open(os.path.abspath(fn))
|
|
||||||
if f.good():
|
|
||||||
return file(f)
|
|
||||||
else:
|
|
||||||
raise IOError("Unable to open file for reading")
|
|
||||||
|
|
||||||
def create_entity(type, *args, **kwargs):
|
|
||||||
e = entity_instance(type)
|
|
||||||
attrs = list(enumerate(args)) + \
|
|
||||||
[(e.wrapped_data.get_argument_index(name), arg) for name, arg in kwargs.items()]
|
|
||||||
for idx, arg in attrs:
|
|
||||||
e[idx] = arg
|
|
||||||
return e
|
|
||||||
|
|
||||||
|
|
||||||
from .main import *
|
|
||||||
Binary file not shown.
@@ -1,254 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# #
|
|
||||||
# This file is part of IfcOpenShell. #
|
|
||||||
# #
|
|
||||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
|
||||||
# it under the terms of the Lesser GNU General Public License as published by #
|
|
||||||
# the Free Software Foundation, either version 3.0 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 #
|
|
||||||
# Lesser GNU General Public License for more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the Lesser GNU General Public License #
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
|
||||||
# #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import numbers
|
|
||||||
import itertools
|
|
||||||
|
|
||||||
from . import ifcopenshell_wrapper
|
|
||||||
|
|
||||||
try:
|
|
||||||
import logging
|
|
||||||
except ImportError as e:
|
|
||||||
logging = type('logger', (object,), {'exception': staticmethod(lambda s: print(s))})
|
|
||||||
|
|
||||||
|
|
||||||
class entity_instance(object):
|
|
||||||
"""This is the base Python class for all IFC objects.
|
|
||||||
|
|
||||||
An instantiated entity_instance will have methods of Python and the IFC class itself.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
ifc_file = ifcopenshell.open(file_path)
|
|
||||||
products = ifc_file.by_type("IfcProduct")
|
|
||||||
print(products[0].__class__)
|
|
||||||
>>> <class 'ifcopenshell.entity_instance.entity_instance'>
|
|
||||||
print(products[0].Representation)
|
|
||||||
>>> #423=IfcProductDefinitionShape($,$,(#409,#421))
|
|
||||||
"""
|
|
||||||
def __init__(self, e):
|
|
||||||
if isinstance(e, tuple):
|
|
||||||
e = ifcopenshell_wrapper.new_IfcBaseClass(*e)
|
|
||||||
super(entity_instance, self).__setattr__('wrapped_data', e)
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
INVALID, FORWARD, INVERSE = range(3)
|
|
||||||
attr_cat = self.wrapped_data.get_attribute_category(name)
|
|
||||||
if attr_cat == FORWARD:
|
|
||||||
return entity_instance.wrap_value(
|
|
||||||
self.wrapped_data.get_argument(self.wrapped_data.get_argument_index(name)))
|
|
||||||
elif attr_cat == INVERSE:
|
|
||||||
return entity_instance.wrap_value(self.wrapped_data.get_inverse(name))
|
|
||||||
else:
|
|
||||||
raise AttributeError(
|
|
||||||
"entity instance of type '%s' has no attribute '%s'" % (self.wrapped_data.is_a(), name))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def walk(f, g, value):
|
|
||||||
if isinstance(value, (tuple, list)):
|
|
||||||
return tuple(map(functools.partial(entity_instance.walk, f, g), value))
|
|
||||||
elif f(value):
|
|
||||||
return g(value)
|
|
||||||
else:
|
|
||||||
return value
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def wrap_value(v):
|
|
||||||
def wrap(e): return entity_instance(e)
|
|
||||||
|
|
||||||
def is_instance(e): return isinstance(e, ifcopenshell_wrapper.entity_instance)
|
|
||||||
|
|
||||||
return entity_instance.walk(is_instance, wrap, v)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def unwrap_value(v):
|
|
||||||
def unwrap(e): return e.wrapped_data
|
|
||||||
|
|
||||||
def is_instance(e): return isinstance(e, entity_instance)
|
|
||||||
|
|
||||||
return entity_instance.walk(is_instance, unwrap, v)
|
|
||||||
|
|
||||||
def attribute_type(self, attr):
|
|
||||||
"""Return the data type of a positional attribute of the element
|
|
||||||
|
|
||||||
:param attr: The index of the attribute
|
|
||||||
:type attr: int
|
|
||||||
:rtype: string
|
|
||||||
"""
|
|
||||||
attr_idx = attr if isinstance(attr, numbers.Integral) else self.wrapped_data.get_argument_index(attr)
|
|
||||||
return self.wrapped_data.get_argument_type(attr_idx)
|
|
||||||
|
|
||||||
def attribute_name(self, attr_idx):
|
|
||||||
"""Return the name of a positional attribute of the element
|
|
||||||
|
|
||||||
:param attr_idx: The index of the attribute
|
|
||||||
:type attr_idx: int
|
|
||||||
:rtype: string
|
|
||||||
"""
|
|
||||||
return self.wrapped_data.get_argument_name(attr_idx)
|
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
|
||||||
self[self.wrapped_data.get_argument_index(key)] = value
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
if key < 0 or key >= len(self):
|
|
||||||
raise IndexError("Attribute index {} out of range for instance of type {}".format(key, self.is_a()))
|
|
||||||
return entity_instance.wrap_value(self.wrapped_data.get_argument(key))
|
|
||||||
|
|
||||||
def __setitem__(self, idx, value):
|
|
||||||
attr_type = real_attr_type = self.attribute_type(idx).title().replace(' ', '')
|
|
||||||
real_attr_type = real_attr_type.replace('Derived', 'None')
|
|
||||||
attr_type = attr_type.replace('Binary', 'String')
|
|
||||||
attr_type = attr_type.replace('Enumeration', 'String')
|
|
||||||
|
|
||||||
if value is None:
|
|
||||||
if attr_type != "Derived":
|
|
||||||
self.wrapped_data.setArgumentAsNull(idx)
|
|
||||||
else:
|
|
||||||
valid = attr_type != "Derived"
|
|
||||||
if valid:
|
|
||||||
try:
|
|
||||||
if isinstance(value, unicode):
|
|
||||||
value = value.encode("utf-8")
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
if attr_type != "Derived":
|
|
||||||
getattr(self.wrapped_data, "setArgumentAs%s" % attr_type)(idx, entity_instance.unwrap_value(value))
|
|
||||||
except BaseException as e:
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
if not valid:
|
|
||||||
raise ValueError("Expected %s for attribute %s.%s, got %r" % (
|
|
||||||
real_attr_type, self.is_a(), self.attribute_name(idx), value))
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.wrapped_data)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return repr(self.wrapped_data)
|
|
||||||
|
|
||||||
def is_a(self, *args):
|
|
||||||
"""Return the IFC class name of an instance, or checks if an instance belongs to a class.
|
|
||||||
|
|
||||||
The check will also return true if a parent class name is provided.
|
|
||||||
|
|
||||||
:param args: If specified, is a case insensitive IFC class name to check
|
|
||||||
:type args: string
|
|
||||||
:returns: Either the name of the class, or a boolean if it passes the check
|
|
||||||
:rtype: string|bool
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
f = ifcopenshell.file()
|
|
||||||
f.create_entity('IfcPerson')
|
|
||||||
f.is_a()
|
|
||||||
>>> 'IfcPerson'
|
|
||||||
f.is_a('IfcPerson')
|
|
||||||
>>> True
|
|
||||||
"""
|
|
||||||
return self.wrapped_data.is_a(*args)
|
|
||||||
|
|
||||||
def id(self):
|
|
||||||
"""Return the STEP numerical identifier
|
|
||||||
|
|
||||||
:rtype: int
|
|
||||||
"""
|
|
||||||
return self.wrapped_data.id()
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(self, type(other)):
|
|
||||||
return False
|
|
||||||
return self.wrapped_data == other.wrapped_data
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.id(), self.wrapped_data.file_pointer()))
|
|
||||||
|
|
||||||
def __dir__(self):
|
|
||||||
return sorted(set(itertools.chain(
|
|
||||||
dir(type(self)),
|
|
||||||
map(str, self.wrapped_data.get_attribute_names()),
|
|
||||||
map(str, self.wrapped_data.get_inverse_attribute_names())
|
|
||||||
)))
|
|
||||||
|
|
||||||
def get_info(self, include_identifier=True, recursive=False, return_type=dict, ignore=()):
|
|
||||||
"""Return a dictionary of the entity_instance's properties (Python and IFC) and their values.
|
|
||||||
|
|
||||||
:param include_identifier: Whether or not to include the STEP numerical identifier
|
|
||||||
:type include_identifier: bool
|
|
||||||
:param recursive: Whether or not to convert referenced IFC elements into dictionaries too. All attributes also apply recursively
|
|
||||||
:type recursive: bool
|
|
||||||
:param return_type: The return data type to be casted into
|
|
||||||
:type return_type: dict|list|other
|
|
||||||
:param ignore: A list of attribute names to ignore
|
|
||||||
:type ignore: set|list
|
|
||||||
:returns: A dictionary of properties and their corresponding values
|
|
||||||
:rtype: dict
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
ifc_file = ifcopenshell.open(file_path)
|
|
||||||
products = ifc_file.by_type("IfcProduct")
|
|
||||||
obj_info = products[0].get_info()
|
|
||||||
print(obj_info.keys())
|
|
||||||
>>> dict_keys(['Description', 'Name', 'BuildingAddress', 'LongName', 'GlobalId', 'ObjectPlacement', 'OwnerHistory', 'ObjectType',
|
|
||||||
>>> ...'ElevationOfTerrain', 'CompositionType', 'id', 'Representation', 'type', 'ElevationOfRefHeight'])
|
|
||||||
"""
|
|
||||||
def _():
|
|
||||||
try:
|
|
||||||
if include_identifier:
|
|
||||||
yield "id", self.id()
|
|
||||||
yield "type", self.is_a()
|
|
||||||
except BaseException:
|
|
||||||
logging.exception("unhandled exception while getting id / type info on {}".format(self))
|
|
||||||
for i in range(len(self)):
|
|
||||||
try:
|
|
||||||
if self.wrapped_data.get_attribute_names()[i] in ignore:
|
|
||||||
continue
|
|
||||||
attr_value = self[i]
|
|
||||||
if recursive:
|
|
||||||
def is_instance(e): return isinstance(e, entity_instance)
|
|
||||||
|
|
||||||
def get_info_(inst):
|
|
||||||
# for ty in ignore:
|
|
||||||
# if inst.is_a(ty):
|
|
||||||
# return None
|
|
||||||
return entity_instance.get_info(inst,
|
|
||||||
include_identifier=include_identifier,
|
|
||||||
recursive=recursive,
|
|
||||||
return_type=return_type,
|
|
||||||
ignore=ignore
|
|
||||||
)
|
|
||||||
|
|
||||||
attr_value = entity_instance.walk(is_instance, get_info_, attr_value)
|
|
||||||
yield self.attribute_name(i), attr_value
|
|
||||||
except BaseException:
|
|
||||||
logging.exception("unhandled exception occurred setting attribute name for {}".format(self))
|
|
||||||
|
|
||||||
return return_type(_())
|
|
||||||
|
|
||||||
__dict__ = property(get_info)
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# #
|
|
||||||
# This file is part of IfcOpenShell. #
|
|
||||||
# #
|
|
||||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
|
||||||
# it under the terms of the Lesser GNU General Public License as published by #
|
|
||||||
# the Free Software Foundation, either version 3.0 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 #
|
|
||||||
# Lesser GNU General Public License for more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the Lesser GNU General Public License #
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
|
||||||
# #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import numbers
|
|
||||||
import functools
|
|
||||||
|
|
||||||
from . import ifcopenshell_wrapper
|
|
||||||
from .entity_instance import entity_instance
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Python 2
|
|
||||||
basestring
|
|
||||||
except NameError:
|
|
||||||
# Python 3 or newer
|
|
||||||
basestring = (str, bytes)
|
|
||||||
|
|
||||||
|
|
||||||
class file(object):
|
|
||||||
"""Base class for containing IFC files.
|
|
||||||
|
|
||||||
Class has instance methods for filtering by element Id, Type, etc.
|
|
||||||
Instantiated objects can be subscripted by Id or Guid
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
ifc_file = ifcopenshell.open(file_path)
|
|
||||||
products = ifc_file.by_type("IfcProduct")
|
|
||||||
print(products[0].id(), products[0].GlobalId)
|
|
||||||
>>> 122 2XQ$n5SLP5MBLyL442paFx
|
|
||||||
# Subscripting
|
|
||||||
print(products[0] == ifc_file[122] == ifc_file['2XQ$n5SLP5MBLyL442paFx'])
|
|
||||||
>>> True
|
|
||||||
"""
|
|
||||||
def __init__(self, f=None, schema=None):
|
|
||||||
if f is not None:
|
|
||||||
self.wrapped_data = f
|
|
||||||
else:
|
|
||||||
args = filter(None, [schema])
|
|
||||||
args = map(ifcopenshell_wrapper.schema_by_name, args)
|
|
||||||
self.wrapped_data = ifcopenshell_wrapper.file(*args)
|
|
||||||
|
|
||||||
def create_entity(self, type, *args, **kwargs):
|
|
||||||
"""Create a new IFC entity in the file.
|
|
||||||
|
|
||||||
:param type: Case insensitive name of the IFC class
|
|
||||||
:type type: string
|
|
||||||
:param args: The positional arguments of the IFC class
|
|
||||||
:param kwargs: The keyword arguments of the IFC class
|
|
||||||
:returns: An entity instance
|
|
||||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
f = ifcopenshell.file()
|
|
||||||
f.create_entity('IfcPerson')
|
|
||||||
>>> #1=IfcPerson($,$,$,$,$,$,$,$)
|
|
||||||
f.create_entity('IfcPerson', 'Foobar')
|
|
||||||
>>> #2=IfcPerson('Foobar',$,$,$,$,$,$,$)
|
|
||||||
f.create_entity('IfcPerson', Identification='Foobar')
|
|
||||||
>>> #3=IfcPerson('Foobar',$,$,$,$,$,$,$)
|
|
||||||
"""
|
|
||||||
e = entity_instance((self.schema, type))
|
|
||||||
self.wrapped_data.add(e.wrapped_data)
|
|
||||||
e.wrapped_data.this.disown()
|
|
||||||
attrs = list(enumerate(args)) + \
|
|
||||||
[(e.wrapped_data.get_argument_index(name), arg) for name, arg in kwargs.items()]
|
|
||||||
for idx, arg in attrs:
|
|
||||||
e[idx] = arg
|
|
||||||
return e
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
if attr[0:6] == 'create':
|
|
||||||
return functools.partial(self.create_entity, attr[6:])
|
|
||||||
else:
|
|
||||||
return getattr(self.wrapped_data, attr)
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
if isinstance(key, numbers.Integral):
|
|
||||||
return entity_instance(self.wrapped_data.by_id(key))
|
|
||||||
elif isinstance(key, basestring):
|
|
||||||
return entity_instance(self.wrapped_data.by_guid(str(key)))
|
|
||||||
|
|
||||||
def by_id(self, id):
|
|
||||||
"""Return an IFC entity instance filtered by IFC ID.
|
|
||||||
|
|
||||||
:param id: STEP numerical identifier
|
|
||||||
:type id: int
|
|
||||||
:returns: An ifcopenshell.entity_instance.entity_instance
|
|
||||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
|
||||||
"""
|
|
||||||
return self[id]
|
|
||||||
|
|
||||||
def by_guid(self, guid):
|
|
||||||
"""Return an IFC entity instance filtered by IFC GUID.
|
|
||||||
|
|
||||||
:param guid: GlobalId value in 22-character encoded form
|
|
||||||
:type guid: string
|
|
||||||
:returns: An ifcopenshell.entity_instance.entity_instance
|
|
||||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
|
||||||
"""
|
|
||||||
return self[guid]
|
|
||||||
|
|
||||||
def add(self, inst):
|
|
||||||
"""Adds an entity including any dependent entities to an IFC file.
|
|
||||||
|
|
||||||
If the entity already exists, it is not re-added."""
|
|
||||||
inst.wrapped_data.this.disown()
|
|
||||||
return entity_instance(self.wrapped_data.add(inst.wrapped_data))
|
|
||||||
|
|
||||||
def by_type(self, type):
|
|
||||||
"""Return IFC objects filtered by IFC Type and wrapped with the entity_instance class.
|
|
||||||
|
|
||||||
If an IFC type class has subclasses, all entities of those subclasses are also returned.
|
|
||||||
|
|
||||||
:param type: The case insensitive type of IFC class to return.
|
|
||||||
:type type: string
|
|
||||||
:returns: A list of ifcopenshell.entity_instance.entity_instance objects
|
|
||||||
:rtype: list
|
|
||||||
"""
|
|
||||||
return [entity_instance(e) for e in self.wrapped_data.by_type(type)]
|
|
||||||
|
|
||||||
def traverse(self, inst, max_levels=None):
|
|
||||||
"""Get a list of all referenced instances for a particular instance including itself
|
|
||||||
|
|
||||||
:param inst: The entity instance to get all sub instances
|
|
||||||
:type inst: ifcopenshell.entity_instance.entity_instance
|
|
||||||
:param max_levels: How far deep to recursively fetch sub instances. None or -1 means infinite.
|
|
||||||
:type max_levels: None|int
|
|
||||||
:returns: A list of ifcopenshell.entity_instance.entity_instance objects
|
|
||||||
:rtype: list
|
|
||||||
"""
|
|
||||||
if max_levels is None:
|
|
||||||
max_levels = -1
|
|
||||||
return [entity_instance(e) for e in self.wrapped_data.traverse(inst.wrapped_data, max_levels)]
|
|
||||||
|
|
||||||
def get_inverse(self, inst):
|
|
||||||
"""Return a list of entities that reference this entity
|
|
||||||
|
|
||||||
:param inst: The entity instance to get inverse relationships
|
|
||||||
:type inst: ifcopenshell.entity_instance.entity_instance
|
|
||||||
:returns: A list of ifcopenshell.entity_instance.entity_instance objects
|
|
||||||
:rtype: list
|
|
||||||
"""
|
|
||||||
return [entity_instance(e) for e in self.wrapped_data.get_inverse(inst.wrapped_data)]
|
|
||||||
|
|
||||||
def remove(self, inst):
|
|
||||||
"""Deletes an IFC object in the file.
|
|
||||||
|
|
||||||
Attribute values in other entity instances that reference the deleted
|
|
||||||
object will be set to null. In the case of a list or set of references,
|
|
||||||
the reference to the deleted will be removed from the aggregate.
|
|
||||||
|
|
||||||
:param inst: The entity instance to delete
|
|
||||||
:type inst: ifcopenshell.entity_instance.entity_instance
|
|
||||||
:rtype: None
|
|
||||||
"""
|
|
||||||
return self.wrapped_data.remove(inst.wrapped_data)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self[id] for id in self.wrapped_data.entity_names())
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def from_string(s):
|
|
||||||
return file(ifcopenshell_wrapper.read(s))
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# #
|
|
||||||
# This file is part of IfcOpenShell. #
|
|
||||||
# #
|
|
||||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
|
||||||
# it under the terms of the Lesser GNU General Public License as published by #
|
|
||||||
# the Free Software Foundation, either version 3.0 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 #
|
|
||||||
# Lesser GNU General Public License for more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the Lesser GNU General Public License #
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
|
||||||
# #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
def _has_occ():
|
|
||||||
try:
|
|
||||||
import OCC.Core.BRepTools
|
|
||||||
return True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
import OCC.BRepTools
|
|
||||||
return True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
has_occ = _has_occ()
|
|
||||||
|
|
||||||
if has_occ:
|
|
||||||
from . import occ_utils as utils
|
|
||||||
|
|
||||||
from .main import *
|
|
||||||
@@ -1,647 +0,0 @@
|
|||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import operator
|
|
||||||
import functools
|
|
||||||
import multiprocessing
|
|
||||||
|
|
||||||
import OCC.AIS
|
|
||||||
|
|
||||||
from collections import defaultdict, Iterable, OrderedDict
|
|
||||||
|
|
||||||
try:
|
|
||||||
QString = unicode
|
|
||||||
except NameError:
|
|
||||||
# Python 3
|
|
||||||
QString = str
|
|
||||||
|
|
||||||
os.environ['QT_API'] = 'pyqt5'
|
|
||||||
try:
|
|
||||||
from pyqode.qt import QtCore
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
||||||
|
|
||||||
from .code_editor_pane import code_edit
|
|
||||||
|
|
||||||
try:
|
|
||||||
from OCC.Display.pyqt5Display import qtViewer3d
|
|
||||||
except BaseException:
|
|
||||||
import OCC.Display
|
|
||||||
|
|
||||||
try:
|
|
||||||
import OCC.Display.backend
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
OCC.Display.backend.get_backend("qt-pyqt5")
|
|
||||||
except BaseException:
|
|
||||||
OCC.Display.backend.load_backend("qt-pyqt5")
|
|
||||||
|
|
||||||
from OCC.Display.qtDisplay import qtViewer3d
|
|
||||||
|
|
||||||
from .main import settings, iterator
|
|
||||||
from .occ_utils import display_shape
|
|
||||||
|
|
||||||
from .. import open as open_ifc_file
|
|
||||||
from .. import version as ifcopenshell_version
|
|
||||||
|
|
||||||
if ifcopenshell_version < "0.6":
|
|
||||||
# not yet ported
|
|
||||||
from .. import get_supertype
|
|
||||||
|
|
||||||
class geometry_creation_signals(QtCore.QObject):
|
|
||||||
completed = QtCore.pyqtSignal('PyQt_PyObject')
|
|
||||||
progress = QtCore.pyqtSignal('PyQt_PyObject')
|
|
||||||
|
|
||||||
class geometry_creation_thread(QtCore.QThread):
|
|
||||||
def __init__(self, signals, settings, f):
|
|
||||||
QtCore.QThread.__init__(self)
|
|
||||||
self.signals = signals
|
|
||||||
self.settings = settings
|
|
||||||
self.f = f
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
t0 = time.time()
|
|
||||||
|
|
||||||
# detect concurrency from hardware, we need to have
|
|
||||||
# at least two threads because otherwise the interface
|
|
||||||
# is different
|
|
||||||
# is different
|
|
||||||
it = iterator(self.settings, self.f, max(2, multiprocessing.cpu_count()))
|
|
||||||
if not it.initialize():
|
|
||||||
self.signals.completed.emit([])
|
|
||||||
return
|
|
||||||
|
|
||||||
def _():
|
|
||||||
|
|
||||||
old_progress = -1
|
|
||||||
while True:
|
|
||||||
shape = it.get()
|
|
||||||
|
|
||||||
if shape:
|
|
||||||
yield shape
|
|
||||||
|
|
||||||
if not it.next():
|
|
||||||
break
|
|
||||||
|
|
||||||
self.signals.completed.emit((it, self.f, list(_())))
|
|
||||||
|
|
||||||
class configuration(object):
|
|
||||||
def __init__(self):
|
|
||||||
try:
|
|
||||||
import ConfigParser
|
|
||||||
Cfg = ConfigParser.RawConfigParser
|
|
||||||
except BaseException:
|
|
||||||
import configparser
|
|
||||||
|
|
||||||
def Cfg():
|
|
||||||
return configparser.ConfigParser(interpolation=None)
|
|
||||||
|
|
||||||
conf_file = os.path.expanduser(os.path.join("~", ".ifcopenshell", "app", "snippets.conf"))
|
|
||||||
if conf_file.startswith("~"):
|
|
||||||
conf_file = None
|
|
||||||
return
|
|
||||||
|
|
||||||
self.config_encode = lambda s: s.replace("\\", "\\\\").replace("\n", "\n|")
|
|
||||||
self.config_decode = lambda s: s.replace("\n|", "\n").replace("\\\\", "\\")
|
|
||||||
|
|
||||||
if not os.path.exists(os.path.dirname(conf_file)):
|
|
||||||
os.makedirs(os.path.dirname(conf_file))
|
|
||||||
|
|
||||||
if not os.path.exists(conf_file):
|
|
||||||
config = Cfg()
|
|
||||||
config.add_section("snippets")
|
|
||||||
config.set("snippets", "print all wall ids", self.config_encode("""
|
|
||||||
###########################################################################
|
|
||||||
# A simple script that iterates over all walls in the current model #
|
|
||||||
# and prints their Globally unique IDs (GUIDS) to the console window #
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
for wall in model.by_type("IfcWall"):
|
|
||||||
print ("wall with global id: "+str(wall.GlobalId))
|
|
||||||
""".lstrip()))
|
|
||||||
|
|
||||||
config.set("snippets", "print properties of current selection", self.config_encode("""
|
|
||||||
###########################################################################
|
|
||||||
# A simple script that iterates over all IfcPropertySets of the currently #
|
|
||||||
# selected object and prints them to the console #
|
|
||||||
###########################################################################
|
|
||||||
|
|
||||||
# check if something is selected
|
|
||||||
if selection:
|
|
||||||
#get the IfcProduct that is stored in the global variable 'selection'
|
|
||||||
obj = selection
|
|
||||||
for relDefinesByProperties in obj.IsDefinedBy:
|
|
||||||
print("[{0}]".format(relDefinesByProperties.RelatingPropertyDefinition.Name))
|
|
||||||
for prop in relDefinesByProperties.RelatingPropertyDefinition.HasProperties:
|
|
||||||
print ("{:<20} :{}".format(prop.Name,prop.NominalValue.wrappedValue))
|
|
||||||
print ("\\n")
|
|
||||||
""".lstrip()))
|
|
||||||
with open(conf_file, 'w') as configfile:
|
|
||||||
config.write(configfile)
|
|
||||||
|
|
||||||
self.config = Cfg()
|
|
||||||
self.config.read(conf_file)
|
|
||||||
|
|
||||||
def options(self, s):
|
|
||||||
return OrderedDict([(k, self.config_decode(self.config.get(s, k))) for k in self.config.options(s)])
|
|
||||||
|
|
||||||
|
|
||||||
class application(QtWidgets.QApplication):
|
|
||||||
"""A pythonOCC, PyQt based IfcOpenShell application
|
|
||||||
with two tree views and a graphical 3d view"""
|
|
||||||
|
|
||||||
class abstract_treeview(QtWidgets.QTreeWidget):
|
|
||||||
|
|
||||||
"""Base class for the two treeview controls"""
|
|
||||||
|
|
||||||
instanceSelected = QtCore.pyqtSignal([object])
|
|
||||||
instanceVisibilityChanged = QtCore.pyqtSignal([object, int])
|
|
||||||
instanceDisplayModeChanged = QtCore.pyqtSignal([object, int])
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
QtWidgets.QTreeView.__init__(self)
|
|
||||||
self.setColumnCount(len(self.ATTRIBUTES))
|
|
||||||
self.setHeaderLabels(self.ATTRIBUTES)
|
|
||||||
self.children = defaultdict(list)
|
|
||||||
|
|
||||||
def get_children(self, inst):
|
|
||||||
c = [inst]
|
|
||||||
i = 0
|
|
||||||
while i < len(c):
|
|
||||||
c.extend(self.children[c[i]])
|
|
||||||
i += 1
|
|
||||||
return c
|
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
|
||||||
menu = QtWidgets.QMenu(self)
|
|
||||||
visibility = [menu.addAction("Show"), menu.addAction("Hide")]
|
|
||||||
displaymode = [menu.addAction("Solid"), menu.addAction("Wireframe")]
|
|
||||||
action = menu.exec_(self.mapToGlobal(event.pos()))
|
|
||||||
index = self.selectionModel().currentIndex()
|
|
||||||
inst = index.data(QtCore.Qt.UserRole)
|
|
||||||
if hasattr(inst, 'toPyObject'):
|
|
||||||
inst = inst
|
|
||||||
if action in visibility:
|
|
||||||
self.instanceVisibilityChanged.emit(inst, visibility.index(action))
|
|
||||||
elif action in displaymode:
|
|
||||||
self.instanceDisplayModeChanged.emit(inst, displaymode.index(action))
|
|
||||||
|
|
||||||
def clicked_(self, index):
|
|
||||||
inst = index.data(QtCore.Qt.UserRole)
|
|
||||||
if hasattr(inst, 'toPyObject'):
|
|
||||||
inst = inst
|
|
||||||
if inst:
|
|
||||||
self.instanceSelected.emit(inst)
|
|
||||||
|
|
||||||
def select(self, product):
|
|
||||||
itm = self.product_to_item.get(product)
|
|
||||||
if itm is None:
|
|
||||||
return
|
|
||||||
self.selectionModel().setCurrentIndex(itm,
|
|
||||||
QtCore.QItemSelectionModel.SelectCurrent | QtCore.QItemSelectionModel.Rows)
|
|
||||||
|
|
||||||
class decomposition_treeview(abstract_treeview):
|
|
||||||
|
|
||||||
"""Treeview with typical IFC decomposition relationships"""
|
|
||||||
|
|
||||||
ATTRIBUTES = ['Entity', 'GlobalId', 'Name']
|
|
||||||
|
|
||||||
def parent(self, instance):
|
|
||||||
if instance.is_a("IfcOpeningElement"):
|
|
||||||
return instance.VoidsElements[0].RelatingBuildingElement
|
|
||||||
if instance.is_a("IfcElement"):
|
|
||||||
fills = instance.FillsVoids
|
|
||||||
if len(fills):
|
|
||||||
return fills[0].RelatingOpeningElement
|
|
||||||
containments = instance.ContainedInStructure
|
|
||||||
if len(containments):
|
|
||||||
return containments[0].RelatingStructure
|
|
||||||
if instance.is_a("IfcObjectDefinition"):
|
|
||||||
decompositions = instance.Decomposes
|
|
||||||
if len(decompositions):
|
|
||||||
return decompositions[0].RelatingObject
|
|
||||||
|
|
||||||
def load_file(self, f, **kwargs):
|
|
||||||
products = list(f.by_type("IfcProduct")) + list(f.by_type("IfcProject"))
|
|
||||||
parents = list(map(self.parent, products))
|
|
||||||
items = {}
|
|
||||||
skipped = 0
|
|
||||||
ATTRS = self.ATTRIBUTES
|
|
||||||
while len(items) + skipped < len(products):
|
|
||||||
for product, parent in zip(products, parents):
|
|
||||||
if parent is None and not product.is_a("IfcProject"):
|
|
||||||
skipped += 1
|
|
||||||
continue
|
|
||||||
if (parent is None or parent in items) and product not in items:
|
|
||||||
sl = []
|
|
||||||
for attr in ATTRS:
|
|
||||||
if attr == 'Entity':
|
|
||||||
sl.append(product.is_a())
|
|
||||||
else:
|
|
||||||
sl.append(getattr(product, attr) or '')
|
|
||||||
itm = items[product] = QtWidgets.QTreeWidgetItem(items.get(parent, self), sl)
|
|
||||||
itm.setData(0, QtCore.Qt.UserRole, product)
|
|
||||||
self.children[parent].append(product)
|
|
||||||
self.product_to_item = dict(zip(items.keys(), map(self.indexFromItem, items.values())))
|
|
||||||
self.clicked.connect(self.clicked_)
|
|
||||||
self.expandAll()
|
|
||||||
|
|
||||||
class type_treeview(abstract_treeview):
|
|
||||||
|
|
||||||
"""Treeview with typical IFC decomposition relationships"""
|
|
||||||
|
|
||||||
ATTRIBUTES = ['Name']
|
|
||||||
|
|
||||||
def load_file(self, f, **kwargs):
|
|
||||||
products = list(f.by_type("IfcProduct"))
|
|
||||||
types = set(map(lambda i: i.is_a(), products))
|
|
||||||
items = {}
|
|
||||||
for t in types:
|
|
||||||
def add(t):
|
|
||||||
s = get_supertype(t)
|
|
||||||
if s:
|
|
||||||
add(s)
|
|
||||||
s2, t2 = map(QString, (s, t))
|
|
||||||
if t2 not in items:
|
|
||||||
itm = items[t2] = QtWidgets.QTreeWidgetItem(items.get(s2, self), [t2])
|
|
||||||
itm.setData(0, QtCore.Qt.UserRole, t2)
|
|
||||||
self.children[s2].append(t2)
|
|
||||||
|
|
||||||
if ifcopenshell_version < "0.6":
|
|
||||||
add(t)
|
|
||||||
|
|
||||||
for p in products:
|
|
||||||
t = QString(p.is_a())
|
|
||||||
itm = items[p] = QtWidgets.QTreeWidgetItem(items.get(t, self), [p.Name or '<no name>'])
|
|
||||||
itm.setData(0, QtCore.Qt.UserRole, t)
|
|
||||||
self.children[t].append(p)
|
|
||||||
|
|
||||||
self.product_to_item = dict(zip(items.keys(), map(self.indexFromItem, items.values())))
|
|
||||||
self.clicked.connect(self.clicked)
|
|
||||||
self.expandAll()
|
|
||||||
|
|
||||||
class property_table(QtWidgets.QWidget):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
QtWidgets.QWidget.__init__(self)
|
|
||||||
self.layout = QtWidgets.QVBoxLayout(self)
|
|
||||||
self.setLayout(self.layout)
|
|
||||||
self.scroll = QtWidgets.QScrollArea(self)
|
|
||||||
self.layout.addWidget(self.scroll)
|
|
||||||
self.scroll.setWidgetResizable(True)
|
|
||||||
self.scrollContent = QtWidgets.QWidget(self.scroll)
|
|
||||||
self.scrollLayout = QtWidgets.QVBoxLayout(self.scrollContent)
|
|
||||||
self.scrollContent.setLayout(self.scrollLayout)
|
|
||||||
self.scroll.setWidget(self.scrollContent)
|
|
||||||
self.prop_dict = {}
|
|
||||||
|
|
||||||
# triggered by selection event in either component of parent
|
|
||||||
def select(self, product):
|
|
||||||
|
|
||||||
# Clear the old contents if any
|
|
||||||
while self.scrollLayout.count():
|
|
||||||
child = self.scrollLayout.takeAt(0)
|
|
||||||
if child is not None:
|
|
||||||
if child.widget() is not None:
|
|
||||||
child.widget().deleteLater()
|
|
||||||
|
|
||||||
self.scroll = QtWidgets.QScrollArea()
|
|
||||||
self.scroll.setWidgetResizable(True)
|
|
||||||
|
|
||||||
prop_sets = self.prop_dict.get(str(product))
|
|
||||||
|
|
||||||
if prop_sets is not None:
|
|
||||||
for k, v in prop_sets:
|
|
||||||
group_box = QtWidgets.QGroupBox()
|
|
||||||
|
|
||||||
group_box.setTitle(k)
|
|
||||||
group_layout = QtWidgets.QVBoxLayout()
|
|
||||||
group_box.setLayout(group_layout)
|
|
||||||
|
|
||||||
for name, value in v.items():
|
|
||||||
prop_name = str(name)
|
|
||||||
|
|
||||||
value_str = value
|
|
||||||
if hasattr(value_str, "wrappedValue"):
|
|
||||||
value_str = value_str.wrappedValue
|
|
||||||
|
|
||||||
if isinstance(value_str, unicode):
|
|
||||||
value_str = value_str.encode('utf-8')
|
|
||||||
else:
|
|
||||||
value_str = str(value_str)
|
|
||||||
|
|
||||||
if hasattr(value, "is_a"):
|
|
||||||
type_str = " <i>(%s)</i>" % value.is_a()
|
|
||||||
else:
|
|
||||||
type_str = ""
|
|
||||||
label = QtWidgets.QLabel("<b>%s</b>: %s%s" % (prop_name, value_str, type_str))
|
|
||||||
group_layout.addWidget(label)
|
|
||||||
|
|
||||||
group_layout.addStretch()
|
|
||||||
self.scrollLayout.addWidget(group_box)
|
|
||||||
|
|
||||||
self.scrollLayout.addStretch()
|
|
||||||
else:
|
|
||||||
label = QtWidgets.QLabel("No IfcPropertySets asscociated with selected entity instance")
|
|
||||||
self.scrollLayout.addWidget(label)
|
|
||||||
|
|
||||||
def load_file(self, f, **kwargs):
|
|
||||||
for p in f.by_type("IfcProduct"):
|
|
||||||
propsets = []
|
|
||||||
|
|
||||||
def process_pset(prop_def):
|
|
||||||
if prop_def is not None:
|
|
||||||
prop_set_name = prop_def.Name
|
|
||||||
props = {}
|
|
||||||
if prop_def.is_a("IfcElementQuantity"):
|
|
||||||
for q in prop_def.Quantities:
|
|
||||||
if q.is_a("IfcPhysicalSimpleQuantity"):
|
|
||||||
props[q.Name] = q[3]
|
|
||||||
elif prop_def.is_a("IfcPropertySet"):
|
|
||||||
for prop in prop_def.HasProperties:
|
|
||||||
if prop.is_a("IfcPropertySingleValue"):
|
|
||||||
props[prop.Name] = prop.NominalValue
|
|
||||||
else:
|
|
||||||
# Entity introduced in IFC4
|
|
||||||
# prop_def.is_a("IfcPreDefinedPropertySet"):
|
|
||||||
for prop in range(4, len(prop_def)):
|
|
||||||
props[prop_def.attribute_name(prop)] = prop_def[prop]
|
|
||||||
return prop_set_name, props
|
|
||||||
|
|
||||||
try:
|
|
||||||
for is_def_by in p.IsDefinedBy:
|
|
||||||
if is_def_by.is_a("IfcRelDefinesByProperties"):
|
|
||||||
propsets.append(process_pset(is_def_by.RelatingPropertyDefinition))
|
|
||||||
elif is_def_by.is_a("IfcRelDefinesByType"):
|
|
||||||
type_psets = is_def_by.RelatingType.HasPropertySets
|
|
||||||
if type_psets is None:
|
|
||||||
continue
|
|
||||||
for propset in type_psets:
|
|
||||||
propsets.append(process_pset(propset))
|
|
||||||
except Exception as e:
|
|
||||||
import traceback
|
|
||||||
print("failed to load properties: {}".format(e))
|
|
||||||
traceback.print_exc()
|
|
||||||
|
|
||||||
if len(propsets):
|
|
||||||
self.prop_dict[str(p)] = propsets
|
|
||||||
|
|
||||||
print("property set dictionary has {} entries".format(len(self.prop_dict)))
|
|
||||||
|
|
||||||
class viewer(qtViewer3d):
|
|
||||||
|
|
||||||
instanceSelected = QtCore.pyqtSignal([object])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def ais_to_key(ais_handle):
|
|
||||||
def yield_shapes():
|
|
||||||
ais = ais_handle.GetObject()
|
|
||||||
if hasattr(ais, 'Shape'):
|
|
||||||
yield ais.Shape()
|
|
||||||
return
|
|
||||||
shp = OCC.AIS.Handle_AIS_Shape.DownCast(ais_handle)
|
|
||||||
if not shp.IsNull():
|
|
||||||
yield shp.Shape()
|
|
||||||
return
|
|
||||||
mult = ais_handle
|
|
||||||
if mult.IsNull():
|
|
||||||
shp = OCC.AIS.Handle_AIS_Shape.DownCast(ais_handle)
|
|
||||||
if not shp.IsNull():
|
|
||||||
yield shp
|
|
||||||
else:
|
|
||||||
li = mult.GetObject().ConnectedTo()
|
|
||||||
for i in range(li.Length()):
|
|
||||||
shp = OCC.AIS.Handle_AIS_Shape.DownCast(li.Value(i + 1))
|
|
||||||
if not shp.IsNull():
|
|
||||||
yield shp
|
|
||||||
|
|
||||||
return tuple(shp.HashCode(1 << 24) for shp in yield_shapes())
|
|
||||||
|
|
||||||
def __init__(self, widget):
|
|
||||||
qtViewer3d.__init__(self, widget)
|
|
||||||
self.ais_to_product = {}
|
|
||||||
self.product_to_ais = {}
|
|
||||||
self.counter = 0
|
|
||||||
self.window = widget
|
|
||||||
self.thread = None
|
|
||||||
|
|
||||||
def initialize(self):
|
|
||||||
self.InitDriver()
|
|
||||||
self._display.Select = self.HandleSelection
|
|
||||||
|
|
||||||
def finished(self, file_shapes):
|
|
||||||
it, f, shapes = file_shapes
|
|
||||||
v = self._display
|
|
||||||
|
|
||||||
t = {0: time.time()}
|
|
||||||
|
|
||||||
def update(dt=None):
|
|
||||||
t1 = time.time()
|
|
||||||
if dt is None or t1 - t[0] > dt:
|
|
||||||
v.FitAll()
|
|
||||||
v.Repaint()
|
|
||||||
t[0] = t1
|
|
||||||
|
|
||||||
for shape in shapes:
|
|
||||||
ais = display_shape(shape, viewer_handle=v)
|
|
||||||
product = f[shape.data.id]
|
|
||||||
|
|
||||||
ais.GetObject().SetSelectionPriority(self.counter)
|
|
||||||
self.ais_to_product[self.counter] = product
|
|
||||||
self.product_to_ais[product] = ais
|
|
||||||
self.counter += 1
|
|
||||||
|
|
||||||
QtWidgets.QApplication.processEvents()
|
|
||||||
|
|
||||||
if product.is_a() in {'IfcSpace', 'IfcOpeningElement'}:
|
|
||||||
v.Context.Erase(ais, True)
|
|
||||||
|
|
||||||
update(1.)
|
|
||||||
|
|
||||||
update()
|
|
||||||
|
|
||||||
self.thread = None
|
|
||||||
|
|
||||||
def load_file(self, f, setting=None):
|
|
||||||
|
|
||||||
if self.thread is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if setting is None:
|
|
||||||
setting = settings()
|
|
||||||
setting.set(setting.INCLUDE_CURVES, True)
|
|
||||||
setting.set(setting.USE_PYTHON_OPENCASCADE, True)
|
|
||||||
|
|
||||||
self.signals = geometry_creation_signals()
|
|
||||||
thread = self.thread = geometry_creation_thread(self.signals, setting, f)
|
|
||||||
self.window.window_closed.connect(lambda *args: thread.terminate())
|
|
||||||
self.signals.completed.connect(self.finished)
|
|
||||||
self.thread.start()
|
|
||||||
|
|
||||||
def select(self, product):
|
|
||||||
ais = self.product_to_ais.get(product)
|
|
||||||
if ais is None:
|
|
||||||
return
|
|
||||||
v = self._display.Context
|
|
||||||
v.ClearSelected(False)
|
|
||||||
v.SetSelected(ais, True)
|
|
||||||
|
|
||||||
def toggle(self, product_or_products, fn):
|
|
||||||
if not isinstance(product_or_products, Iterable):
|
|
||||||
product_or_products = [product_or_products]
|
|
||||||
aiss = list(filter(None, map(self.product_to_ais.get, product_or_products)))
|
|
||||||
last = len(aiss) - 1
|
|
||||||
for i, ais in enumerate(aiss):
|
|
||||||
fn(ais, i == last)
|
|
||||||
|
|
||||||
def toggle_visibility(self, product_or_products, flag):
|
|
||||||
v = self._display.Context
|
|
||||||
if flag:
|
|
||||||
def visibility(ais, last):
|
|
||||||
v.Erase(ais, last)
|
|
||||||
else:
|
|
||||||
def visibility(ais, last):
|
|
||||||
v.Display(ais, last)
|
|
||||||
self.toggle(product_or_products, visibility)
|
|
||||||
|
|
||||||
def toggle_wireframe(self, product_or_products, flag):
|
|
||||||
v = self._display.Context
|
|
||||||
if flag:
|
|
||||||
def wireframe(ais, last):
|
|
||||||
if v.IsDisplayed(ais):
|
|
||||||
v.SetDisplayMode(ais, 0, last)
|
|
||||||
else:
|
|
||||||
def wireframe(ais, last):
|
|
||||||
if v.IsDisplayed(ais):
|
|
||||||
v.SetDisplayMode(ais, 1, last)
|
|
||||||
self.toggle(product_or_products, wireframe)
|
|
||||||
|
|
||||||
def HandleSelection(self, X, Y):
|
|
||||||
v = self._display.Context
|
|
||||||
v.Select()
|
|
||||||
v.InitSelected()
|
|
||||||
if v.MoreSelected():
|
|
||||||
ais = v.SelectedInteractive()
|
|
||||||
inst = self.ais_to_product[ais.GetObject().SelectionPriority()]
|
|
||||||
self.instanceSelected.emit(inst)
|
|
||||||
|
|
||||||
class window(QtWidgets.QMainWindow):
|
|
||||||
|
|
||||||
TITLE = "IfcOpenShell IFC viewer"
|
|
||||||
|
|
||||||
window_closed = QtCore.pyqtSignal([])
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
QtWidgets.QMainWindow.__init__(self)
|
|
||||||
self.setWindowTitle(self.TITLE)
|
|
||||||
self.menu = self.menuBar()
|
|
||||||
self.menus = {}
|
|
||||||
|
|
||||||
def closeEvent(self, *args):
|
|
||||||
self.window_closed.emit()
|
|
||||||
|
|
||||||
def add_menu_item(self, menu, label, callback, icon=None, shortcut=None):
|
|
||||||
m = self.menus.get(menu)
|
|
||||||
if m is None:
|
|
||||||
m = self.menu.addMenu(menu)
|
|
||||||
self.menus[menu] = m
|
|
||||||
|
|
||||||
if icon:
|
|
||||||
a = QtWidgets.QAction(QtGui.QIcon(icon), label, self)
|
|
||||||
else:
|
|
||||||
a = QtWidgets.QAction(label, self)
|
|
||||||
|
|
||||||
if shortcut:
|
|
||||||
a.setShortcut(shortcut)
|
|
||||||
|
|
||||||
a.triggered.connect(callback)
|
|
||||||
m.addAction(a)
|
|
||||||
|
|
||||||
def makeSelectionHandler(self, component):
|
|
||||||
def handler(inst):
|
|
||||||
for c in self.components:
|
|
||||||
if c != component:
|
|
||||||
c.select(inst)
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def __init__(self, settings=None):
|
|
||||||
QtWidgets.QApplication.__init__(self, sys.argv)
|
|
||||||
self.window = application.window()
|
|
||||||
self.tree = application.decomposition_treeview()
|
|
||||||
self.tree2 = application.type_treeview()
|
|
||||||
self.propview = self.property_table()
|
|
||||||
self.canvas = application.viewer(self.window)
|
|
||||||
self.tabs = QtWidgets.QTabWidget()
|
|
||||||
self.window.resize(800, 600)
|
|
||||||
splitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
|
|
||||||
splitter.addWidget(self.tabs)
|
|
||||||
self.tabs.addTab(self.tree, 'Decomposition')
|
|
||||||
self.tabs.addTab(self.tree2, 'Types')
|
|
||||||
self.tabs.addTab(self.propview, "Properties")
|
|
||||||
splitter2 = QtWidgets.QSplitter(QtCore.Qt.Vertical)
|
|
||||||
splitter2.addWidget(self.canvas)
|
|
||||||
self.editor = code_edit(self.canvas, configuration().options('snippets'))
|
|
||||||
splitter2.addWidget(self.editor)
|
|
||||||
splitter.addWidget(splitter2)
|
|
||||||
splitter.setSizes([200, 600])
|
|
||||||
splitter2.setSizes([400, 200])
|
|
||||||
self.window.setCentralWidget(splitter)
|
|
||||||
self.canvas.initialize()
|
|
||||||
self.components = [self.tree, self.tree2, self.canvas, self.propview, self.editor]
|
|
||||||
self.files = {}
|
|
||||||
|
|
||||||
self.window.add_menu_item('File', '&Open', self.browse, shortcut='CTRL+O')
|
|
||||||
self.window.add_menu_item('File', '&Close', self.clear, shortcut='CTRL+W')
|
|
||||||
self.window.add_menu_item('File', '&Exit', self.window.close, shortcut='ALT+F4')
|
|
||||||
|
|
||||||
self.tree.instanceSelected.connect(self.makeSelectionHandler(self.tree))
|
|
||||||
self.tree2.instanceSelected.connect(self.makeSelectionHandler(self.tree2))
|
|
||||||
self.canvas.instanceSelected.connect(self.makeSelectionHandler(self.canvas))
|
|
||||||
for t in [self.tree, self.tree2]:
|
|
||||||
t.instanceVisibilityChanged.connect(functools.partial(self.change_visibility, t))
|
|
||||||
t.instanceDisplayModeChanged.connect(functools.partial(self.change_displaymode, t))
|
|
||||||
|
|
||||||
self.settings = settings
|
|
||||||
|
|
||||||
def change_visibility(self, tree, inst, flag):
|
|
||||||
insts = tree.get_children(inst)
|
|
||||||
self.canvas.toggle_visibility(insts, flag)
|
|
||||||
|
|
||||||
def change_displaymode(self, tree, inst, flag):
|
|
||||||
insts = tree.get_children(inst)
|
|
||||||
self.canvas.toggle_wireframe(insts, flag)
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self.window.show()
|
|
||||||
sys.exit(self.exec_())
|
|
||||||
|
|
||||||
def browse(self):
|
|
||||||
filename = QtWidgets.QFileDialog.getOpenFileName(self.window, 'Open file', ".",
|
|
||||||
"Industry Foundation Classes (*.ifc)")[0]
|
|
||||||
self.load(filename)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self.canvas._display.Context.RemoveAll()
|
|
||||||
self.tree.clear()
|
|
||||||
self.files.clear()
|
|
||||||
|
|
||||||
def load(self, fn):
|
|
||||||
if fn in self.files:
|
|
||||||
return
|
|
||||||
f = open_ifc_file(str(fn))
|
|
||||||
self.files[fn] = f
|
|
||||||
for c in self.components:
|
|
||||||
c.load_file(f, setting=self.settings)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
application().start()
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# #
|
|
||||||
# This file is part of IfcOpenShell. #
|
|
||||||
# #
|
|
||||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
|
||||||
# it under the terms of the Lesser GNU General Public License as published by #
|
|
||||||
# the Free Software Foundation, either version 3.0 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 #
|
|
||||||
# Lesser GNU General Public License for more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the Lesser GNU General Public License #
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
|
||||||
# #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
Rough draft of a client application for the C++ IfcGeomServer binary
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import numpy
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
class message_headers(object):
|
|
||||||
HELLO = 0xff00
|
|
||||||
IFC_MODEL = HELLO + 1
|
|
||||||
GET = IFC_MODEL + 1
|
|
||||||
ENTITY = GET + 1
|
|
||||||
MORE = ENTITY + 1
|
|
||||||
NEXT = MORE + 1
|
|
||||||
BYE = NEXT + 1
|
|
||||||
GET_LOG = BYE + 1
|
|
||||||
LOG = GET_LOG + 1
|
|
||||||
DEFLECTION = LOG + 1
|
|
||||||
SETTING = DEFLECTION + 1
|
|
||||||
|
|
||||||
message = namedtuple("message", ("header", "contents"))
|
|
||||||
|
|
||||||
def process(geomserver_exe, ifc_filename):
|
|
||||||
|
|
||||||
proc = subprocess.Popen([geomserver_exe], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
|
|
||||||
|
|
||||||
def cast(data, dtype, n=None):
|
|
||||||
arr = numpy.frombuffer(data, dtype=dtype)
|
|
||||||
if n is None: return arr[0]
|
|
||||||
else: return arr
|
|
||||||
|
|
||||||
def read(dtype, n=None):
|
|
||||||
data = proc.stdout.read(dtype().nbytes * (n or 1))
|
|
||||||
return cast(data, dtype, n)
|
|
||||||
|
|
||||||
def read_message(header_assertion=None):
|
|
||||||
header, size = read(numpy.int32, 2)
|
|
||||||
assert header_assertion is None or header_assertion == header
|
|
||||||
contents = b""
|
|
||||||
if size > 0:
|
|
||||||
contents = proc.stdout.read(size)
|
|
||||||
return message(header, contents)
|
|
||||||
|
|
||||||
def write(header, contents=None):
|
|
||||||
if contents is None: contents = []
|
|
||||||
proc.stdin.write(numpy.int32(header).tobytes())
|
|
||||||
integers_as_int32 = list(map(lambda s: numpy.int32(s) if isinstance(s, int) else s, contents))
|
|
||||||
to_bytes = list(map(lambda s: s.tobytes() if hasattr(s, 'tobytes') else s, integers_as_int32))
|
|
||||||
total_length = numpy.int32(sum(map(len, to_bytes)))
|
|
||||||
proc.stdin.write(total_length.tobytes())
|
|
||||||
for b in to_bytes:
|
|
||||||
proc.stdin.write(b)
|
|
||||||
proc.stdin.flush()
|
|
||||||
|
|
||||||
read_message(message_headers.HELLO)
|
|
||||||
|
|
||||||
# @todo: no need to read the entire file in memory
|
|
||||||
s = open(ifc_filename, "rb").read()
|
|
||||||
|
|
||||||
write(message_headers.SETTING, [numpy.int32((1 << 4)), numpy.int32(1)])
|
|
||||||
write(message_headers.IFC_MODEL, [numpy.int32(len(s)), s, b"\x00" * ((4 - (len(s) % 4)) % 4)])
|
|
||||||
|
|
||||||
while True:
|
|
||||||
has_more = cast(read_message(message_headers.MORE).contents, numpy.int32) == 1
|
|
||||||
if not has_more: break
|
|
||||||
write(message_headers.GET)
|
|
||||||
print(read_message(message_headers.ENTITY).contents)
|
|
||||||
write(message_headers.NEXT)
|
|
||||||
|
|
||||||
write(message_headers.BYE)
|
|
||||||
read_message(message_headers.BYE)
|
|
||||||
proc.wait()
|
|
||||||
assert proc.returncode == 0
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import sys
|
|
||||||
import platform
|
|
||||||
exe_extension = ".exe" if platform.system() == 'Windows' else ""
|
|
||||||
exe = os.environ.get("IFCGEOMSERVER") or ("IfcGeomServer" + exe_extension)
|
|
||||||
for fn in sys.argv[1:]:
|
|
||||||
process(exe, fn)
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from code import InteractiveConsole
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
||||||
|
|
||||||
try:
|
|
||||||
from PyQt5 import QtWidgets
|
|
||||||
except BaseException:
|
|
||||||
QtWidgets = QtGui
|
|
||||||
|
|
||||||
try:
|
|
||||||
from pyqode.core.panels import CheckerPanel
|
|
||||||
from pyqode.core import api
|
|
||||||
from pyqode.core import modes
|
|
||||||
from pyqode.core import panels
|
|
||||||
from pyqode.core.api import CodeEdit, ColorScheme
|
|
||||||
from pyqode.python.modes import PyAutoIndentMode, PythonSH
|
|
||||||
from pyqode.python.backend import server
|
|
||||||
from pyqode.python import modes as pymodes, panels as pypanels, widgets
|
|
||||||
from pyqode.python.widgets import PyInteractiveConsole
|
|
||||||
|
|
||||||
has_pyqode = True
|
|
||||||
except BaseException:
|
|
||||||
has_pyqode = False
|
|
||||||
CodeEdit = QtWidgets.QPlainTextEdit
|
|
||||||
|
|
||||||
|
|
||||||
class StdoutRedirector(object):
|
|
||||||
"""A class for redirecting stdout to this Text widget."""
|
|
||||||
|
|
||||||
def __init__(self, widget):
|
|
||||||
self.widget = widget
|
|
||||||
self.isError = False
|
|
||||||
|
|
||||||
def write(self, myStr):
|
|
||||||
self.widget.moveCursor(QtGui.QTextCursor.End)
|
|
||||||
if self.isError:
|
|
||||||
self.widget.setTextColor(QtCore.Qt.red)
|
|
||||||
else:
|
|
||||||
self.widget.setTextColor(QtCore.Qt.white)
|
|
||||||
self.widget.insertPlainText(myStr)
|
|
||||||
self.widget.moveCursor(QtGui.QTextCursor.End)
|
|
||||||
|
|
||||||
|
|
||||||
class code_edit(QtWidgets.QWidget):
|
|
||||||
class Console(InteractiveConsole):
|
|
||||||
def __init__(*args):
|
|
||||||
InteractiveConsole.__init__(*args)
|
|
||||||
|
|
||||||
def enter(self, source):
|
|
||||||
self.runcode(source)
|
|
||||||
|
|
||||||
def runCode(self):
|
|
||||||
sys.stdout = StdoutRedirector(self.output)
|
|
||||||
sys.stderr = StdoutRedirector(self.output)
|
|
||||||
sys.stderr.isError = True
|
|
||||||
|
|
||||||
if not self.model:
|
|
||||||
print("please load a model first", file=sys.stderr)
|
|
||||||
else:
|
|
||||||
self.c.enter(str(self.editor.toPlainText()))
|
|
||||||
|
|
||||||
sys.stdout = sys.__stdout__
|
|
||||||
sys.stderr = sys.__stderr__
|
|
||||||
|
|
||||||
def select(self, product):
|
|
||||||
self.c = self.Console({'model': self.model, 'viewer': self.viewer, 'selection': product})
|
|
||||||
|
|
||||||
def __init__(self, viewer, snippets=None):
|
|
||||||
self.model = None
|
|
||||||
self.viewer = viewer
|
|
||||||
QtWidgets.QWidget.__init__(self)
|
|
||||||
self.layout = QtWidgets.QVBoxLayout(self)
|
|
||||||
self.setLayout(self.layout)
|
|
||||||
self.c = None
|
|
||||||
self.tools = QtWidgets.QHBoxLayout(self)
|
|
||||||
self.layout.addLayout(self.tools)
|
|
||||||
self.runbutton = QtWidgets.QPushButton("Run")
|
|
||||||
width = self.runbutton.fontMetrics().boundingRect("Run").width() + 20
|
|
||||||
self.runbutton.setMaximumWidth(width)
|
|
||||||
self.tools.addWidget(self.runbutton)
|
|
||||||
self.runbutton.clicked.connect(self.runCode)
|
|
||||||
|
|
||||||
editor = CodeEdit()
|
|
||||||
if has_pyqode:
|
|
||||||
editor.backend.start(server.__file__)
|
|
||||||
editor.panels.append(panels.FoldingPanel())
|
|
||||||
editor.panels.append(panels.LineNumberPanel())
|
|
||||||
editor.panels.append(panels.SearchAndReplacePanel(),
|
|
||||||
panels.SearchAndReplacePanel.Position.BOTTOM)
|
|
||||||
editor.panels.append(panels.EncodingPanel(), api.Panel.Position.TOP)
|
|
||||||
editor.add_separator()
|
|
||||||
editor.panels.append(pypanels.QuickDocPanel(), api.Panel.Position.BOTTOM)
|
|
||||||
sh = editor.modes.append(PythonSH(editor.document()))
|
|
||||||
editor.modes.append(modes.CaretLineHighlighterMode())
|
|
||||||
editor.modes.append(modes.CodeCompletionMode())
|
|
||||||
editor.modes.append(modes.ExtendedSelectionMode())
|
|
||||||
editor.modes.append(modes.FileWatcherMode())
|
|
||||||
editor.modes.append(modes.OccurrencesHighlighterMode())
|
|
||||||
editor.modes.append(modes.RightMarginMode())
|
|
||||||
editor.modes.append(modes.SmartBackSpaceMode())
|
|
||||||
editor.modes.append(modes.SymbolMatcherMode())
|
|
||||||
editor.modes.append(modes.ZoomMode())
|
|
||||||
editor.modes.append(pymodes.CommentsMode())
|
|
||||||
editor.modes.append(pymodes.CalltipsMode())
|
|
||||||
auto = pymodes.PyAutoCompleteMode()
|
|
||||||
auto.logger.setLevel(logging.CRITICAL)
|
|
||||||
editor.modes.append(auto)
|
|
||||||
editor.modes.append(pymodes.PyAutoIndentMode())
|
|
||||||
editor.modes.append(pymodes.PyIndenterMode())
|
|
||||||
editor.show()
|
|
||||||
else:
|
|
||||||
editor.setStyleSheet('font-size: 10pt; font-family: Consolas, Courier;')
|
|
||||||
|
|
||||||
self.editor = editor
|
|
||||||
self.snippets = snippets
|
|
||||||
if self.snippets:
|
|
||||||
self.list = QtWidgets.QComboBox(self)
|
|
||||||
self.replace_snippet(0)
|
|
||||||
for snip_name in self.snippets.keys():
|
|
||||||
self.list.addItem(snip_name)
|
|
||||||
self.tools.addWidget(self.list)
|
|
||||||
self.list.currentIndexChanged[int].connect(self.replace_snippet)
|
|
||||||
|
|
||||||
self.layout.addWidget(self.editor)
|
|
||||||
self.output = QtWidgets.QTextEdit()
|
|
||||||
self.output.setReadOnly(True)
|
|
||||||
self.output.setStyleSheet('font-size: 10pt; font-family: Consolas, Courier; background-color: #444;')
|
|
||||||
self.layout.addWidget(self.output)
|
|
||||||
|
|
||||||
def replace_snippet(self, number=None):
|
|
||||||
snip = list(self.snippets.values())[number]
|
|
||||||
if has_pyqode:
|
|
||||||
self.editor.setPlainText(snip, "", "")
|
|
||||||
else:
|
|
||||||
self.editor.setPlainText(snip)
|
|
||||||
|
|
||||||
def load_file(self, f, **kwargs):
|
|
||||||
output = []
|
|
||||||
sys.stdout = StdoutRedirector(self.output)
|
|
||||||
self.model = f
|
|
||||||
self.c = self.Console({'model': self.model, 'selection': None, 'viewer': self.viewer})
|
|
||||||
sys.stdout = sys.__stdout__
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# #
|
|
||||||
# This file is part of IfcOpenShell. #
|
|
||||||
# #
|
|
||||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
|
||||||
# it under the terms of the Lesser GNU General Public License as published by #
|
|
||||||
# the Free Software Foundation, either version 3.0 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 #
|
|
||||||
# Lesser GNU General Public License for more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the Lesser GNU General Public License #
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
|
||||||
# #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from .. import ifcopenshell_wrapper
|
|
||||||
from ..file import file
|
|
||||||
from ..entity_instance import entity_instance
|
|
||||||
|
|
||||||
from . import has_occ
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_shape_creation(settings, shape):
|
|
||||||
return shape
|
|
||||||
|
|
||||||
|
|
||||||
if has_occ:
|
|
||||||
from . import occ_utils as utils
|
|
||||||
|
|
||||||
try:
|
|
||||||
from OCC.Core import TopoDS
|
|
||||||
except ImportError:
|
|
||||||
from OCC import TopoDS
|
|
||||||
|
|
||||||
def wrap_shape_creation(settings, shape):
|
|
||||||
if getattr(settings, 'use_python_opencascade', False):
|
|
||||||
return utils.create_shape_from_serialization(shape)
|
|
||||||
else:
|
|
||||||
return shape
|
|
||||||
|
|
||||||
# Subclass the settings module to provide an additional
|
|
||||||
# setting to enable pythonOCC when available
|
|
||||||
class settings(ifcopenshell_wrapper.settings):
|
|
||||||
if has_occ:
|
|
||||||
USE_PYTHON_OPENCASCADE = -1
|
|
||||||
|
|
||||||
def set(self, *args):
|
|
||||||
setting, value = args
|
|
||||||
if setting == settings.USE_PYTHON_OPENCASCADE:
|
|
||||||
self.set(settings.USE_BREP_DATA, value)
|
|
||||||
self.set(settings.USE_WORLD_COORDS, value)
|
|
||||||
self.set(settings.DISABLE_TRIANGULATION, value)
|
|
||||||
self.use_python_opencascade = value
|
|
||||||
else:
|
|
||||||
ifcopenshell_wrapper.settings.set(self, *args)
|
|
||||||
|
|
||||||
|
|
||||||
# Assert templated precision to match Python's internal float type
|
|
||||||
assert ifcopenshell_wrapper.iterator_double_precision.mantissa_size() == sys.float_info.mant_dig
|
|
||||||
_iterator = ifcopenshell_wrapper.iterator_double_precision
|
|
||||||
|
|
||||||
# Make sure people are able to use python's platform agnostic paths
|
|
||||||
class iterator(_iterator):
|
|
||||||
def __init__(self, settings, file_or_filename, num_threads = 1):
|
|
||||||
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)
|
|
||||||
_iterator.__init__(self, settings, file_or_filename, num_threads)
|
|
||||||
|
|
||||||
if has_occ:
|
|
||||||
def get(self):
|
|
||||||
return wrap_shape_creation(self.settings, _iterator.get(self))
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
if self.initialize():
|
|
||||||
while True:
|
|
||||||
yield self.get()
|
|
||||||
if not self.next(): break
|
|
||||||
|
|
||||||
class tree(ifcopenshell_wrapper.tree):
|
|
||||||
|
|
||||||
def __init__(self, file=None, settings=None):
|
|
||||||
args = [self]
|
|
||||||
if file is not None:
|
|
||||||
args.append(file.wrapped_data)
|
|
||||||
if settings is not None:
|
|
||||||
args.append(settings)
|
|
||||||
ifcopenshell_wrapper.tree.__init__(*args)
|
|
||||||
|
|
||||||
def add_file(self, file, settings):
|
|
||||||
ifcopenshell_wrapper.tree.add_file(self, file.wrapped_data, settings)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
args = [self, unwrap(value)]
|
|
||||||
if isinstance(value, entity_instance):
|
|
||||||
args.append(kwargs.get("completely_within", False))
|
|
||||||
elif has_occ:
|
|
||||||
if isinstance(value, TopoDS.TopoDS_Shape):
|
|
||||||
args[1] = utils.serialize_shape(value)
|
|
||||||
return [entity_instance(e) for e in ifcopenshell_wrapper.tree.select(*args)]
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
args = [self, unwrap(value)]
|
|
||||||
if "extend" in kwargs or "completely_within" in kwargs:
|
|
||||||
args.append(kwargs.get("completely_within", False))
|
|
||||||
if "extend" in kwargs:
|
|
||||||
args.append(kwargs.get("extend", -1.e-5))
|
|
||||||
return [entity_instance(e) for e in ifcopenshell_wrapper.tree.select_box(*args)]
|
|
||||||
|
|
||||||
|
|
||||||
def create_shape(settings, inst, repr=None):
|
|
||||||
"""
|
|
||||||
Return a geometric representation from STEP-based IFCREPRESENTATIONSHAPE
|
|
||||||
or
|
|
||||||
Return an OpenCASCADE BRep if settings.USE_PYTHON_OPENCASCADE == True
|
|
||||||
|
|
||||||
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:
|
|
||||||
shape = geom.create_shape(settings, inst=product).geometry
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
return wrap_shape_creation(
|
|
||||||
settings,
|
|
||||||
ifcopenshell_wrapper.create_shape(
|
|
||||||
settings,
|
|
||||||
inst.wrapped_data,
|
|
||||||
repr.wrapped_data if repr is not None else None
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def iterate(settings, filename):
|
|
||||||
it = iterator(settings, filename)
|
|
||||||
if it.initialize():
|
|
||||||
while True:
|
|
||||||
yield it.get()
|
|
||||||
if not it.next():
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def make_shape_function(fn):
|
|
||||||
def entity_instance_or_none(e):
|
|
||||||
return None if e is None else entity_instance(e)
|
|
||||||
|
|
||||||
if has_occ:
|
|
||||||
def _(schema, string_or_shape, *args):
|
|
||||||
if isinstance(string_or_shape, TopoDS.TopoDS_Shape):
|
|
||||||
string_or_shape = utils.serialize_shape(string_or_shape)
|
|
||||||
return entity_instance_or_none(fn(schema, string_or_shape, *args))
|
|
||||||
else:
|
|
||||||
def _(schema, string, *args):
|
|
||||||
return entity_instance_or_none(fn(schema, string, *args))
|
|
||||||
return _
|
|
||||||
|
|
||||||
|
|
||||||
serialise = make_shape_function(ifcopenshell_wrapper.serialise)
|
|
||||||
tesselate = make_shape_function(ifcopenshell_wrapper.tesselate)
|
|
||||||
@@ -1,233 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# #
|
|
||||||
# This file is part of IfcOpenShell. #
|
|
||||||
# #
|
|
||||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
|
||||||
# it under the terms of the Lesser GNU General Public License as published by #
|
|
||||||
# the Free Software Foundation, either version 3.0 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 #
|
|
||||||
# Lesser GNU General Public License for more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the Lesser GNU General Public License #
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
|
||||||
# #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import random
|
|
||||||
import operator
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from collections import namedtuple, Iterable
|
|
||||||
|
|
||||||
try:
|
|
||||||
from OCC.Core import V3d, TopoDS, gp, AIS, Quantity, BRepTools, Graphic3d
|
|
||||||
except ImportError:
|
|
||||||
from OCC import V3d, TopoDS, gp, AIS, Quantity, BRepTools, Graphic3d
|
|
||||||
|
|
||||||
shape_tuple = namedtuple('shape_tuple', ('data', 'geometry', 'styles'))
|
|
||||||
|
|
||||||
handle, main_loop, add_menu, add_function_to_menu = None, None, None, None
|
|
||||||
|
|
||||||
DEFAULT_STYLES = {
|
|
||||||
"DEFAULT": (.7, .7, .7),
|
|
||||||
"IfcWall": (.8, .8, .8),
|
|
||||||
"IfcSite": (.75, .8, .65),
|
|
||||||
"IfcSlab": (.4, .4, .4),
|
|
||||||
"IfcWallStandardCase": (.9, .9, .9),
|
|
||||||
"IfcWall": (.9, .9, .9),
|
|
||||||
"IfcWindow": (.75, .8, .75, .3),
|
|
||||||
"IfcDoor": (.55, .3, .15),
|
|
||||||
"IfcBeam": (.75, .7, .7),
|
|
||||||
"IfcRailing": (.65, .6, .6),
|
|
||||||
"IfcMember": (.65, .6, .6),
|
|
||||||
"IfcPlate": (.8, .8, .8)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_display():
|
|
||||||
import OCC.Display.SimpleGui
|
|
||||||
|
|
||||||
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()
|
|
||||||
viewer = viewer_handle.GetObject() if hasattr(viewer_handle, "GetObject") else viewer_handle
|
|
||||||
|
|
||||||
def lights():
|
|
||||||
viewer.InitActiveLights()
|
|
||||||
for _ in range(2):
|
|
||||||
try:
|
|
||||||
active_light = viewer.ActiveLight()
|
|
||||||
except BaseException:
|
|
||||||
break
|
|
||||||
yield active_light
|
|
||||||
viewer.NextActiveLights()
|
|
||||||
|
|
||||||
lights = list(lights())
|
|
||||||
for l in lights:
|
|
||||||
viewer.DelLight(l)
|
|
||||||
|
|
||||||
for dir in [V3d.V3d_TypeOfOrientation_Yup_AxoRight, V3d.V3d_TypeOfOrientation_Zup_AxoRight]:
|
|
||||||
light = V3d.V3d_DirectionalLight(viewer_handle)
|
|
||||||
light.SetDirection(dir)
|
|
||||||
viewer.SetLightOn(light)
|
|
||||||
|
|
||||||
setup()
|
|
||||||
return handle
|
|
||||||
|
|
||||||
|
|
||||||
def yield_subshapes(shape):
|
|
||||||
it = TopoDS.TopoDS_Iterator(shape)
|
|
||||||
while it.More():
|
|
||||||
yield it.Value()
|
|
||||||
it.Next()
|
|
||||||
|
|
||||||
|
|
||||||
def display_shape(shape, clr=None, viewer_handle=None):
|
|
||||||
if viewer_handle is None:
|
|
||||||
viewer_handle = handle
|
|
||||||
|
|
||||||
if isinstance(shape, shape_tuple):
|
|
||||||
shape, representation = shape.geometry, shape
|
|
||||||
else:
|
|
||||||
representation = None
|
|
||||||
|
|
||||||
material = Graphic3d.Graphic3d_MaterialAspect(Graphic3d.Graphic3d_NOM_PLASTER)
|
|
||||||
|
|
||||||
if representation and not clr:
|
|
||||||
if len(set(representation.styles)) == 1:
|
|
||||||
clr = representation.styles[0]
|
|
||||||
if min(clr) < 0. or max(clr) > 1.:
|
|
||||||
clr = DEFAULT_STYLES.get(representation.data.type, DEFAULT_STYLES["DEFAULT"])
|
|
||||||
|
|
||||||
if clr:
|
|
||||||
ais = AIS.AIS_Shape(shape)
|
|
||||||
ais.SetMaterial(material)
|
|
||||||
|
|
||||||
if isinstance(clr, str):
|
|
||||||
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)
|
|
||||||
if len(clr) < 3 or len(clr) > 4:
|
|
||||||
raise Exception("Need 3 or 4 color components. Got '%r'." % len(clr))
|
|
||||||
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))
|
|
||||||
|
|
||||||
ais.SetColor(qclr)
|
|
||||||
if isinstance(clr, tuple) and len(clr) == 4 and clr[3] < 1.:
|
|
||||||
ais.SetTransparency(1. - clr[3])
|
|
||||||
|
|
||||||
elif representation and hasattr(AIS, "AIS_MultipleConnectedShape"):
|
|
||||||
default_style_applied = None
|
|
||||||
|
|
||||||
ais = AIS.AIS_MultipleConnectedShape(shape)
|
|
||||||
|
|
||||||
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):
|
|
||||||
subshape = AIS.AIS_Shape(shp)
|
|
||||||
if min(stl) < 0. or max(stl) > 1.:
|
|
||||||
default_style_applied = stl = DEFAULT_STYLES.get(representation.data.type,
|
|
||||||
DEFAULT_STYLES["DEFAULT"])
|
|
||||||
subshape.SetColor(Quantity.Quantity_Color(stl[0], stl[1], stl[2], Quantity.Quantity_TOC_RGB))
|
|
||||||
subshape.SetMaterial(material)
|
|
||||||
if len(stl) == 4 and stl[3] < 1.:
|
|
||||||
subshape.SetTransparency(1. - stl[3])
|
|
||||||
ais.Connect(subshape.GetHandle())
|
|
||||||
|
|
||||||
# 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:
|
|
||||||
if len(default_style_applied) == 3:
|
|
||||||
default_style_applied += (1.,)
|
|
||||||
applied_styles += (default_style_applied,)
|
|
||||||
|
|
||||||
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))
|
|
||||||
if min_transp < 1.:
|
|
||||||
ais.SetTransparency(1.)
|
|
||||||
|
|
||||||
else:
|
|
||||||
ais = AIS.AIS_Shape(shape)
|
|
||||||
ais.SetMaterial(material)
|
|
||||||
|
|
||||||
def r():
|
|
||||||
return random.random() * 0.3 + 0.7
|
|
||||||
|
|
||||||
clr = Quantity.Quantity_Color(r(), r(), r(), Quantity.Quantity_TOC_RGB)
|
|
||||||
ais.SetColor(clr)
|
|
||||||
|
|
||||||
ais_handle = ais
|
|
||||||
viewer_handle.Context.Display(ais_handle, False)
|
|
||||||
|
|
||||||
return ais_handle
|
|
||||||
|
|
||||||
|
|
||||||
def set_shape_transparency(ais, t):
|
|
||||||
handle.Context.SetTransparency(ais, t)
|
|
||||||
|
|
||||||
|
|
||||||
def get_bounding_box_center(bbox):
|
|
||||||
bbmin = [0.] * 3
|
|
||||||
bbmax = [0.] * 3
|
|
||||||
bbmin[0], bbmin[1], bbmin[2], bbmax[0], bbmax[1], bbmax[2] = bbox.Get()
|
|
||||||
return gp.gp_Pnt(*map(lambda xy: (xy[0] + xy[1]) / 2., zip(bbmin, bbmax)))
|
|
||||||
|
|
||||||
|
|
||||||
def serialize_shape(shape):
|
|
||||||
shapes = BRepTools.BRepTools_ShapeSet()
|
|
||||||
shapes.Add(shape)
|
|
||||||
return shapes.WriteToString()
|
|
||||||
|
|
||||||
|
|
||||||
def create_shape_from_serialization(brep_object):
|
|
||||||
brep_data, occ_shape, styles = None, None, ()
|
|
||||||
|
|
||||||
is_product_shape = True
|
|
||||||
try:
|
|
||||||
brep_data = brep_object.geometry.brep_data
|
|
||||||
styles = brep_object.geometry.surface_styles
|
|
||||||
except BaseException:
|
|
||||||
try:
|
|
||||||
brep_data = brep_object.brep_data
|
|
||||||
styles = brep_object.surface_styles
|
|
||||||
is_product_shape = False
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
styles = tuple(styles[i:i + 4] for i in range(0, len(styles), 4))
|
|
||||||
|
|
||||||
if not brep_data:
|
|
||||||
return shape_tuple(brep_object, None, styles)
|
|
||||||
|
|
||||||
try:
|
|
||||||
ss = BRepTools.BRepTools_ShapeSet()
|
|
||||||
ss.ReadFromString(brep_data)
|
|
||||||
occ_shape = ss.Shape(ss.NbShapes())
|
|
||||||
except BaseException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if is_product_shape:
|
|
||||||
return shape_tuple(brep_object, occ_shape, styles)
|
|
||||||
else:
|
|
||||||
return occ_shape
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# #
|
|
||||||
# This file is part of IfcOpenShell. #
|
|
||||||
# #
|
|
||||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
|
||||||
# it under the terms of the Lesser GNU General Public License as published by #
|
|
||||||
# the Free Software Foundation, either version 3.0 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 #
|
|
||||||
# Lesser GNU General Public License for more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the Lesser GNU General Public License #
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
|
||||||
# #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
import string
|
|
||||||
|
|
||||||
from functools import reduce
|
|
||||||
|
|
||||||
chars = string.digits + string.ascii_uppercase + string.ascii_lowercase + '_$'
|
|
||||||
|
|
||||||
|
|
||||||
def compress(g):
|
|
||||||
bs = [int(g[i:i + 2], 16) for i in range(0, len(g), 2)]
|
|
||||||
|
|
||||||
def b64(v, l=4):
|
|
||||||
return ''.join([chars[(v // (64 ** i)) % 64] for i in range(l)][::-1])
|
|
||||||
|
|
||||||
return ''.join([b64(bs[0], 2)] + [b64((bs[i] << 16) + (bs[i + 1] << 8) + bs[i + 2]) for i in range(1, 16, 3)])
|
|
||||||
|
|
||||||
|
|
||||||
def expand(g):
|
|
||||||
def b64(v):
|
|
||||||
return reduce(lambda a, b: a * 64 + b, map(lambda c: chars.index(c), v))
|
|
||||||
|
|
||||||
bs = [b64(g[0:2])]
|
|
||||||
for i in range(5):
|
|
||||||
d = b64(g[2 + 4 * i:6 + 4 * i])
|
|
||||||
bs += [(d >> (8 * (2 - j))) % 256 for j in range(3)]
|
|
||||||
return ''.join(['%02x' % b for b in bs])
|
|
||||||
|
|
||||||
|
|
||||||
def split(g):
|
|
||||||
return '{%s-%s-%s-%s-%s}' % (g[:8], g[8:12], g[12:16], g[16:20], g[20:])
|
|
||||||
|
|
||||||
|
|
||||||
def new():
|
|
||||||
return compress(uuid.uuid4().hex)
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,27 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# #
|
|
||||||
# This file is part of IfcOpenShell. #
|
|
||||||
# #
|
|
||||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
|
||||||
# it under the terms of the Lesser GNU General Public License as published by #
|
|
||||||
# the Free Software Foundation, either version 3.0 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 #
|
|
||||||
# Lesser GNU General Public License for more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the Lesser GNU General Public License #
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
|
||||||
# #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
from . import ifcopenshell_wrapper
|
|
||||||
|
|
||||||
version = ifcopenshell_wrapper.version()
|
|
||||||
get_log = ifcopenshell_wrapper.get_log
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# #
|
|
||||||
# This file is part of IfcOpenShell. #
|
|
||||||
# #
|
|
||||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
|
||||||
# it under the terms of the Lesser GNU General Public License as published by #
|
|
||||||
# the Free Software Foundation, either version 3.0 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 #
|
|
||||||
# Lesser GNU General Public License for more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the Lesser GNU General Public License #
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
|
||||||
# #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import time
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from .file import file
|
|
||||||
from .guid import compress
|
|
||||||
from . import main
|
|
||||||
|
|
||||||
# A quick way to setup an 'empty' IFC file, taken from:
|
|
||||||
# http://academy.ifcopenshell.org/creating-a-simple-wall-with-property-set-and-quantity-information/
|
|
||||||
TEMPLATE = """ISO-10303-21;
|
|
||||||
HEADER;
|
|
||||||
FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1');
|
|
||||||
FILE_NAME('%(filename)s','%(timestring)s',('%(creator)s'),('%(organization)s'),'%(application)s','%(application)s','');
|
|
||||||
FILE_SCHEMA(('%(schema_identifier)s'));
|
|
||||||
ENDSEC;
|
|
||||||
DATA;
|
|
||||||
#1=IFCPERSON($,$,'%(creator)s',$,$,$,$,$);
|
|
||||||
#2=IFCORGANIZATION($,'%(organization)s',$,$,$);
|
|
||||||
#3=IFCPERSONANDORGANIZATION(#1,#2,$);
|
|
||||||
#4=IFCAPPLICATION(#2,'%(application_version)s','%(application)s','');
|
|
||||||
#5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,%(timestamp)s);
|
|
||||||
#6=IFCDIRECTION((1.,0.,0.));
|
|
||||||
#7=IFCDIRECTION((0.,0.,1.));
|
|
||||||
#8=IFCCARTESIANPOINT((0.,0.,0.));
|
|
||||||
#9=IFCAXIS2PLACEMENT3D(#8,#7,#6);
|
|
||||||
#10=IFCDIRECTION((0.,1.,0.));
|
|
||||||
#11=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-05,#9,#10);
|
|
||||||
#12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
|
||||||
#13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
|
||||||
#14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
|
||||||
#15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
|
||||||
#16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
|
||||||
#17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16);
|
|
||||||
#18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17);
|
|
||||||
#19=IFCUNITASSIGNMENT((#13,#14,#15,#18));
|
|
||||||
#20=IFCPROJECT('%(project_globalid)s',#5,'%(project_name)s',$,$,$,$,(#11),#19);
|
|
||||||
ENDSEC;
|
|
||||||
END-ISO-10303-21;
|
|
||||||
"""
|
|
||||||
|
|
||||||
DEFAULTS = {
|
|
||||||
"application": lambda d: 'IfcOpenShell-%s' % main.version,
|
|
||||||
"application_version": lambda d: main.version,
|
|
||||||
"project_globalid": lambda d: compress(uuid.uuid4().hex),
|
|
||||||
"schema_identifier": lambda d: main.schema_identifier,
|
|
||||||
"timestamp": lambda d: int(time.time()),
|
|
||||||
"timestring": lambda d: time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(d.get('timestamp') or time.time()))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def create(filename=None, timestring=None, organization=None, creator=None,
|
|
||||||
schema_identifier=None, application_version=None, timestamp=None,
|
|
||||||
application=None, project_globalid=None, project_name=None):
|
|
||||||
d = dict(locals())
|
|
||||||
|
|
||||||
def _():
|
|
||||||
for var, value in d.items():
|
|
||||||
if value is None:
|
|
||||||
yield var, DEFAULTS.get(var, lambda *args: '')(d)
|
|
||||||
|
|
||||||
d.update(dict(_()))
|
|
||||||
|
|
||||||
return file.from_string(TEMPLATE % d)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
def get_psets(element):
|
|
||||||
psets = {}
|
|
||||||
try:
|
|
||||||
if element.is_a('IfcTypeObject'):
|
|
||||||
if element.HasPropertySets:
|
|
||||||
for definition in element.HasPropertySets:
|
|
||||||
psets[definition.Name] = get_properties(definition)
|
|
||||||
else:
|
|
||||||
for relationship in element.IsDefinedBy:
|
|
||||||
if relationship.is_a('IfcRelDefinesByProperties'):
|
|
||||||
definition = relationship.RelatingPropertyDefinition
|
|
||||||
psets[definition.Name] = get_properties(definition)
|
|
||||||
except Exception as e:
|
|
||||||
import traceback
|
|
||||||
print('failed to load properties: {}'.format(e))
|
|
||||||
traceback.print_exc()
|
|
||||||
return psets
|
|
||||||
|
|
||||||
def get_properties(definition):
|
|
||||||
if definition is not None:
|
|
||||||
props = {}
|
|
||||||
if definition.is_a('IfcElementQuantity'):
|
|
||||||
for q in definition.Quantities:
|
|
||||||
if q.is_a('IfcPhysicalSimpleQuantity'):
|
|
||||||
props[q.Name] = q[3]
|
|
||||||
elif definition.is_a('IfcPropertySet'):
|
|
||||||
for prop in definition.HasProperties:
|
|
||||||
if prop.is_a('IfcPropertySingleValue'):
|
|
||||||
props[prop.Name] = prop.NominalValue
|
|
||||||
else:
|
|
||||||
# Entity introduced in IFC4
|
|
||||||
# definition.is_a('IfcPreDefinedPropertySet'):
|
|
||||||
for prop in range(4, len(definition)):
|
|
||||||
props[definition.attribute_name(prop)] = definition[prop]
|
|
||||||
return props
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import math
|
|
||||||
|
|
||||||
def dms2dd(degrees, minutes, seconds, milliseconds=0):
|
|
||||||
dd = float(degrees) + float(minutes)/60.0 + float(seconds)/(3600.0) + float(milliseconds/3600000.0)
|
|
||||||
return dd
|
|
||||||
|
|
||||||
def dd2dms(dd):
|
|
||||||
dd = float(dd)
|
|
||||||
sign = 1 if dd >= 0 else -1
|
|
||||||
dd = abs(dd)
|
|
||||||
minutes, seconds = divmod(dd*3600, 60)
|
|
||||||
degrees, minutes = divmod(minutes, 60)
|
|
||||||
if dd < 0:
|
|
||||||
degrees = -degrees
|
|
||||||
return (int(degrees) * sign, int(minutes) * sign, int(seconds) * sign)
|
|
||||||
|
|
||||||
def xyz2enh(x, y, z, eastings, northings, orthogonal_height, x_axis_abscissa, x_axis_ordinate, scale=None):
|
|
||||||
if scale is None:
|
|
||||||
scale = 1.
|
|
||||||
rotation = math.atan2(x_axis_ordinate, x_axis_abscissa)
|
|
||||||
a = scale * math.cos(rotation)
|
|
||||||
b = scale * math.sin(rotation)
|
|
||||||
eastings = (a * x) - (b * y) + eastings
|
|
||||||
northings = (b * x) + (a * y) + northings
|
|
||||||
height = z + orthogonal_height
|
|
||||||
return (eastings, northings, height)
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import lark
|
|
||||||
|
|
||||||
class Selector():
|
|
||||||
def parse(self, ifc_file, query):
|
|
||||||
self.file = ifc_file
|
|
||||||
l = lark.Lark('''start: query (lfunction query)*
|
|
||||||
query: selector | group
|
|
||||||
group: "(" query (lfunction query)* ")"
|
|
||||||
selector: (inverse_relationship)? guid_selector | (inverse_relationship)? class_selector
|
|
||||||
guid_selector: "#" /[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$]{22}/
|
|
||||||
class_selector: "." WORD filter ?
|
|
||||||
filter: "[" filter_key (comparison filter_value)? "]"
|
|
||||||
filter_key: WORD | pset_or_qto
|
|
||||||
filter_value: ESCAPED_STRING
|
|
||||||
pset_or_qto: /[A-Za-z0-9_]+/ "." /[A-Za-z0-9_]+/
|
|
||||||
lfunction: and | or
|
|
||||||
inverse_relationship: types | contains_elements
|
|
||||||
types: "*"
|
|
||||||
contains_elements: "@"
|
|
||||||
and: "&"
|
|
||||||
or: "|"
|
|
||||||
comparison: contains | morethanequalto | lessthanequalto | equal | morethan | lessthan
|
|
||||||
contains: "*="
|
|
||||||
morethanequalto: ">="
|
|
||||||
lessthanequalto: "<"
|
|
||||||
equal: "="
|
|
||||||
morethan: ">"
|
|
||||||
lessthan: "<"
|
|
||||||
|
|
||||||
// Embed common.lark for packaging
|
|
||||||
DIGIT: "0".."9"
|
|
||||||
HEXDIGIT: "a".."f"|"A".."F"|DIGIT
|
|
||||||
INT: DIGIT+
|
|
||||||
SIGNED_INT: ["+"|"-"] INT
|
|
||||||
DECIMAL: INT "." INT? | "." INT
|
|
||||||
_EXP: ("e"|"E") SIGNED_INT
|
|
||||||
FLOAT: INT _EXP | DECIMAL _EXP?
|
|
||||||
SIGNED_FLOAT: ["+"|"-"] FLOAT
|
|
||||||
NUMBER: FLOAT | INT
|
|
||||||
SIGNED_NUMBER: ["+"|"-"] NUMBER
|
|
||||||
_STRING_INNER: /.*?/
|
|
||||||
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
|
|
||||||
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
|
|
||||||
LCASE_LETTER: "a".."z"
|
|
||||||
UCASE_LETTER: "A".."Z"
|
|
||||||
LETTER: UCASE_LETTER | LCASE_LETTER
|
|
||||||
WORD: LETTER+
|
|
||||||
CNAME: ("_"|LETTER) ("_"|LETTER|DIGIT)*
|
|
||||||
WS_INLINE: (" "|/\\t/)+
|
|
||||||
WS: /[ \\t\\f\\r\\n]/+
|
|
||||||
CR : /\\r/
|
|
||||||
LF : /\\n/
|
|
||||||
NEWLINE: (CR? LF)+
|
|
||||||
|
|
||||||
%ignore WS // Disregard spaces in text
|
|
||||||
''')
|
|
||||||
|
|
||||||
start = l.parse(query)
|
|
||||||
return self.get_group(start)
|
|
||||||
|
|
||||||
def get_group(self, group):
|
|
||||||
lfunction = None
|
|
||||||
for child in group.children:
|
|
||||||
if child.data == 'query':
|
|
||||||
new_results = self.get_query(child)
|
|
||||||
if not lfunction:
|
|
||||||
results = new_results
|
|
||||||
elif lfunction == 'or':
|
|
||||||
results.extend(new_results)
|
|
||||||
elif lfunction == 'and':
|
|
||||||
results = list(set(results).intersection(new_results))
|
|
||||||
results = list(set(results))
|
|
||||||
elif child.data == 'lfunction':
|
|
||||||
lfunction = child.children[0].data
|
|
||||||
return results
|
|
||||||
|
|
||||||
def get_query(self, query):
|
|
||||||
for child in query.children:
|
|
||||||
if child.data == 'selector':
|
|
||||||
return self.get_selector(child)
|
|
||||||
elif child.data == 'group':
|
|
||||||
return self.get_group(child)
|
|
||||||
|
|
||||||
def get_selector(self, selector):
|
|
||||||
if len(selector.children) == 1:
|
|
||||||
inverse_relationship = None
|
|
||||||
class_or_guid_selector = selector.children[0]
|
|
||||||
else:
|
|
||||||
inverse_relationship = selector.children[0]
|
|
||||||
class_or_guid_selector = selector.children[1]
|
|
||||||
|
|
||||||
if class_or_guid_selector.data == 'class_selector':
|
|
||||||
results = self.get_class_selector(class_or_guid_selector)
|
|
||||||
elif class_or_guid_selector.data == 'guid_selector':
|
|
||||||
results = self.get_guid_selector(class_or_guid_selector)
|
|
||||||
|
|
||||||
if not inverse_relationship:
|
|
||||||
return results
|
|
||||||
return self.parse_inverse_relationship(results, inverse_relationship.children[0].data)
|
|
||||||
|
|
||||||
def parse_inverse_relationship(self, elements, inverse_relationship):
|
|
||||||
results = []
|
|
||||||
for element in elements:
|
|
||||||
if inverse_relationship == 'types':
|
|
||||||
if hasattr(element, 'Types') and element.Types:
|
|
||||||
results.extend(element.Types[0].RelatedObjects)
|
|
||||||
elif hasattr(element, 'ObjectTypeOf') and element.ObjectTypeOf:
|
|
||||||
results.extend(element.ObjectTypeOf[0].RelatedObjects)
|
|
||||||
elif inverse_relationship == 'contains_elements' \
|
|
||||||
and hasattr(element, 'ContainsElements'):
|
|
||||||
for relationship in element.ContainsElements:
|
|
||||||
results.extend(relationship.RelatedElements)
|
|
||||||
return results
|
|
||||||
|
|
||||||
def get_class_selector(self, class_selector):
|
|
||||||
elements = self.file.by_type(class_selector.children[0])
|
|
||||||
if len(class_selector.children) > 1 \
|
|
||||||
and class_selector.children[1].data == 'filter':
|
|
||||||
return self.filter_elements(elements, class_selector.children[1])
|
|
||||||
return elements
|
|
||||||
|
|
||||||
def filter_elements(self, elements, filter_rule):
|
|
||||||
results = []
|
|
||||||
key = filter_rule.children[0].children[0]
|
|
||||||
if not isinstance(key, str):
|
|
||||||
key = key.children[0] + '.' + key.children[1]
|
|
||||||
comparison = value = None
|
|
||||||
if len(filter_rule.children) > 1:
|
|
||||||
comparison = filter_rule.children[1].children[0].data
|
|
||||||
value = filter_rule.children[2].children[0][1:-1]
|
|
||||||
for element in elements:
|
|
||||||
element_value = IfcAttributeExtractor.get_element_key(element, key)
|
|
||||||
if not element_value:
|
|
||||||
continue
|
|
||||||
if not comparison \
|
|
||||||
or self.filter_element(element, element_value, comparison, value):
|
|
||||||
results.append(element)
|
|
||||||
return results
|
|
||||||
|
|
||||||
def filter_element(self, element, element_value, comparison, value):
|
|
||||||
if comparison == 'equal':
|
|
||||||
return str(element_value) == value
|
|
||||||
elif comparison == 'contains':
|
|
||||||
return value in str(element_value)
|
|
||||||
elif comparison == 'morethan':
|
|
||||||
return element_value > float(value)
|
|
||||||
elif comparison == 'lessthan':
|
|
||||||
return element_value < float(value)
|
|
||||||
elif comparison == 'morethanequalto':
|
|
||||||
return element_value >= float(value)
|
|
||||||
elif comparison == 'lessthanequalto':
|
|
||||||
return element_value <= float(value)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_guid_selector(self, guid_selector):
|
|
||||||
return [self.file.by_id(guid_selector.children[0])]
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
from math import pi
|
|
||||||
|
|
||||||
prefixes = {'EXA': 1e18, 'PETA': 1e15, 'TERA': 1e12, 'GIGA': 1e9, 'MEGA':
|
|
||||||
1e6, 'KILO': 1e3, 'HECTO': 1e2, 'DECA': 1e1, 'DECI': 1e-1, 'CENTI':
|
|
||||||
1e-2, 'MILLI': 1e-3, 'MICRO': 1e-6, 'NANO': 1e-9, 'PICO': 1e-12,
|
|
||||||
'FEMTO': 1e-15, 'ATTO': 1e-18}
|
|
||||||
|
|
||||||
unit_names = ['AMPERE', 'BECQUEREL', 'CANDELA', 'COULOMB',
|
|
||||||
'CUBIC_METRE', 'DEGREE CELSIUS', 'FARAD', 'GRAM', 'GRAY', 'HENRY',
|
|
||||||
'HERTZ', 'JOULE', 'KELVIN', 'LUMEN', 'LUX', 'MOLE', 'NEWTON', 'OHM',
|
|
||||||
'PASCAL', 'RADIAN', 'SECOND', 'SIEMENS', 'SIEVERT', 'SQUARE METRE',
|
|
||||||
'METRE', 'STERADIAN', 'TESLA', 'VOLT', 'WATT', 'WEBER']
|
|
||||||
|
|
||||||
si_conversions = {
|
|
||||||
'inch': 0.0254,
|
|
||||||
'foot': 0.3048,
|
|
||||||
'yard': 0.914,
|
|
||||||
'mile': 1609,
|
|
||||||
'square inch': 0.0006452,
|
|
||||||
'square foot': 0.09290,
|
|
||||||
'square yard': 0.83612736,
|
|
||||||
'acre': 4046.86,
|
|
||||||
'square mile': 2588881,
|
|
||||||
'cubic inch': 0.00001639,
|
|
||||||
'cubic foot': 0.02832,
|
|
||||||
'cubic yard': 0.7636,
|
|
||||||
'litre': 0.001,
|
|
||||||
'fluid ounce UK': 0.0000284130625,
|
|
||||||
'fluid ounce US': 0.00002957353,
|
|
||||||
'pint UK': 0.000568,
|
|
||||||
'pint US': 0.000473,
|
|
||||||
'gallon UK': 0.004546,
|
|
||||||
'gallon US': 0.003785,
|
|
||||||
'degree': pi/180,
|
|
||||||
'ounce': 0.02835,
|
|
||||||
'pound': 0.454,
|
|
||||||
'ton UK': 1016.0469088,
|
|
||||||
'ton US': 907.18474,
|
|
||||||
'lbf': 4.4482216153,
|
|
||||||
'kip': 4448.2216153,
|
|
||||||
'psi': 6894.7572932,
|
|
||||||
'ksi': 6894757.2932,
|
|
||||||
'minute': 60,
|
|
||||||
'hour': 3600,
|
|
||||||
'day': 86400,
|
|
||||||
'btu': 1055.056}
|
|
||||||
|
|
||||||
def get_prefix(text):
|
|
||||||
for prefix in prefixes.keys():
|
|
||||||
if prefix in text.upper():
|
|
||||||
return prefix
|
|
||||||
|
|
||||||
def get_prefix_multiplier(text):
|
|
||||||
if not text:
|
|
||||||
return 1
|
|
||||||
prefix = get_prefix(text)
|
|
||||||
if prefix:
|
|
||||||
return prefixes[prefix]
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def get_unit_name(text):
|
|
||||||
for name in unit_names:
|
|
||||||
if name in text.upper().replace('METER', 'METRE'):
|
|
||||||
return name
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import ifcopenshell
|
|
||||||
|
|
||||||
named_type = ifcopenshell.ifcopenshell_wrapper.named_type
|
|
||||||
aggregation_type = ifcopenshell.ifcopenshell_wrapper.aggregation_type
|
|
||||||
simple_type = ifcopenshell.ifcopenshell_wrapper.simple_type
|
|
||||||
type_declaration = ifcopenshell.ifcopenshell_wrapper.type_declaration
|
|
||||||
enumeration_type = ifcopenshell.ifcopenshell_wrapper.enumeration_type
|
|
||||||
entity_type = ifcopenshell.ifcopenshell_wrapper.entity
|
|
||||||
select_type = ifcopenshell.ifcopenshell_wrapper.select_type
|
|
||||||
attribute = ifcopenshell.ifcopenshell_wrapper.attribute
|
|
||||||
|
|
||||||
class ValidationError(Exception): pass
|
|
||||||
|
|
||||||
simple_type_python_mapping = {
|
|
||||||
# @todo should include unicode for Python2
|
|
||||||
"string": str,
|
|
||||||
"integer": int,
|
|
||||||
"real": float,
|
|
||||||
"number": float,
|
|
||||||
"boolean": bool,
|
|
||||||
"logical": bool, # still not implemented in IfcOpenShell
|
|
||||||
"binary": str # maps to a str of "0" and "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
def assert_valid_inverse(attr, val):
|
|
||||||
b1, b2 = attr.bound1(), attr.bound2()
|
|
||||||
invalid = len(val) < b1 or (b2 != -1 and len(val) > b2)
|
|
||||||
if invalid:
|
|
||||||
raise ValidationError("%r not valid for %s" % (val, attr))
|
|
||||||
return True
|
|
||||||
|
|
||||||
def assert_valid(attr, val):
|
|
||||||
if isinstance(attr, attribute):
|
|
||||||
attr_type = attr.type_of_attribute()
|
|
||||||
else:
|
|
||||||
attr_type = attr
|
|
||||||
|
|
||||||
type_wrappers = (named_type,)
|
|
||||||
if not isinstance(val, ifcopenshell.entity_instance):
|
|
||||||
# If val is not an entity instance we need to
|
|
||||||
# flatten the type declaration to something that
|
|
||||||
# maps to the python types
|
|
||||||
type_wrappers += (type_declaration,)
|
|
||||||
|
|
||||||
while isinstance(attr_type, type_wrappers):
|
|
||||||
attr_type = attr_type.declared_type()
|
|
||||||
|
|
||||||
if isinstance(attr_type, simple_type):
|
|
||||||
invalid = type(val) != simple_type_python_mapping[attr_type.declared_type()]
|
|
||||||
elif isinstance(attr_type, (entity_type, type_declaration)):
|
|
||||||
invalid = not isinstance(val, ifcopenshell.entity_instance) or not val.is_a(attr_type.name())
|
|
||||||
elif isinstance(attr_type, select_type):
|
|
||||||
invalid = not any(try_valid(x, val) for x in attr_type.select_list())
|
|
||||||
elif isinstance(attr_type, enumeration_type):
|
|
||||||
invalid = val not in attr_type.enumeration_items()
|
|
||||||
elif isinstance(attr_type, aggregation_type):
|
|
||||||
b1, b2 = attr_type.bound1(), attr_type.bound2()
|
|
||||||
ty = attr_type.type_of_element()
|
|
||||||
invalid = len(val) < b1 or (b2 != -1 and len(val) > b2) or not all(assert_valid(ty, v) for v in val)
|
|
||||||
else:
|
|
||||||
raise NotImplementedError("Not impl %s %s" % (type(attr_type), attr_type))
|
|
||||||
|
|
||||||
if invalid:
|
|
||||||
raise ValidationError("%r not valid for %s" % (val, attr))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def try_valid(attr, val):
|
|
||||||
try:
|
|
||||||
return assert_valid(attr, val)
|
|
||||||
except ValidationError as e:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def validate(f, logger):
|
|
||||||
schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(f.schema)
|
|
||||||
for inst in f:
|
|
||||||
entity = schema.declaration_by_name(inst.is_a())
|
|
||||||
|
|
||||||
for attr, val, is_derived in zip(entity.all_attributes(), inst, entity.derived()):
|
|
||||||
|
|
||||||
if val is None and not (is_derived or attr.optional()):
|
|
||||||
logger.error("Attribute %s.%s not optional" % (entity, attr))
|
|
||||||
|
|
||||||
if val is not None:
|
|
||||||
attr_type = attr.type_of_attribute()
|
|
||||||
try:
|
|
||||||
assert_valid(attr, val)
|
|
||||||
except ValidationError as e:
|
|
||||||
logger.error('In {}\n{}'.format(inst, e))
|
|
||||||
|
|
||||||
for attr in entity.all_inverse_attributes():
|
|
||||||
val = getattr(inst, attr.name())
|
|
||||||
try:
|
|
||||||
assert_valid_inverse(attr, val)
|
|
||||||
except ValidationError as e:
|
|
||||||
logger.error('In {}\n{}'.format(inst, e))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
|
|
||||||
for fn in sys.argv[1:]:
|
|
||||||
logger = logging.getLogger('validate')
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
print("Validating", fn)
|
|
||||||
validate(ifcopenshell.open(fn), logger)
|
|
||||||
Reference in New Issue
Block a user