First Commit
This commit is contained in:
@@ -0,0 +1,691 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import functools
|
||||
import multiprocessing
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
|
||||
try:
|
||||
from OCC.Core import AIS # noqa: F401
|
||||
|
||||
USE_OCCT_HANDLE = False
|
||||
except ImportError:
|
||||
|
||||
USE_OCCT_HANDLE = True
|
||||
|
||||
from collections import OrderedDict, defaultdict
|
||||
from collections.abc import Iterable
|
||||
|
||||
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:
|
||||
|
||||
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 .. import open as open_ifc_file
|
||||
from .. import version as ifcopenshell_version
|
||||
from .main import iterator, settings
|
||||
from .occ_utils import display_shape
|
||||
|
||||
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:
|
||||
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:
|
||||
if not relDefinesByProperties.is_a("IfcRelDefinesByProperties"):
|
||||
continue
|
||||
print("[{0}]".format(relDefinesByProperties.RelatingPropertyDefinition.Name))
|
||||
if relDefinesByProperties.RelatingPropertyDefinition.is_a("IfcPropertySet"):
|
||||
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 associated 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]
|
||||
|
||||
if USE_OCCT_HANDLE:
|
||||
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.0)
|
||||
|
||||
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("dimensionality", W.CURVES_SURFACES_AND_SOLIDS)
|
||||
setting.set("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()
|
||||
Reference in New Issue
Block a user