diff --git a/src/ifcopenshell-python/ifcopenshell/geom/app.py b/src/ifcopenshell-python/ifcopenshell/geom/app.py index c61227fb3b..4f3bafca07 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/app.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/app.py @@ -480,7 +480,7 @@ class application(QtGui.QApplication): def load(self, fn): if fn in self.files: return - f = open(fn) + f = open(str(fn)) self.files[fn] = f for c in self.components: c.load_file(f, setting=self.settings) diff --git a/src/ifcopenshell-python/ifcopenshell/geom/code_editor_pane.py b/src/ifcopenshell-python/ifcopenshell/geom/code_editor_pane.py new file mode 100644 index 0000000000..f8f4fa994c --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/geom/code_editor_pane.py @@ -0,0 +1,162 @@ +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 + + + +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.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) + + 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 + + if not self.model: + print("please load a model first") + if not self.c: + self.c = self.Console({'model':self.model, 'viewer':self.viewer}) + 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 + + + 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() + + + 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],"","") + self.list = QtWidgets.QComboBox(self) + for snip_name in self.snippets.keys(): + self.list.addItem(snip_name) + self.layout.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.layout.addWidget(self.output) + + def replace_snippet (self,number): + self.editor.setPlainText(self.snippets[self.list.currentText()],"","") + + 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 diff --git a/src/ifcopenshell-python/ifcopenshell/test/snippets.config b/src/ifcopenshell-python/ifcopenshell/test/snippets.config new file mode 100644 index 0000000000..ef6d3e2699 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/test/snippets.config @@ -0,0 +1,25 @@ + +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 new file mode 100644 index 0000000000..df6f61de35 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/test/viewer_scripting_test.py @@ -0,0 +1,51 @@ +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