| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| """This contains nativeifc status widgets and functionality""" |
|
|
| import csv |
| import os |
|
|
| import FreeCAD |
| import FreeCADGui |
|
|
| translate = FreeCAD.Qt.translate |
| PARAMS = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/NativeIFC") |
| text_on = translate("BIM", "Strict IFC mode is ON (all objects are IFC)") |
| text_off = translate("BIM", "Strict IFC mode is OFF (IFC and non-IFC objects allowed)") |
|
|
|
|
| def set_status_widget(statuswidget): |
| """Adds the needed controls to the status bar""" |
|
|
| from PySide import QtGui |
| import Arch_rc |
|
|
| |
| lock_button = QtGui.QAction() |
| icon = QtGui.QIcon(":/icons/IFC.svg") |
| lock_button.setIcon(icon) |
| lock_button.setCheckable(True) |
| doc = FreeCAD.ActiveDocument |
| statuswidget.addAction(lock_button) |
| statuswidget.lock_button = lock_button |
| if doc and "IfcFilePath" in doc.PropertiesList: |
| checked = True |
| else: |
| checked = False |
| |
| lock_button.setChecked(checked) |
| on_toggle_lock(checked, noconvert=True) |
| lock_button.triggered.connect(on_toggle_lock) |
| set_properties_editor(statuswidget) |
|
|
|
|
| def set_properties_editor(statuswidget): |
| """Adds additional buttons to the properties editor""" |
|
|
| if hasattr(statuswidget, "propertybuttons"): |
| statuswidget.propertybuttons.show() |
| else: |
| from PySide import QtCore, QtGui |
|
|
| mw = FreeCADGui.getMainWindow() |
| editor = mw.findChild(QtGui.QTabWidget, "propertyTab") |
| if editor: |
| pTabCornerWidget = QtGui.QWidget() |
| pButton1 = QtGui.QToolButton(pTabCornerWidget) |
| pButton1.setText("") |
| pButton1.setToolTip(translate("BIM", "Add IFC property...")) |
| pButton1.setIcon(QtGui.QIcon(":/icons/IFC.svg")) |
| pButton1.clicked.connect(on_add_property) |
| pButton2 = QtGui.QToolButton(pTabCornerWidget) |
| pButton2.setText("") |
| pButton2.setToolTip(translate("BIM", "Add standard IFC Property Set...")) |
| pButton2.setIcon(QtGui.QIcon(":/icons/BIM_IfcProperties.svg")) |
| pButton2.clicked.connect(on_add_pset) |
| pHLayout = QtGui.QHBoxLayout(pTabCornerWidget) |
| pHLayout.addWidget(pButton1) |
| pHLayout.addWidget(pButton2) |
| pHLayout.setSpacing(2) |
| pHLayout.setContentsMargins(2, 2, 0, 0) |
| pHLayout.insertStretch(0) |
| editor.setCornerWidget(pTabCornerWidget, QtCore.Qt.BottomRightCorner) |
| statuswidget.propertybuttons = pTabCornerWidget |
| QtCore.QTimer.singleShot(0, pTabCornerWidget.show) |
|
|
|
|
| def on_add_property(): |
| """When the 'add property' button is clicked""" |
|
|
| sel = FreeCADGui.Selection.getSelection() |
| if not sel: |
| return |
| from PySide import QtGui |
| from . import ifc_psets |
|
|
| obj = sel[0] |
| psets = list(set([obj.getGroupOfProperty(p) for p in obj.PropertiesList])) |
| psets = [p for p in psets if p] |
| psets = [p for p in psets if p not in ["Base", "IFC", "Geometry"]] |
| mw = FreeCADGui.getMainWindow() |
| editor = mw.findChild(QtGui.QTabWidget, "propertyTab") |
| pset = None |
| if editor: |
| wid = editor.currentWidget() |
| if wid and wid.objectName() == "propertyEditorData": |
| if wid.currentIndex().parent(): |
| pset = wid.currentIndex().parent().data() |
| else: |
| pset = wid.currentIndex().data() |
| form = FreeCADGui.PySideUic.loadUi(":/ui/dialogAddProperty.ui") |
| |
| form.move(mw.frameGeometry().topLeft() + mw.rect().center() - form.rect().center()) |
| form.field_pset.clear() |
| form.field_pset.addItems(psets) |
| if pset and (pset in psets): |
| form.field_pset.setCurrentIndex(psets.index(pset)) |
| |
| |
| result = form.exec_() |
| if not result: |
| return |
| pname = form.field_name.text() |
| if pname in obj.PropertiesList: |
| print("DEBUG: property already exists", pname) |
| return |
| pset = form.field_pset.currentText() |
| if not pset: |
| |
| t = translate("BIM", "No Property set provided") |
| FreeCAD.Console.PrintError(t + "\n") |
| ptype = form.field_type.currentIndex() |
| ptype = [ |
| "IfcLabel", |
| "IfcBoolean", |
| "IfcInteger", |
| "IfcReal", |
| "IfcLengthMeasure", |
| "IfcAreaMeasure", |
| ][ptype] |
| fctype = ifc_psets.get_freecad_type(ptype) |
| FreeCAD.ActiveDocument.openTransaction(translate("BIM", "add property")) |
| for obj in sel: |
| obj.addProperty(fctype, pname, pset, ptype + ":" + pname) |
| ifc_psets.edit_pset(obj, pname, force=True) |
| FreeCAD.ActiveDocument.commitTransaction() |
|
|
|
|
| def on_add_pset(): |
| """When the 'add pset' button is pressed""" |
|
|
| def read_csv(csvfile): |
| result = {} |
| if os.path.exists(csvfile): |
| with open(csvfile, "r") as f: |
| reader = csv.reader(f, delimiter=";") |
| for row in reader: |
| result[row[0]] = row[1:] |
| return result |
|
|
| def get_fcprop(ifcprop): |
| if ifcprop == "IfcLengthMeasure": |
| return "App::PropertyDistance" |
| elif ifcprop == "IfcPositiveLengthMeasure": |
| return "App::PropertyLength" |
| elif ifcprop in ["IfcBoolean", "IfcLogical"]: |
| return "App::PropertyBool" |
| elif ifcprop == "IfcInteger": |
| return "App::PropertyInteger" |
| elif ifcprop == "IfcReal": |
| return "App::PropertyFloat" |
| elif ifcprop == "IfcAreaMeasure": |
| return "App::PropertyArea" |
| return "App::PropertyString" |
|
|
| sel = FreeCADGui.Selection.getSelection() |
| if not sel: |
| return |
| from . import ifc_psets |
|
|
| obj = sel[0] |
| mw = FreeCADGui.getMainWindow() |
| |
| psetpath = os.path.join( |
| FreeCAD.getResourceDir(), "Mod", "BIM", "Presets", "pset_definitions.csv" |
| ) |
| custompath = os.path.join(FreeCAD.getUserAppDataDir(), "BIM", "CustomPsets.csv") |
| psetdefs = read_csv(psetpath) |
| psetdefs.update(read_csv(custompath)) |
| psetkeys = list(psetdefs.keys()) |
| psetkeys.sort() |
| form = FreeCADGui.PySideUic.loadUi(":/ui/dialogAddPSet.ui") |
| |
| form.move(mw.frameGeometry().topLeft() + mw.rect().center() - form.rect().center()) |
| form.field_pset.clear() |
| form.field_pset.addItems(psetkeys) |
| |
| result = form.exec_() |
| if not result: |
| return |
| pset = form.field_pset.currentText() |
| existing_psets = list(set([obj.getGroupOfProperty(p) for p in obj.PropertiesList])) |
| if pset in existing_psets: |
| t = translate("BIM", "Property set already exists") |
| FreeCAD.Console.PrintError(t + ": " + pset + "\n") |
| return |
| props = [psetdefs[pset][i : i + 2] for i in range(0, len(psetdefs[pset]), 2)] |
| props = [[p[0], p[1]] for p in props] |
| FreeCAD.ActiveDocument.openTransaction(translate("BIM", "add property set")) |
| for obj in sel: |
| existing_psets = list(set([obj.getGroupOfProperty(p) for p in obj.PropertiesList])) |
| if pset not in existing_psets: |
| ifc_psets.add_pset(obj, pset) |
| for prop in props: |
| if prop[0] in obj.PropertiesList: |
| t = translate("BIM", "Property already exists") |
| FreeCAD.Console.PrintWarning(t + ": " + obj.Label + "," + prop[0] + "\n") |
| else: |
| obj.addProperty(get_fcprop(prop[1]), prop[0], pset, prop[1] + ":" + prop[0]) |
| FreeCAD.ActiveDocument.commitTransaction() |
|
|
|
|
| def on_toggle_lock(checked=None, noconvert=False, setchecked=False): |
| """When the toolbar button is pressed""" |
|
|
| if checked is None: |
| checked = get_lock_status() |
| set_menu(checked) |
| set_button(checked, setchecked) |
| if not noconvert: |
| if checked: |
| lock_document() |
| else: |
| unlock_document() |
|
|
|
|
| def on_open(): |
| """What happens when opening an existing document""" |
|
|
| pass |
|
|
|
|
| def on_activate(): |
| """What happens when activating a document""" |
|
|
| from PySide import QtGui |
|
|
| |
| set_menu(False) |
| if FreeCADGui.activeWorkbench().name() != "BIMWorkbench": |
| return |
| doc = FreeCAD.ActiveDocument |
| if doc and "IfcFilePath" in doc.PropertiesList: |
| checked = True |
| else: |
| checked = False |
| mw = FreeCADGui.getMainWindow() |
| statuswidget = mw.findChild(QtGui.QToolBar, "BIMStatusWidget") |
| if hasattr(statuswidget, "lock_button"): |
| statuswidget.lock_button.setChecked(checked) |
| on_toggle_lock(checked, noconvert=True) |
|
|
|
|
| def on_new(): |
| """What happens when creating a new document""" |
|
|
| pass |
|
|
|
|
| def set_menu(locked=False): |
| """Sets the File menu items""" |
|
|
| from PySide import QtGui |
|
|
| |
| mw = FreeCADGui.getMainWindow() |
| wb = FreeCADGui.activeWorkbench() |
| save_action = mw.findChild(QtGui.QAction, "Std_Save") |
| if locked and "IFC_Save" in FreeCADGui.listCommands(): |
| if not hasattr(FreeCADGui, "IFC_WBManipulator"): |
| FreeCADGui.IFC_WBManipulator = IFC_WBManipulator() |
| |
| |
| FreeCADGui.IFC_saveshortcut = save_action.shortcut() |
| save_action.setShortcut("") |
| FreeCADGui.addWorkbenchManipulator(FreeCADGui.IFC_WBManipulator) |
| wb.reloadActive() |
| else: |
| if hasattr(FreeCADGui, "IFC_saveshortcut"): |
| save_action.setShortcut(FreeCADGui.IFC_saveshortcut) |
| del FreeCADGui.IFC_saveshortcut |
| if hasattr(FreeCADGui, "IFC_WBManipulator"): |
| FreeCADGui.removeWorkbenchManipulator(FreeCADGui.IFC_WBManipulator) |
| del FreeCADGui.IFC_WBManipulator |
| wb.reloadActive() |
|
|
|
|
| def set_button(checked=False, setchecked=False): |
| """Sets the lock button""" |
|
|
| from PySide import QtGui |
|
|
| mw = FreeCADGui.getMainWindow() |
| statuswidget = mw.findChild(QtGui.QToolBar, "BIMStatusWidget") |
| if hasattr(statuswidget, "lock_button"): |
| lock_button = statuswidget.lock_button |
| if checked: |
| lock_button.setToolTip(text_on) |
| icon = QtGui.QIcon(":/icons/IFC.svg") |
| lock_button.setIcon(icon) |
| if setchecked: |
| lock_button.setChecked(True) |
| else: |
| lock_button.setToolTip(text_off) |
| image = QtGui.QImage(":/icons/IFC.svg") |
| grayscale = image.convertToFormat(QtGui.QImage.Format_Grayscale8) |
| grayscale = grayscale.convertToFormat(image.format()) |
| grayscale.setAlphaChannel(image) |
| icon = QtGui.QIcon(QtGui.QPixmap.fromImage(grayscale)) |
| lock_button.setIcon(icon) |
| if setchecked: |
| lock_button.setChecked(False) |
|
|
|
|
| def unlock_document(): |
| """Unlocks the active document""" |
|
|
| from . import ifc_tools |
|
|
| doc = FreeCAD.ActiveDocument |
| if not doc: |
| return |
| if "IfcFilePath" in doc.PropertiesList: |
| |
| doc.openTransaction("Unlock document") |
| children = [o for o in doc.Objects if not o.InList] |
| project = None |
| if children: |
| project = ifc_tools.create_document_object(doc, filename=doc.IfcFilePath, silent=True) |
| project.Group = children |
| props = ["IfcFilePath", "Modified", "Proxy", "Schema"] |
| props += [p for p in doc.PropertiesList if doc.getGroupOfProperty(p) == "IFC"] |
| for prop in props: |
| doc.setPropertyStatus(prop, "-LockDynamic") |
| doc.removeProperty(prop) |
| if project: |
| project.Modified = True |
| doc.commitTransaction() |
| doc.recompute() |
|
|
|
|
| def lock_document(): |
| """Locks the active document""" |
|
|
| from . import ifc_tools |
| from importers import exportIFC |
| from . import ifc_geometry |
| from . import ifc_export |
| from PySide import QtCore |
|
|
| doc = FreeCAD.ActiveDocument |
| if not doc: |
| return |
| products = [] |
| spatial = [] |
| ifcfile = None |
| if "IfcFilePath" not in doc.PropertiesList: |
| |
| projects = [o for o in doc.Objects if getattr(o, "Class", None) == "IfcProject"] |
| if len(projects) == 1: |
| |
| project = projects[0] |
| children = project.OutListRecursive |
| rest = [o for o in doc.Objects if o not in children and o != project] |
| doc.openTransaction("Lock document") |
| ifc_tools.convert_document(doc, filename=project.IfcFilePath, strategy=3, silent=True) |
| ifcfile = doc.Proxy.ifcfile |
| if rest: |
| |
| objs = find_toplevel(rest) |
| prefs, context = ifc_export.get_export_preferences(ifcfile) |
| products = exportIFC.export(objs, ifcfile, preferences=prefs) |
| for product in products.values(): |
| if not getattr(product, "ContainedInStructure", None): |
| if not getattr(product, "FillsVoids", None): |
| if not getattr(product, "VoidsElements", None): |
| if not getattr(product, "Decomposes", None): |
| new = ifc_tools.create_object(product, doc, ifcfile) |
| children = ifc_tools.create_children( |
| new, ifcfile, recursive=True |
| ) |
| for o in [new] + children: |
| ifc_geometry.add_geom_properties(o) |
| for n in [o.Name for o in rest]: |
| doc.removeObject(n) |
| else: |
| |
| pass |
| doc.removeObject(project.Name) |
| doc.Modified = True |
| |
| if not doc.Objects: |
| ifc_tools.create_children(doc, ifcfile, recursive=True) |
| doc.commitTransaction() |
| doc.recompute() |
| elif len(projects) > 1: |
| |
| FreeCAD.Console.PrintError( |
| "Unable to lock this document because it contains several IFC documents\n" |
| ) |
| QtCore.QTimer.singleShot(100, on_toggle_lock) |
| elif doc.Objects: |
| |
| doc.openTransaction("Lock document") |
| objs = find_toplevel(doc.Objects) |
| deletelist = [o.Name for o in doc.Objects] |
| |
| ifc_export.direct_conversion(objs, doc) |
| for n in deletelist: |
| if doc.getObject(n): |
| doc.removeObject(n) |
| doc.IfcFilePath = "" |
| doc.Modified = True |
| doc.commitTransaction() |
| doc.recompute() |
| else: |
| |
| doc.openTransaction("Create IFC document") |
| ifc_tools.convert_document(doc) |
| doc.commitTransaction() |
| doc.recompute() |
| |
| if "IfcFilePath" in doc.PropertiesList: |
| create = True |
| for o in doc.Objects: |
| |
| if getattr(o, "IfcClass", "") in ("IfcSite", "IfcBuilding"): |
| create = False |
| break |
| if create: |
| if not ifcfile: |
| ifcfile = doc.Proxy.ifcfile |
| ifc_tools.create_children(doc, ifcfile, recursive=False) |
|
|
|
|
| def find_toplevel(objs): |
| """Finds the top-level objects from the list""" |
|
|
| |
| nobjs = [] |
| for obj in objs: |
| for parent in obj.InListRecursive: |
| if parent in objs: |
| |
| if hasattr(parent, "Host") and parent.Host == obj: |
| nobjs.append(obj) |
| elif hasattr(parent, "Hosts") and obj in parent.Hosts: |
| nobjs.append(obj) |
| break |
| else: |
| nobjs.append(obj) |
| |
| objs = filter_out(nobjs) |
| return objs |
|
|
|
|
| def filter_out(objs): |
| """Filter out objects that should not be converted to IFC""" |
|
|
| import Draft |
|
|
| nobjs = [] |
| for obj in objs: |
| if obj.isDerivedFrom("Part::Feature"): |
| nobjs.append(obj) |
| elif obj.isDerivedFrom("Mesh::Feature"): |
| nobjs.append(obj) |
| elif Draft.is_group(obj): |
| if filter_out(obj.Group): |
| |
| nobjs.append(obj) |
| else: |
| print("DEBUG: Filtering out", obj.Label) |
| elif obj.isDerivedFrom("App::Feature"): |
| if Draft.get_type(obj) in ( |
| "Dimension", |
| "LinearDimension", |
| "Layer", |
| "Text", |
| "DraftText", |
| ): |
| nobjs.append(obj) |
| else: |
| print("DEBUG: Filtering out", obj.Label) |
| else: |
| print("DEBUG: Filtering out", obj.Label) |
| return nobjs |
|
|
|
|
| def get_lock_status(): |
| """Returns the status of the IFC lock button""" |
|
|
| if not FreeCAD.GuiUp: |
| return PARAMS.GetBool("SingleDoc") |
| from PySide import QtGui |
|
|
| mw = FreeCADGui.getMainWindow() |
| statuswidget = mw.findChild(QtGui.QToolBar, "BIMStatusWidget") |
| if hasattr(statuswidget, "lock_button"): |
| return statuswidget.lock_button.isChecked() |
| else: |
| return False |
|
|
|
|
| |
| |
| class IFC_WBManipulator: |
| def modifyMenuBar(self): |
| return [ |
| {"remove": "Std_Save"}, |
| {"remove": "Std_SaveAs"}, |
| {"insert": "IFC_Save", "menuItem": "Std_SaveCopy"}, |
| {"insert": "IFC_SaveAs", "menuItem": "Std_SaveCopy"}, |
| ] |
|
|
| def modifyToolBars(self): |
| return [ |
| {"remove": "Std_Save"}, |
| {"append": "IFC_Save", "toolBar": "File"}, |
| ] |
|
|