diff --git a/src/ifcopenshell-python/ifcopenshell/geom/app.py b/src/ifcopenshell-python/ifcopenshell/geom/app.py index 4f3bafca07..f7cbd3d87c 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/app.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/app.py @@ -1,5 +1,6 @@ from __future__ import print_function +import os import sys import time import operator @@ -7,10 +8,17 @@ import functools import OCC.AIS -from collections import defaultdict, Iterable +from collections import defaultdict, Iterable, OrderedDict + +os.environ['QT_API'] = 'pyqt4' +try: + from pyqode.qt import QtCore +except: pass from PyQt4 import QtGui, QtCore +from .code_editor_pane import code_edit + try: from OCC.Display.pyqt4Display import qtViewer3d except: import OCC.Display @@ -27,13 +35,73 @@ except: from .main import settings, iterator from .occ_utils import display_shape -from .. import open, get_supertype +from .. import open as open_ifc_file +from .. import get_supertype # Depending on Python version and what not there may or may not be a QString try: from PyQt4.QtCore import QString except ImportError: QString = str + +class configuration(object): + def __init__(self): + try: + import ConfigParser + Cfg = ConfigParser.RawConfigParser + except: + import configparser + Cfg = 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(QtGui.QApplication): @@ -168,7 +236,7 @@ class application(QtGui.QApplication): class property_table(QtGui.QWidget): - instanceSelected = QtCore.pyqtSignal([object]) + def __init__(self): QtGui.QWidget.__init__(self) self.layout= QtGui.QVBoxLayout(self) @@ -184,39 +252,49 @@ class application(QtGui.QApplication): #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 = QtGui.QScrollArea() self.scroll.setWidgetResizable(True) - scrollContent = QtGui.QWidget(self.scroll) - # print ("properties for selection {}".format(self.prop_dict.get(str(product)))) prop_sets = self.prop_dict.get(str(product)) + if prop_sets is not None: - for k,v in prop_sets.items(): + for k,v in prop_sets: group_box = QtGui.QGroupBox() group_box.setTitle(k) group_layout = QtGui.QVBoxLayout() - group_box.setLayout(group_layout) + for name, value in v.items(): prop_name = str(name) - value_str = value.wrappedValue - if isinstance(value_str,unicode): + + 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) - # print (value_str, type(value_str)) - type_str = value.is_a() - label = QtGui.QLabel(prop_name+" : "+value_str+" : "+type_str) + + if hasattr(value, "is_a"): + type_str = " (%s)" % value.is_a() + else: + type_str = "" + label = QtGui.QLabel("%s: %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 = QtGui.QLabel("No IfcPropertySets asscociated with selected entity instance" ) @@ -224,31 +302,46 @@ class application(QtGui.QApplication): def load_file(self, f, **kwargs): - products = list(f.by_type("IfcProduct")) - for p in products: - propset_dict={} + 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 not is_def_by.is_a("IfcRelDefinesByProperties"): continue - if is_def_by.RelatingPropertyDefinition is not None: - prop_def=is_def_by.RelatingPropertyDefinition - 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] - else: - for prop in prop_def.HasProperties: - if prop.is_a("IfcPropertySingleValue"): - props[prop.Name]=prop.NominalValue - - propset_dict[prop_set_name]=props - + 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, e: + import traceback print("failed to load properties: {}".format(e)) - self.prop_dict[str(p)]=propset_dict - print ("property set dictionary has {} entries".format(len(propset_dict))) + 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): @@ -436,11 +529,16 @@ class application(QtGui.QApplication): self.tabs.addTab(self.tree, 'Decomposition') self.tabs.addTab(self.tree2, 'Types') self.tabs.addTab(self.propview, "Properties") - splitter.addWidget(self.canvas) + splitter2 = QtGui.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.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') @@ -450,7 +548,6 @@ class application(QtGui.QApplication): self.tree.instanceSelected.connect(self.makeSelectionHandler(self.tree)) self.tree2.instanceSelected.connect(self.makeSelectionHandler(self.tree2)) self.canvas.instanceSelected.connect(self.makeSelectionHandler(self.canvas)) - self.propview.instanceSelected.connect(self.makeSelectionHandler(self.propview)) 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)) @@ -480,7 +577,10 @@ class application(QtGui.QApplication): def load(self, fn): if fn in self.files: return - f = open(str(fn)) + 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() diff --git a/src/ifcopenshell-python/ifcopenshell/geom/code_editor_pane.py b/src/ifcopenshell-python/ifcopenshell/geom/code_editor_pane.py index f8f4fa994c..70594470cc 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/code_editor_pane.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/code_editor_pane.py @@ -1,162 +1,152 @@ +from __future__ import print_function + import os import sys import logging -from pyqode.core.panels import CheckerPanel -logging.basicConfig(level=logging.CRITICAL) -from pyqode.qt import QtCore, QtGui, QtWidgets -print('Qt version:%s' % QtCore.__version__) from code import InteractiveConsole -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 +from PyQt4 import QtCore, QtGui +try: + from PyQt4 import QtWidgets +except: + 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: + has_pyqode = False + CodeEdit = QtWidgets.QPlainTextEdit + + +class StdoutRedirector(object): -class StdoutRedirector(QtGui.QTextEdit): '''A class for redirecting stdout to this Text widget.''' + def __init__(self, widget): self.widget=widget self.isError = False + def write(self,str): - # print(str) - # self.widget.append(str) self.widget.moveCursor(QtGui.QTextCursor.End) if self.isError: self.widget.setTextColor(QtCore.Qt.red) else: - self.widget.setTextColor(QtCore.Qt.black) + self.widget.setTextColor(QtCore.Qt.white) self.widget.insertPlainText(str) self.widget.moveCursor(QtGui.QTextCursor.End) - # scroll = self.widget.verticalScrollBar() - # scroll.setValue(scroll.maximum()) class code_edit(QtGui.QWidget): - class Console(InteractiveConsole): - def __init__(*args): InteractiveConsole.__init__(*args) + class Console(InteractiveConsole): + def __init__(*args): + InteractiveConsole.__init__(*args) def enter(self, source): - source = self.preprocess(source) self.runcode(source) - @staticmethod - def preprocess(source): return source - def runCode(self): - c =self.Console({'model':self.files[0]}) - c.enter("print (model.by_type('IfcDoor')") - def runCode(self): - sys.stdout = StdoutRedirector(self.output) - err_redirect= StdoutRedirector(self.output) - err_redirect.isError=True - sys.stderr = err_redirect + sys.stderr = StdoutRedirector(self.output) + sys.stderr.isError=True if not self.model: - print("please load a model first") - if not self.c: - self.c = self.Console({'model':self.model, 'viewer':self.viewer}) + print("please load a model first", file=sys.stderr) else: - # self.console.start_process(sys.executable,args= [ os.path.join(os.getcwd(), 'interactive_process.py')],env={'model':self.model}) 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}) - self.c.enter def __init__(self,viewer,snippets=None): - - logging.basicConfig(level=logging.CRITICAL) + self.model=None self.viewer = viewer QtGui.QWidget.__init__(self) self.layout= QtGui.QVBoxLayout(self) self.setLayout(self.layout) self.c = None - - + + self.tools = QtGui.QHBoxLayout(self) + self.layout.addLayout(self.tools) + + self.runbutton = QtGui.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() - - # start the backend as soon as possible - # editor.backend.start('server.py') - editor.backend.start(server.__file__) - #--- core panels - editor.panels.append(panels.FoldingPanel()) - editor.panels.append(panels.LineNumberPanel()) - # editor.panels.append(panels.CheckerPanel()) - editor.panels.append(panels.SearchAndReplacePanel(), - panels.SearchAndReplacePanel.Position.BOTTOM) - editor.panels.append(panels.EncodingPanel(), api.Panel.Position.TOP) - # add a context menu separator between editor's - # builtin action and the python specific actions - editor.add_separator() - - #--- python specific panels - editor.panels.append(pypanels.QuickDocPanel(), api.Panel.Position.BOTTOM) - sh = editor.modes.append(PythonSH(editor.document())) - # sh.color_scheme = ColorScheme('monokai') - #--- core modes - 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()) - - #--- python specific modes - 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() - + 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 not self.snippets: - self.editor.setPlainText("""print('hello from console') -for w in model.by_type('IfcWall'): - print ("wall with GlobalId " + str(w.GlobalId)) - ""","","") - else: - self.editor.setPlainText(self.snippets.values()[0],"","") + 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.layout.addWidget(self.list) + self.tools.addWidget(self.list) QtCore.QObject.connect(self.list, QtCore.SIGNAL("currentIndexChanged(int)"), self.replace_snippet) - # self.textedit = QtGui.QTextEdit() - # self.scrollLayout.addWidget(self.textedit) self.layout.addWidget(self.editor) self.output = QtGui.QTextEdit() - self.output.setReadOnly(False) - self.output.setStyleSheet('font-size: 10pt; font-family: Consolas,Courier;') + 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): - self.editor.setPlainText(self.snippets[self.list.currentText()],"","") + 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}) - # c.enter("print (model.by_type('IfcWall'))") - sys.stdout = sys.__stdout__ \ No newline at end of file + self.c = self.Console({'model':self.model, 'selection':None, 'viewer':self.viewer}) + sys.stdout = sys.__stdout__ diff --git a/src/ifcopenshell-python/ifcopenshell/test/snippets.config b/src/ifcopenshell-python/ifcopenshell/test/snippets.config deleted file mode 100644 index ef6d3e2699..0000000000 --- a/src/ifcopenshell-python/ifcopenshell/test/snippets.config +++ /dev/null @@ -1,25 +0,0 @@ - -quickload_model = "D:\Project1.ifc" - -["snippets"] -'print all wall ids' = """# 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)) -""" -'print all properties of current selection' = """# 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") -""" diff --git a/src/ifcopenshell-python/ifcopenshell/test/viewer_scripting_test.py b/src/ifcopenshell-python/ifcopenshell/test/viewer_scripting_test.py deleted file mode 100644 index df6f61de35..0000000000 --- a/src/ifcopenshell-python/ifcopenshell/test/viewer_scripting_test.py +++ /dev/null @@ -1,51 +0,0 @@ -from pyqode.qt import QtCore, QtGui, QtWidgets -from configobj import ConfigObj - -from ..geom.app import application -try: - from ..geom.code_editor_pane import code_edit -except ImportError: - print ("code editor or one of its dependencies (pyQode) could not be found. Please make sure to install pyQode") -class my_app(application): - - class toolbar(QtGui.QToolBar): - def __init__(self): - QtGui.QToolBar.__init__(self) - self.widget.append() - - def __init__(self): - application.__init__(self) - # self.window = my_app.window() - self.window.setWindowTitle("Interactive IfcOpenShell Viewer with REPL-like funcionality") - tb = self.window.addToolBar("File") - self.window.resize(1024,720) - # loadButton = QtGui.QAction(QtGui.QIcon("new.bmp"),"new",self) - loadButton = QtGui.QAction("Quickload",self) - loadButton.triggered.connect(self.loadExampleFile) - loadButton.setShortcut(QtGui.QKeySequence("CTRL+L")) - tb.addAction(loadButton) - # zoomAllButton = QtGui.QAction(QtGui.QIcon("new.bmp"),"new",self) - zoomAllButton = QtGui.QAction("zoom All",self) - zoomAllButton.triggered.connect(self.zoomAll) - tb.addAction(zoomAllButton) - self.config = ConfigObj("snippets.config") - print (self.config["snippets"]) - - snippets = self.config["snippets"] - - self.codeedit= code_edit(self.canvas, snippets ) - - self.components.append(self.codeedit) - self.window.centralWidget().addWidget(self.codeedit) - self.runButton = QtGui.QAction("execute code (Strg+P)",self) - self.runButton.setShortcut(QtGui.QKeySequence("CTRL+P")) - self.runButton.triggered.connect(self.codeedit.runCode) - - tb.addAction(self.runButton) - def loadExampleFile(self): - self.load(self.config["quickload_model"]) - def zoomAll(self): - self.canvas._display.FitAll() - - -my_app().start() \ No newline at end of file