1184 lines
46 KiB
Python
1184 lines
46 KiB
Python
# ***************************************************************************
|
|
# * *
|
|
# * Copyright (c) 2017 Yorik van Havre <yorik@uncreated.net> *
|
|
# * *
|
|
# * This program is free software; you can redistribute it and/or modify *
|
|
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
|
# * as published by the Free Software Foundation; either version 2 of *
|
|
# * the License, or (at your option) any later version. *
|
|
# * for detail see the LICENCE text file. *
|
|
# * *
|
|
# * This program 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 Library General Public License for more details. *
|
|
# * *
|
|
# * You should have received a copy of the GNU Library General Public *
|
|
# * License along with this program; if not, write to the Free Software *
|
|
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
|
# * USA *
|
|
# * *
|
|
# ***************************************************************************
|
|
|
|
"""This module contains FreeCAD commands for the BIM workbench"""
|
|
|
|
|
|
import sys
|
|
import os
|
|
import FreeCAD
|
|
import FreeCADGui
|
|
|
|
QT_TRANSLATE_NOOP = FreeCAD.Qt.QT_TRANSLATE_NOOP
|
|
translate = FreeCAD.Qt.translate
|
|
|
|
|
|
import importlib
|
|
import inspect
|
|
|
|
|
|
tests = [
|
|
"testAll",
|
|
"testIFC4",
|
|
"testHierarchy",
|
|
"testSites",
|
|
"testBuildings",
|
|
"testStoreys",
|
|
"testUndefined",
|
|
"testSolid",
|
|
"testQuantities",
|
|
"testCommonPsets",
|
|
"testPsets",
|
|
"testMaterials",
|
|
"testStandards",
|
|
"testExtrusions",
|
|
"testStandardCases",
|
|
"testTinyLines",
|
|
"testRectangleProfileDef",
|
|
]
|
|
|
|
|
|
class BIM_Preflight:
|
|
def GetResources(self):
|
|
return {
|
|
"Pixmap": "BIM_Preflight",
|
|
"MenuText": QT_TRANSLATE_NOOP("BIM_Preflight", "Preflight checks..."),
|
|
"ToolTip": QT_TRANSLATE_NOOP(
|
|
"BIM_Preflight",
|
|
"Checks several characteristics of this model before exporting to IFC",
|
|
),
|
|
}
|
|
|
|
def IsActive(self):
|
|
v = hasattr(FreeCADGui.getMainWindow().getActiveWindow(), "getSceneGraph")
|
|
return v
|
|
|
|
def Activated(self):
|
|
FreeCADGui.BIMPreflightDone = False
|
|
FreeCADGui.Control.showDialog(BIM_Preflight_TaskPanel())
|
|
|
|
|
|
class BIM_Preflight_TaskPanel:
|
|
|
|
def __init__(self):
|
|
from PySide import QtCore, QtGui
|
|
|
|
self.results = {} # to store the result message
|
|
self.culprits = {} # to store objects to highlight
|
|
self.rform = None # to store the results dialog
|
|
self.form = FreeCADGui.PySideUic.loadUi(":/ui/dialogPreflight.ui")
|
|
self.form.setWindowIcon(QtGui.QIcon(":/icons/BIM_Preflight.svg"))
|
|
for test in tests:
|
|
getattr(self.form, test).setIcon(QtGui.QIcon(":/icons/button_right.svg"))
|
|
getattr(self.form, test).setToolTip(
|
|
translate("BIM", "Press to perform the test")
|
|
)
|
|
if hasattr(self, test):
|
|
getattr(self.form, test).clicked.connect(getattr(self, test))
|
|
self.results[test] = None
|
|
self.culprits[test] = None
|
|
|
|
# setup custom tests
|
|
self.customTests = {}
|
|
customModulePath = os.path.join(FreeCAD.getUserAppDataDir(), "BIM", "Preflight")
|
|
if os.path.exists(customModulePath):
|
|
customModules = [
|
|
m[:-3] for m in os.listdir(customModulePath) if m.endswith(".py")
|
|
]
|
|
if customModules:
|
|
sys.path.append(customModulePath)
|
|
for customModule in customModules:
|
|
mod = importlib.import_module(customModule)
|
|
if not "Preflight" in mod.__file__:
|
|
# prevent from using other modules with same name
|
|
FreeCAD.Console.PrintLog(
|
|
"Preflight: loaded wrong module - skipping: "
|
|
+ customModule
|
|
+ " "
|
|
+ str(mod)
|
|
+ "\n"
|
|
)
|
|
continue
|
|
FreeCAD.Console.PrintLog(
|
|
"Preflight: found custom module: "
|
|
+ customModule
|
|
+ " "
|
|
+ str(mod)
|
|
+ "\n"
|
|
)
|
|
functions = [
|
|
o[0]
|
|
for o in inspect.getmembers(mod)
|
|
if inspect.isfunction(o[1])
|
|
]
|
|
if functions:
|
|
box = QtGui.QGroupBox(customModule)
|
|
lay = QtGui.QGridLayout(box)
|
|
self.form.layout().addWidget(box)
|
|
for funcname in functions:
|
|
FreeCAD.Console.PrintLog(
|
|
"Preflight: found custom test: " + funcname + "\n"
|
|
)
|
|
func = getattr(mod, funcname)
|
|
descr = func.__doc__
|
|
if not descr:
|
|
descr = "Undefined"
|
|
lab = QtGui.QLabel(descr)
|
|
lab.setWordWrap(True)
|
|
but = QtGui.QPushButton()
|
|
butname = "Custom_" + customModule + "_" + funcname
|
|
but.setObjectName(butname)
|
|
setattr(self.form, butname, but)
|
|
self.reset(butname)
|
|
row = lay.rowCount()
|
|
lay.addWidget(lab, row, 0)
|
|
lay.addWidget(but, row, 1)
|
|
but.clicked.connect(lambda: self.testCustom(butname))
|
|
self.customTests[butname] = func
|
|
|
|
def getStandardButtons(self):
|
|
from PySide import QtCore, QtGui
|
|
|
|
return int(QtGui.QDialogButtonBox.Close)
|
|
|
|
def reject(self):
|
|
from PySide import QtCore, QtGui
|
|
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
FreeCADGui.Control.closeDialog()
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
def passed(self, test):
|
|
"sets the button as passed"
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
getattr(self.form, test).setIcon(QtGui.QIcon(":/icons/button_valid.svg"))
|
|
getattr(self.form, test).setText(translate("BIM", "Passed"))
|
|
getattr(self.form, test).setToolTip(
|
|
translate("BIM", "This test has succeeded.")
|
|
)
|
|
|
|
def failed(self, test):
|
|
"sets the button as failed"
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
getattr(self.form, test).setIcon(QtGui.QIcon(":/icons/process-stop.svg"))
|
|
getattr(self.form, test).setText("Failed")
|
|
getattr(self.form, test).setToolTip(
|
|
translate("BIM", "This test has failed. Press the button to know more")
|
|
)
|
|
|
|
def reset(self, test):
|
|
"reset the button"
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
getattr(self.form, test).setIcon(QtGui.QIcon(":/icons/button_right.svg"))
|
|
getattr(self.form, test).setText(translate("BIM", "Test"))
|
|
getattr(self.form, test).setToolTip(
|
|
translate("BIM", "Press to perform the test")
|
|
)
|
|
|
|
def show(self, test):
|
|
"shows test results"
|
|
|
|
if (test in self.results) and self.results[test]:
|
|
if (test in self.culprits) and self.culprits[test]:
|
|
FreeCADGui.Selection.clearSelection()
|
|
for c in self.culprits[test]:
|
|
FreeCADGui.Selection.addSelection(c)
|
|
if not self.rform:
|
|
self.rform = FreeCADGui.PySideUic.loadUi(":/ui/dialogPreflightResults.ui")
|
|
# center the dialog over FreeCAD window
|
|
mw = FreeCADGui.getMainWindow()
|
|
self.rform.move(
|
|
mw.frameGeometry().topLeft()
|
|
+ mw.rect().center()
|
|
- self.rform.rect().center()
|
|
)
|
|
self.rform.buttonReport.clicked.connect(self.toReport)
|
|
self.rform.buttonOK.clicked.connect(self.closeReport)
|
|
self.rform.textBrowser.setText(self.results[test])
|
|
label = test.replace("test", "label")
|
|
self.rform.label.setText(getattr(self.form, label).text())
|
|
self.rform.test = test
|
|
self.rform.show()
|
|
|
|
def toReport(self):
|
|
"copies the resulting text to the report view"
|
|
|
|
if self.rform and hasattr(self.rform, "test") and self.rform.test:
|
|
if self.results[self.rform.test]:
|
|
FreeCAD.Console.PrintMessage(self.results[self.rform.test] + "\n")
|
|
|
|
def closeReport(self):
|
|
if self.rform:
|
|
self.rform.test = None
|
|
self.rform.hide()
|
|
|
|
def getObjects(self):
|
|
"selects target objects"
|
|
|
|
import Draft
|
|
import Arch
|
|
|
|
objs = []
|
|
if self.form.getAll.isChecked():
|
|
objs = FreeCAD.ActiveDocument.Objects
|
|
elif self.form.getVisible.isChecked():
|
|
objs = [
|
|
o
|
|
for o in FreeCAD.ActiveDocument.Objects
|
|
if o.ViewObject.Visibility == True
|
|
]
|
|
else:
|
|
objs = FreeCADGui.Selection.getSelection()
|
|
# clean objects list of unwanted types
|
|
objs = Draft.get_group_contents(objs, walls=True, addgroups=True)
|
|
objs = [obj for obj in objs if not obj.isDerivedFrom("Part::Part2DObject")]
|
|
objs = [obj for obj in objs if not obj.isDerivedFrom("App::Annotation")]
|
|
objs = [
|
|
obj
|
|
for obj in objs
|
|
if (
|
|
hasattr(obj, "Shape")
|
|
and obj.Shape
|
|
and not (obj.Shape.Edges and (not obj.Shape.Faces))
|
|
)
|
|
]
|
|
objs = Arch.pruneIncluded(objs)
|
|
objs = [
|
|
obj for obj in objs if not obj.isDerivedFrom("App::DocumentObjectGroup")
|
|
]
|
|
objs = [
|
|
obj
|
|
for obj in objs
|
|
if Draft.getType(obj)
|
|
not in ["DraftText", "Material", "MaterialContainer", "WorkingPlaneProxy"]
|
|
]
|
|
return objs
|
|
|
|
def getToolTip(self, test):
|
|
"gets the toolTip text from the ui file"
|
|
|
|
import re
|
|
|
|
label = test.replace("test", "label")
|
|
tooltip = getattr(self.form, label).toolTip()
|
|
tooltip = tooltip.replace("</p>", "</p>\n\n")
|
|
tooltip = re.sub("<.*?>", "", tooltip) # strip html tags
|
|
return tooltip
|
|
|
|
def testAll(self):
|
|
"runs all tests"
|
|
|
|
from PySide import QtCore, QtGui
|
|
from DraftGui import todo
|
|
|
|
for test in tests:
|
|
if test != "testAll":
|
|
QtGui.QApplication.processEvents()
|
|
self.reset(test)
|
|
if hasattr(self, test):
|
|
todo.delay(getattr(self, test), None)
|
|
for customTest in self.customTests.keys():
|
|
todo.delay(self.testCustom, customTest)
|
|
FreeCADGui.BIMPreflightDone = True
|
|
|
|
def testIFC4(self):
|
|
"tests for IFC4 support"
|
|
|
|
test = "testIFC4"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = None
|
|
msg = None
|
|
try:
|
|
import ifcopenshell
|
|
except ImportError:
|
|
msg = (
|
|
translate(
|
|
"BIM",
|
|
"ifcopenshell is not installed on your system or not available to FreeCAD. This library is responsible for IFC support in FreeCAD, and therefore IFC support is currently disabled. Check %1 to obtain more information.",
|
|
).replace("%1", "https://www.freecadweb.org/wiki/Extra_python_modules#IfcOpenShell")
|
|
+ " "
|
|
)
|
|
self.failed(test)
|
|
else:
|
|
if hasattr(
|
|
ifcopenshell, "schema_identifier"
|
|
) and ifcopenshell.schema_identifier.startswith("IFC4"):
|
|
self.passed(test)
|
|
elif hasattr(ifcopenshell, "version"):
|
|
try:
|
|
from packaging import version
|
|
if "-" in ifcopenshell.version:
|
|
# Prebuild version have a version like 'v0.7.0-<GIT_COMMIT_ID>,
|
|
# trying to remove the commit id.
|
|
cur_version = version.parse(ifcopenshell.version.split('-')[0])
|
|
else:
|
|
cur_version = version.parse(ifcopenshell.version)
|
|
min_version = version.parse("0.6")
|
|
if cur_version >= min_version:
|
|
self.passed(test)
|
|
else:
|
|
msg = self.getToolTip(test)
|
|
msg = (
|
|
translate(
|
|
"BIM",
|
|
"The version of ifcopenshell installed on your system could not be parsed",
|
|
)
|
|
+ " "
|
|
)
|
|
self.failed(test)
|
|
except Exception as e:
|
|
self.failed(test)
|
|
else:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM",
|
|
"The version of ifcopenshell installed on your system will produce files with this schema version:",
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
if hasattr(ifcopenshell, "schema_identifier"):
|
|
msg += ifcopenshell.schema_identifier + "\n\n"
|
|
else:
|
|
msg += "Unable to retrieve schemas information from ifcopenshell\n\n"
|
|
self.failed(test)
|
|
self.results[test] = msg
|
|
|
|
def testHierarchy(self):
|
|
"tests for project hierarchy support"
|
|
|
|
import Draft
|
|
from PySide import QtCore, QtGui
|
|
|
|
test = "testHierarchy"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
sites = False
|
|
buildings = False
|
|
storeys = False
|
|
for obj in self.getObjects():
|
|
if (
|
|
(Draft.getType(obj) == "Site")
|
|
or (hasattr(obj, "IfcRole") and (obj.IfcRole == "Site"))
|
|
or (hasattr(obj, "IfcType") and (obj.IfcType == "Site"))
|
|
):
|
|
sites = True
|
|
elif (
|
|
(Draft.getType(obj) == "Building")
|
|
or (hasattr(obj, "IfcRole") and (obj.IfcRole == "Building"))
|
|
or (hasattr(obj, "IfcType") and (obj.IfcType == "Building"))
|
|
):
|
|
buildings = True
|
|
elif (
|
|
hasattr(obj, "IfcRole") and (obj.IfcRole == "Building Storey")
|
|
) or (hasattr(obj, "IfcType") and (obj.IfcType == "Building Storey")):
|
|
storeys = True
|
|
if (not sites) or (not buildings) or (not storeys):
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM", "The following types were not found in the project:"
|
|
)
|
|
+ "\n"
|
|
)
|
|
if not sites:
|
|
msg += "\nSite"
|
|
if not buildings:
|
|
msg += "\nBuilding"
|
|
if not storeys:
|
|
msg += "\nBuilding Storey"
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testSites(self):
|
|
"tests for Sites support"
|
|
|
|
import Draft
|
|
from PySide import QtCore, QtGui
|
|
|
|
test = "testSites"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
for obj in self.getObjects():
|
|
if (
|
|
(Draft.getType(obj) == "Building")
|
|
or (hasattr(obj, "IfcRole") and (obj.IfcRole == "Building"))
|
|
or (hasattr(obj, "IfcType") and (obj.IfcType == "Building"))
|
|
):
|
|
ok = False
|
|
for parent in obj.InList:
|
|
if (
|
|
(Draft.getType(parent) == "Site")
|
|
or (
|
|
hasattr(parent, "IfcRole")
|
|
and (parent.IfcRole == "Site")
|
|
)
|
|
or (
|
|
hasattr(parent, "IfcType")
|
|
and (parent.IfcType == "Site")
|
|
)
|
|
):
|
|
if hasattr(parent, "Group") and parent.Group:
|
|
if obj in parent.Group:
|
|
ok = True
|
|
break
|
|
if not ok:
|
|
self.culprits[test].append(obj)
|
|
if not msg:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM",
|
|
"The following Building objects have been found to not be included in any Site. You can resolve the situation by creating a Site object, if none is present in your model, and drag and drop the Building objects into it in the tree view:",
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
msg += obj.Label + "\n"
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testBuildings(self):
|
|
"tests for Buildings support"
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
test = "testBuildings"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
for obj in self.getObjects():
|
|
if (hasattr(obj, "IfcRole") and (obj.IfcRole == "Building Storey")) or (
|
|
hasattr(obj, "IfcType") and (obj.IfcType == "Building Storey")
|
|
):
|
|
ok = False
|
|
for parent in obj.InList:
|
|
if (
|
|
hasattr(parent, "IfcRole")
|
|
and (parent.IfcRole == "Building")
|
|
) or (
|
|
hasattr(parent, "IfcType")
|
|
and (parent.IfcType == "Building")
|
|
):
|
|
if hasattr(parent, "Group") and parent.Group:
|
|
if obj in parent.Group:
|
|
ok = True
|
|
break
|
|
if not ok:
|
|
self.culprits[test].append(obj)
|
|
if not msg:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM",
|
|
'The following Building Storey (BuildingParts with their IFC role set as "Building Storey") objects have been found to not be included in any Building. You can resolve the situation by creating a Building object, if none is present in your model, and drag and drop the Building Storey objects into it in the tree view:',
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
msg += obj.Label + "\n"
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testStoreys(self):
|
|
"tests for Building Storey support"
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
test = "testStoreys"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
for obj in self.getObjects():
|
|
if (
|
|
hasattr(obj, "IfcRole")
|
|
and (not obj.IfcRole in ["Building", "Building Storey", "Site"])
|
|
) or (
|
|
hasattr(obj, "IfcType")
|
|
and (not obj.IfcType in ["Building", "Building Storey", "Site"])
|
|
):
|
|
ok = False
|
|
ancestors = obj.InListRecursive
|
|
# append extra objects not in InList
|
|
if hasattr(obj,"Host") and not obj.Host in ancestors:
|
|
ancestors.append(obj.Host)
|
|
if hasattr(obj,"Hosts"):
|
|
for h in obj.Hosts:
|
|
if not h in ancestors:
|
|
ancestors.append(h)
|
|
for parent in ancestors:
|
|
# just check if any of the ancestors is a Building Storey for now. Don't check any further...
|
|
if (
|
|
hasattr(parent, "IfcRole")
|
|
and (parent.IfcRole in ["Building Storey", "Building"])
|
|
) or (
|
|
hasattr(parent, "IfcType")
|
|
and (parent.IfcType in ["Building Storey", "Building"])
|
|
):
|
|
ok = True
|
|
break
|
|
if not ok:
|
|
self.culprits[test].append(obj)
|
|
if not msg:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM",
|
|
'The following BIM objects have been found to not be included in any Building Storey (BuildingParts with their IFC role set as "Building Storey"). You can resolve the situation by creating a Building Storey object, if none is present in your model, and drag and drop these objects into it in the tree view:',
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
msg += obj.Label + "\n"
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testUndefined(self):
|
|
"tests for undefined BIM objects"
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
test = "testUndefined"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
undefined = []
|
|
notbim = []
|
|
msg = None
|
|
|
|
for obj in self.getObjects():
|
|
if hasattr(obj, "IfcType"):
|
|
if obj.IfcType == "Undefined":
|
|
self.culprits[test].append(obj)
|
|
undefined.append(obj)
|
|
elif hasattr(obj, "IfcRole"):
|
|
if obj.IfcRole == "Undefined":
|
|
self.culprits[test].append(obj)
|
|
undefined.append(obj)
|
|
else:
|
|
self.culprits[test].append(obj)
|
|
notbim.append(obj)
|
|
if undefined or notbim:
|
|
msg = self.getToolTip(test)
|
|
if undefined:
|
|
msg += (
|
|
translate(
|
|
"BIM",
|
|
'The following BIM objects have the "Undefined" type:',
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
for o in undefined:
|
|
msg += o.Label + "\n"
|
|
if notbim:
|
|
msg += (
|
|
translate("BIM", "The following objects are not BIM objects:")
|
|
+ "\n\n"
|
|
)
|
|
for o in notbim:
|
|
msg += o.Label + "\n"
|
|
msg += translate(
|
|
"BIM",
|
|
"You can turn these objects into BIM objects by using the Utils -> Make Component tool.",
|
|
)
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testSolid(self):
|
|
"tests for invalid/non-solid BIM objects"
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
test = "testSolid"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
|
|
for obj in self.getObjects():
|
|
if obj.isDerivedFrom("Part::Feature"):
|
|
if (not obj.Shape.isNull()) and (
|
|
(not obj.Shape.isValid()) or (not obj.Shape.Solids)
|
|
):
|
|
self.culprits[test].append(obj)
|
|
if self.culprits[test]:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM",
|
|
"The following BIM objects have an invalid or non-solid geometry:",
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
for o in self.culprits[test]:
|
|
msg += o.Label + "\n"
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testQuantities(self):
|
|
"tests for explicit quantities export"
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
test = "testQuantities"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
|
|
for obj in self.getObjects():
|
|
if hasattr(obj, "IfcAttributes") and (
|
|
Draft.getType(obj) != "BuildingPart"
|
|
):
|
|
for prop in ["Length", "Width", "Height"]:
|
|
if prop in obj.PropertiesList:
|
|
if (not "Export" + prop in obj.IfcAttributes) or (
|
|
obj.IfcAttributes["Export" + prop] == "False"
|
|
):
|
|
self.culprits[test].append(obj)
|
|
break
|
|
if self.culprits[test]:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM",
|
|
"The objects below have Length, Width or Height properties, but these properties won't be explicitly exported to IFC. This is not necessarily an issue, unless you specifically want these quantities to be exported:",
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
for o in self.culprits[test]:
|
|
msg += o.Label + "\n"
|
|
msg += "\n" + translate(
|
|
"BIM",
|
|
"To enable exporting of these quantities, use the IFC quantities manager tool located under menu Manage -> Manage IFC Quantities...",
|
|
)
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testCommonPsets(self):
|
|
"tests for common property sets"
|
|
|
|
from PySide import QtCore, QtGui
|
|
import csv
|
|
|
|
test = "testCommonPsets"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
psets = []
|
|
psetspath = os.path.join(
|
|
FreeCAD.getResourceDir(),
|
|
"Mod",
|
|
"Arch",
|
|
"Presets",
|
|
"pset_definitions.csv",
|
|
)
|
|
if os.path.exists(psetspath):
|
|
with open(psetspath, "r") as csvfile:
|
|
reader = csv.reader(csvfile, delimiter=";")
|
|
for row in reader:
|
|
if "Common" in row[0]:
|
|
psets.append(row[0][5:-6])
|
|
psets = [
|
|
"".join(map(lambda x: x if x.islower() else " " + x, p)) for p in psets
|
|
]
|
|
psets = [pset.strip() for pset in psets]
|
|
# print(psets)
|
|
|
|
for obj in self.getObjects():
|
|
ok = True
|
|
if hasattr(obj, "IfcProperties") and isinstance(
|
|
obj.IfcProperties, dict
|
|
):
|
|
r = None
|
|
if hasattr(obj, "IfcType"):
|
|
r = obj.IfcType
|
|
if hasattr(obj, "IfcRole"):
|
|
r = obj.IfcRole
|
|
if r and (r in psets):
|
|
ok = False
|
|
if "Pset_" + r.replace(" ", "") + "Common" in ",".join(
|
|
obj.IfcProperties.values()
|
|
):
|
|
ok = True
|
|
if not ok:
|
|
self.culprits[test].append(obj)
|
|
|
|
if self.culprits[test]:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM",
|
|
"The objects below have a defined IFC type but do not have the associated common property set:",
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
for o in self.culprits[test]:
|
|
msg += o.Label + "\n"
|
|
msg += "\n" + translate(
|
|
"BIM",
|
|
"To add common property sets to these objects, use the IFC properties manager tool located under menu Manage -> Manage IFC Properties...",
|
|
)
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testPsets(self):
|
|
"tests for property sets integrity"
|
|
|
|
from PySide import QtCore, QtGui
|
|
import csv
|
|
|
|
test = "testPsets"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
psets = {}
|
|
psetspath = os.path.join(
|
|
FreeCAD.getResourceDir(),
|
|
"Mod",
|
|
"Arch",
|
|
"Presets",
|
|
"pset_definitions.csv",
|
|
)
|
|
if os.path.exists(psetspath):
|
|
with open(psetspath, "r") as csvfile:
|
|
reader = csv.reader(csvfile, delimiter=";")
|
|
for row in reader:
|
|
if "Common" in row[0]:
|
|
psets[row[0]] = row[1:]
|
|
|
|
for obj in self.getObjects():
|
|
ok = True
|
|
if hasattr(obj, "IfcProperties") and isinstance(
|
|
obj.IfcProperties, dict
|
|
):
|
|
r = None
|
|
if hasattr(obj, "IfcType"):
|
|
r = obj.IfcType
|
|
elif hasattr(obj, "IfcRole"):
|
|
r = obj.IfcRole
|
|
if r and (r != "Undefined"):
|
|
found = None
|
|
for pset in psets.keys():
|
|
for val in obj.IfcProperties.values():
|
|
if pset in val:
|
|
found = pset
|
|
break
|
|
if found:
|
|
for i in range(int(len(psets[found]) / 2)):
|
|
p = psets[found][i * 2]
|
|
t = psets[found][i * 2 + 1]
|
|
# print("testing for ",p,t,found," in ",obj.IfcProperties)
|
|
if p in obj.IfcProperties:
|
|
if (not found in obj.IfcProperties[p]) or (
|
|
not t in obj.IfcProperties[p]
|
|
):
|
|
ok = False
|
|
else:
|
|
ok = False
|
|
if not ok:
|
|
self.culprits[test].append(obj)
|
|
|
|
if self.culprits[test]:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM",
|
|
"The objects below have a common property set but that property set doesn't contain all the needed properties:",
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
for o in self.culprits[test]:
|
|
msg += o.Label + "\n"
|
|
msg += (
|
|
"\n"
|
|
+ translate(
|
|
"BIM",
|
|
"Verify which properties a certain property set must contain on %1",
|
|
).replace("%1", "https://standards.buildingsmart.org/IFC/DEV/IFC4_2/FINAL/HTML/annex/annex-b/alphabeticalorder_psets.htm")
|
|
+ "\n\n"
|
|
)
|
|
msg += translate(
|
|
"BIM",
|
|
"To fix the property sets of these objects, use the IFC properties manager tool located under menu Manage -> Manage IFC Properties...",
|
|
)
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testMaterials(self):
|
|
"tests for materials in BIM objects"
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
test = "testMaterials"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
for obj in self.getObjects():
|
|
if "Material" in obj.PropertiesList:
|
|
if not obj.Material:
|
|
self.culprits[test].append(obj)
|
|
if self.culprits[test]:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM", "The following BIM objects have no material attributed:"
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
for o in self.culprits[test]:
|
|
msg += o.Label + "\n"
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testStandards(self):
|
|
"tests for standards in BIM objects"
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
test = "testStandards"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
for obj in self.getObjects():
|
|
if "StandardCode" in obj.PropertiesList:
|
|
if not obj.StandardCode:
|
|
self.culprits[test].append(obj)
|
|
if "Material" in obj.PropertiesList:
|
|
if obj.Material:
|
|
if "StandardCode" in obj.Material.PropertiesList:
|
|
if not obj.Material.StandardCode:
|
|
self.culprits[test].append(obj.Material)
|
|
if self.culprits[test]:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM",
|
|
"The following BIM objects have no defined standard code:",
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
for o in self.culprits[test]:
|
|
msg += o.Label + "\n"
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testExtrusions(self):
|
|
"tests is all objects are extrusions"
|
|
|
|
from PySide import QtCore, QtGui
|
|
import Draft
|
|
|
|
test = "testExtrusions"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
for obj in self.getObjects():
|
|
if hasattr(obj, "Proxy"):
|
|
if (
|
|
hasattr(obj, "IfcAttributes")
|
|
and ("FlagForceBrep" in obj.IfcAttributes.keys())
|
|
and (obj.IfcAttributes["FlagForceBrep"] == "True")
|
|
):
|
|
self.culprits[test].append(obj)
|
|
elif hasattr(
|
|
obj.Proxy, "getExtrusionData"
|
|
) and not obj.Proxy.getExtrusionData(obj):
|
|
self.culprits[test].append(obj)
|
|
elif Draft.getType(obj) == "BuildingPart":
|
|
pass
|
|
elif obj.isDerivedFrom("Part::Extrusion"):
|
|
pass
|
|
elif obj.isDerivedFrom("App::DocumentObjectGroup"):
|
|
pass
|
|
elif obj.isDerivedFrom("App::MaterialObject"):
|
|
pass
|
|
else:
|
|
self.culprits[test].append(obj)
|
|
if self.culprits[test]:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate("BIM", "The following BIM objects are not extrusions:")
|
|
+ "\n\n"
|
|
)
|
|
for o in self.culprits[test]:
|
|
msg += o.Label + "\n"
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testStandardCases(self):
|
|
"tests for structs and wall standard cases"
|
|
|
|
import Draft
|
|
from PySide import QtCore, QtGui
|
|
|
|
test = "testStandardCases"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
for obj in self.getObjects():
|
|
if Draft.getType(obj) == "Wall":
|
|
if obj.Base and (len(obj.Base.Shape.Edges) != 1):
|
|
self.culprits[test].append(obj)
|
|
elif Draft.getType(obj) == "Structure":
|
|
if obj.Base and (
|
|
(len(obj.Base.Shape.Wires) != 1)
|
|
or (not obj.Base.Shape.Wires[0].isClosed())
|
|
):
|
|
self.culprits[test].append(obj)
|
|
if self.culprits[test]:
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM", "The following BIM objects are not standard cases:"
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
for o in self.culprits[test]:
|
|
msg += o.Label + "\n"
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testTinyLines(self):
|
|
"tests for objects with tiny lines (< 0.8mm)"
|
|
|
|
from PySide import QtCore, QtGui
|
|
|
|
test = "testTinyLines"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = []
|
|
msg = None
|
|
minl = 0.79376 # min 1/32"
|
|
edges = []
|
|
objs = []
|
|
for obj in self.getObjects():
|
|
if obj.isDerivedFrom("Part::Feature"):
|
|
if obj.Shape:
|
|
for e in obj.Shape.Edges:
|
|
if e.Length <= minl:
|
|
edges.append(e)
|
|
if not obj in objs:
|
|
objs.append(obj)
|
|
if edges:
|
|
import Part
|
|
result = FreeCAD.ActiveDocument.addObject(
|
|
"Part::Feature", "TinyLinesResult"
|
|
)
|
|
result.Shape = Part.makeCompound(edges)
|
|
result.ViewObject.LineWidth = 5
|
|
self.culprits[test] = [result]
|
|
msg = self.getToolTip(test)
|
|
msg += (
|
|
translate(
|
|
"BIM",
|
|
"The objects below have lines smaller than 1/32 inch or 0.79 mm, which is the smallest line size that Revit accepts. These objects will be discarded when imported into Revit:",
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
for obj in objs:
|
|
msg += obj.Label + "\n"
|
|
msg += (
|
|
"\n"
|
|
+ translate(
|
|
"BIM",
|
|
'An additional object, called "TinyLinesResult" has been added to this model, and selected. It contains all the tiny lines found, so you can inspect them and fix the needed objects. Be sure to delete the TinyLinesResult object when you are done!',
|
|
)
|
|
+ "\n\n"
|
|
)
|
|
msg += translate(
|
|
"BIM",
|
|
"Tip: The results are best viewed in Wireframe mode (menu Views -> Draw Style -> Wireframe)",
|
|
)
|
|
if msg:
|
|
self.failed(test)
|
|
else:
|
|
self.passed(test)
|
|
self.results[test] = msg
|
|
QtGui.QApplication.restoreOverrideCursor()
|
|
|
|
def testRectangleProfileDef(self):
|
|
"tests for RectangleProfileDef disable"
|
|
|
|
test = "testRectangleProfileDef"
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
self.culprits[test] = None
|
|
msg = None
|
|
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool(
|
|
"DisableIfcRectangleProfileDef", False
|
|
):
|
|
self.passed(test)
|
|
else:
|
|
msg = self.getToolTip(test)
|
|
self.failed(test)
|
|
self.results[test] = msg
|
|
|
|
def testCustom(self, test):
|
|
"performs a custom test"
|
|
|
|
if test in self.customTests:
|
|
if getattr(self.form, test).text() == "Failed":
|
|
self.show(test)
|
|
else:
|
|
self.reset(test)
|
|
self.results[test] = None
|
|
result = self.customTests[test]()
|
|
if result == True:
|
|
self.passed(test)
|
|
else:
|
|
self.failed(test)
|
|
self.results[test] = result
|
|
|
|
|
|
FreeCADGui.addCommand("BIM_Preflight", BIM_Preflight())
|