diff --git a/src/Mod/BIM/BimStatus.py b/src/Mod/BIM/BimStatus.py
index 0a361999c9..9da009da0d 100644
--- a/src/Mod/BIM/BimStatus.py
+++ b/src/Mod/BIM/BimStatus.py
@@ -116,6 +116,8 @@ def setStatusIcons(show=True):
if show:
if statuswidget:
statuswidget.show()
+ if hasattr(statuswidget, "propertybuttons"):
+ statuswidget.propertybuttons.show()
else:
statuswidget = FreeCADGui.UiLoader().createWidget("Gui::ToolBar")
statuswidget.setObjectName("BIMStatusWidget")
@@ -221,3 +223,5 @@ def setStatusIcons(show=True):
if statuswidget:
statuswidget.hide()
statuswidget.toggleViewAction().setVisible(False)
+ if hasattr(statuswidget, "propertybuttons"):
+ statuswidget.propertybuttons.hide()
diff --git a/src/Mod/BIM/CMakeLists.txt b/src/Mod/BIM/CMakeLists.txt
index dedb0c8b03..14e80c6841 100644
--- a/src/Mod/BIM/CMakeLists.txt
+++ b/src/Mod/BIM/CMakeLists.txt
@@ -82,6 +82,7 @@ SET(Arch_presets
Presets/ifc_types_IFC4.json
Presets/ifc_contexts_IFC2X3.json
Presets/ifc_contexts_IFC4.json
+ Presets/properties_conversion.csv
)
SET(bimcommands_SRCS
diff --git a/src/Mod/BIM/Presets/properties_conversion.csv b/src/Mod/BIM/Presets/properties_conversion.csv
new file mode 100644
index 0000000000..96309d096c
--- /dev/null
+++ b/src/Mod/BIM/Presets/properties_conversion.csv
@@ -0,0 +1,230 @@
+FreeCAD,IFC
+App::PropertyBool,IfcBoolean:IfcLogical
+App::PropertyBoolList,
+App::PropertyFloat,IfcReal
+App::PropertyFloatList,
+App::PropertyFloatConstraint,
+App::PropertyPrecision,
+App::PropertyQuantity,
+App::PropertyQuantityConstraint,
+App::PropertyInteger,IfcInteger
+App::PropertyIntegerConstraint,
+App::PropertyPercent,
+App::PropertyEnumeration,
+App::PropertyIntegerList,
+App::PropertyIntegerSet,
+App::PropertyMap,
+App::PropertyString,IfcLabel:IfcIdentifier:IfcText
+App::PropertyPersistentObject,
+App::PropertyUUID,IfcGloballyUniqueId
+App::PropertyFont,IfcTextFontName
+App::PropertyStringList,
+App::PropertyLink,
+App::PropertyLinkChild,
+App::PropertyLinkGlobal,
+App::PropertyLinkHidden,
+App::PropertyLinkSub,
+App::PropertyLinkSubChild,
+App::PropertyLinkSubGlobal,
+App::PropertyLinkSubHidden,
+App::PropertyLinkList,
+App::PropertyLinkListChild,
+App::PropertyLinkListGlobal,
+App::PropertyLinkListHidden,
+App::PropertyLinkSubList,
+App::PropertyLinkSubListChild,
+App::PropertyLinkSubListGlobal,
+App::PropertyLinkSubListHidden,
+App::PropertyXLink,
+App::PropertyXLinkSub,
+App::PropertyXLinkSubHidden,
+App::PropertyXLinkSubList,
+App::PropertyXLinkList,
+App::PropertyMatrix,
+App::PropertyVector,
+App::PropertyVectorDistance,
+App::PropertyPosition,
+App::PropertyDirection,
+App::PropertyVectorList,
+App::PropertyPlacement,
+App::PropertyPlacementList,
+App::PropertyPlacementLink,
+App::PropertyRotation,
+App::PropertyColor,
+App::PropertyColorList,
+App::PropertyMaterial,
+App::PropertyMaterialList,
+App::PropertyPath,
+App::PropertyFile,
+App::PropertyFileIncluded,
+App::PropertyPythonObject,
+App::PropertyExpressionEngine,
+App::PropertyAcceleration,
+App::PropertyAmountOfSubstance,
+App::PropertyAngle,IfcPlaneAngleMeasure
+App::PropertyArea,IfcAreaMeasure
+App::PropertyCompressiveStrength,
+App::PropertyCurrentDensity,
+App::PropertyDensity,
+App::PropertyDissipationRate,
+App::PropertyDistance,IfcLengthMeasure
+App::PropertyDynamicViscosity,
+App::PropertyElectricalCapacitance,
+App::PropertyElectricalConductance,
+App::PropertyElectricalConductivity,
+App::PropertyElectricalInductance,
+App::PropertyElectricalResistance,
+App::PropertyElectricCharge,
+App::PropertyElectricCurrent,
+App::PropertyElectricPotential,
+App::PropertyFrequency,IfcFrequencyMeasure
+App::PropertyForce,IfcForceMeasure
+App::PropertyHeatFlux,IfcHeatFluxDensityMeasure
+App::PropertyInverseArea,
+App::PropertyInverseLength,
+App::PropertyInverseVolume,
+App::PropertyKinematicViscosity,
+App::PropertyLength,IfcPositiveLengthMeasure
+App::PropertyLuminousIntensity,
+App::PropertyMagneticFieldStrength,
+App::PropertyMagneticFlux,
+App::PropertyMagneticFluxDensity,
+App::PropertyMagnetization,
+App::PropertyMass,IfcMassMeasure
+App::PropertyMoment,IfcMomentOfInertiaMeasure
+App::PropertyPressure,IfcPressureMeasure
+App::PropertyPower,IfcPowerMeasure
+App::PropertyShearModulus,IfcShearModulusMeasure
+App::PropertySpecificEnergy,
+App::PropertySpecificHeat,
+App::PropertySpeed,
+App::PropertyStiffness,
+App::PropertyStiffnessDensity,
+App::PropertyStress,
+App::PropertyTemperature,IfcThermodynamicTemperatureMeasure
+App::PropertyThermalConductivity,IfcThermalConductivityMeasure
+App::PropertyThermalExpansionCoefficient,IfcThermalExpansionCoefficientMeasure
+App::PropertyThermalTransferCoefficient,IfcThermalTransmittanceMeasure
+App::PropertyTime,IfcDateTime:IfcDate:IfcTime:IfcTimeMeasure:IfcTimeStamp
+App::PropertyUltimateTensileStrength,
+App::PropertyVacuumPermittivity,
+App::PropertyVelocity,
+App::PropertyVolume,IfcVolumeMeasure
+App::PropertyVolumeFlowRate,
+App::PropertyVolumetricThermalExpansionCoefficient,
+App::PropertyWork,
+App::PropertyYieldStrength,
+App::PropertyYoungsModulus,
+Materials::PropertyMaterial,
+Part::PropertyPartShape,
+Part::PropertyGeometryList,
+Part::PropertyShapeHistory,
+Part::PropertyFilletEdges,
+Part::PropertyShapeCache,
+Part::PropertyTopoShapeList,
+Sketcher::PropertyConstraintList,
+,IfcBSplineCurveForm
+,IfcBSplineSurfaceForm
+,IfcBooleanOperator
+,IfcKnotType
+,IfcNullStyle
+,IfcPreferredSurfaceCurveRepresentation
+,IfcSIPrefix
+,IfcSIUnitName
+,IfcSurfaceSide
+,IfcTextPath
+,IfcTransitionCode
+,IfcTrimmingPreference
+,IfcAbsorbedDoseMeasure
+,IfcAccelerationMeasure
+,IfcAmountOfSubstanceMeasure
+,IfcAngularVelocityMeasure
+,IfcAreaDensityMeasure
+,IfcBoxAlignment
+,IfcCardinalPointReference
+,IfcContextDependentMeasure
+,IfcCountMeasure
+,IfcCurvatureMeasure
+,IfcDayInMonthNumber
+,IfcDayInWeekNumber
+,IfcDescriptiveMeasure
+,IfcDimensionCount
+,IfcDoseEquivalentMeasure
+,IfcDuration
+,IfcDynamicViscosityMeasure
+,IfcElectricCapacitanceMeasure
+,IfcElectricChargeMeasure
+,IfcElectricConductanceMeasure
+,IfcElectricCurrentMeasure
+,IfcElectricResistanceMeasure
+,IfcElectricVoltageMeasure
+,IfcEnergyMeasure
+,IfcFontStyle
+,IfcFontVariant
+,IfcFontWeight
+,IfcHeatingValueMeasure
+,IfcIlluminanceMeasure
+,IfcInductanceMeasure
+,IfcIntegerCountRateMeasure
+,IfcIonConcentrationMeasure
+,IfcIsothermalMoistureCapacityMeasure
+,IfcKinematicViscosityMeasure
+,IfcLanguageId
+,IfcLinearForceMeasure
+,IfcLinearMomentMeasure
+,IfcLinearStiffnessMeasure
+,IfcLinearVelocityMeasure
+,IfcLuminousFluxMeasure
+,IfcLuminousIntensityDistributionMeasure
+,IfcLuminousIntensityMeasure
+,IfcMagneticFluxDensityMeasure
+,IfcMagneticFluxMeasure
+,IfcMassDensityMeasure
+,IfcMassFlowRateMeasure
+,IfcMassPerLengthMeasure
+,IfcModulusOfElasticityMeasure
+,IfcModulusOfLinearSubgradeReactionMeasure
+,IfcModulusOfRotationalSubgradeReactionMeasure
+,IfcModulusOfSubgradeReactionMeasure
+,IfcMoistureDiffusivityMeasure
+,IfcMolecularWeightMeasure
+,IfcMonetaryMeasure
+,IfcMonthInYearNumber
+,IfcNonNegativeLengthMeasure
+,IfcNormalisedRatioMeasure
+,IfcNumericMeasure
+,IfcPHMeasure
+,IfcParameterValue
+,IfcPlanarForceMeasure
+,IfcPositiveInteger
+,IfcPositivePlaneAngleMeasure
+,IfcPositiveRatioMeasure
+,IfcPresentableText
+,IfcRadioActivityMeasure
+,IfcRatioMeasure
+,IfcRotationalFrequencyMeasure
+,IfcRotationalMassMeasure
+,IfcRotationalStiffnessMeasure
+,IfcSectionModulusMeasure
+,IfcSectionalAreaIntegralMeasure
+,IfcSolidAngleMeasure
+,IfcSoundPowerLevelMeasure
+,IfcSoundPowerMeasure
+,IfcSoundPressureLevelMeasure
+,IfcSoundPressureMeasure
+,IfcSpecificHeatCapacityMeasure
+,IfcSpecularExponent
+,IfcSpecularRoughness
+,IfcTemperatureGradientMeasure
+,IfcTemperatureRateOfChangeMeasure
+,IfcTextAlignment
+,IfcTextDecoration
+,IfcTextTransformation
+,IfcThermalAdmittanceMeasure
+,IfcThermalResistanceMeasure
+,IfcTorqueMeasure
+,IfcURIReference
+,IfcVaporPermeabilityMeasure
+,IfcVolumetricFlowRateMeasure
+,IfcWarpingConstantMeasure
+,IfcWarpingMomentMeasure
diff --git a/src/Mod/BIM/Resources/Arch.qrc b/src/Mod/BIM/Resources/Arch.qrc
index aef9a36972..48a769697c 100644
--- a/src/Mod/BIM/Resources/Arch.qrc
+++ b/src/Mod/BIM/Resources/Arch.qrc
@@ -185,6 +185,8 @@
ui/ParametersWindowFixed.svg
ui/ParametersWindowSimple.svg
ui/ParametersWindowStash.svg
+ ui/dialogAddPSet.ui
+ ui/dialogAddProperty.ui
ui/dialogClasses.ui
ui/dialogClassification.ui
ui/dialogConvertDocument.ui
diff --git a/src/Mod/BIM/Resources/ui/dialogAddPSet.ui b/src/Mod/BIM/Resources/ui/dialogAddPSet.ui
new file mode 100644
index 0000000000..92c46b63bb
--- /dev/null
+++ b/src/Mod/BIM/Resources/ui/dialogAddPSet.ui
@@ -0,0 +1,94 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 310
+ 122
+
+
+
+ Add standard IFC PSet
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Name
+
+
+
+ -
+
+
+ true
+
+
+ false
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ Dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ Dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/src/Mod/BIM/Resources/ui/dialogAddProperty.ui b/src/Mod/BIM/Resources/ui/dialogAddProperty.ui
new file mode 100644
index 0000000000..ffaf8c8c79
--- /dev/null
+++ b/src/Mod/BIM/Resources/ui/dialogAddProperty.ui
@@ -0,0 +1,142 @@
+
+
+ Dialog
+
+
+
+ 0
+ 0
+ 338
+ 217
+
+
+
+ Add IFC property
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
-
+
+ IfcLabel
+
+
+ -
+
+ IfcBoolean
+
+
+ -
+
+ IfcInteger
+
+
+ -
+
+ IfcReal
+
+
+ -
+
+ IfcLengthMeasure
+
+
+ -
+
+ IfcAreaMeasure
+
+
+
+
+ -
+
+
+ -
+
+
+ Type
+
+
+
+ -
+
+
+ Name
+
+
+
+ -
+
+
+ PSet
+
+
+
+ -
+
+
+ true
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ Dialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ Dialog
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/src/Mod/BIM/bimcommands/BimIfcProperties.py b/src/Mod/BIM/bimcommands/BimIfcProperties.py
index 01f8f98765..8dde26e648 100644
--- a/src/Mod/BIM/bimcommands/BimIfcProperties.py
+++ b/src/Mod/BIM/bimcommands/BimIfcProperties.py
@@ -175,6 +175,8 @@ class BIM_IfcProperties:
obj.IfcProperties, dict
):
props = obj.IfcProperties
+ elif hasattr(obj, "IfcClass"):
+ props = self.getNativeIfcProperties(obj)
else:
props = {}
objectslist[obj.Name] = [role, props]
@@ -182,6 +184,7 @@ class BIM_IfcProperties:
val = val.split(";;")
if ";;" in key:
# 0.19 format
+ # pset;;pname = ptype;;pvalue
key = key.split(";;")
val = [key[1]] + val
key = key[0]
@@ -222,6 +225,8 @@ class BIM_IfcProperties:
return obj.IfcType
elif hasattr(obj, "IfcRole"):
return obj.IfcRole
+ elif hasattr(obj, "IfcClass"):
+ return obj.IfcClass
else:
return None
@@ -259,14 +264,7 @@ class BIM_IfcProperties:
obj = FreeCAD.ActiveDocument.getObject(name)
if obj:
it1 = QtGui.QStandardItem(obj.Label)
- if QtCore.QFileInfo(
- ":/icons/Arch_" + obj.Proxy.Type + "_Tree.svg"
- ).exists():
- icon = QtGui.QIcon(
- ":/icons/Arch_" + obj.Proxy.Type + "_Tree.svg"
- )
- else:
- icon = QtGui.QIcon(":/icons/Arch_Component.svg")
+ icon = obj.ViewObject.Icon
it1.setIcon(icon)
it1.setToolTip(obj.Name)
it2 = QtGui.QStandardItem(group)
@@ -315,12 +313,7 @@ class BIM_IfcProperties:
role = self.objectslist[obj.Name][0]
if (not self.form.onlyVisible.isChecked()) or obj.ViewObject.isVisible():
it1 = QtGui.QStandardItem(obj.Label)
- if QtCore.QFileInfo(
- ":/icons/Arch_" + obj.Proxy.Type + "_Tree.svg"
- ).exists():
- icon = QtGui.QIcon(":/icons/Arch_" + obj.Proxy.Type + "_Tree.svg")
- else:
- icon = QtGui.QIcon(":/icons/Arch_Component.svg")
+ icon = obj.ViewObject.Icon
it1.setIcon(icon)
it1.setToolTip(obj.Name)
it2 = QtGui.QStandardItem(role)
@@ -353,14 +346,7 @@ class BIM_IfcProperties:
not self.form.onlyVisible.isChecked()
) or obj.ViewObject.isVisible():
it1 = QtGui.QStandardItem(obj.Label)
- if QtCore.QFileInfo(
- ":/icons/Arch_" + obj.Proxy.Type + "_Tree.svg"
- ).exists():
- icon = QtGui.QIcon(
- ":/icons/Arch_" + obj.Proxy.Type + "_Tree.svg"
- )
- else:
- icon = QtGui.QIcon(":/icons/Arch_Component.svg")
+ icon = obj.ViewObject.Icon
it1.setIcon(icon)
it1.setToolTip(obj.Name)
it2 = QtGui.QStandardItem(role)
@@ -398,13 +384,28 @@ class BIM_IfcProperties:
)
continue
props = obj.IfcProperties
+ elif hasattr(obj, "IfcClass"):
+ props = self.getNativeIfcProperties(obj)
else:
props = {}
if values[1] != props:
if not changed:
FreeCAD.ActiveDocument.openTransaction("Change properties")
changed = True
- if not hasattr(obj, "IfcProperties"):
+ if hasattr(obj,"IfcClass"):
+ print("props:",props)
+ for key,value in values[1].items():
+ if ";;" in key and ";;" in value:
+ pname, pset = key.split(";;")
+ ptype, pvalue = value.split(";;")
+ from nativeifc import ifc_psets # lazy loading
+ fctype = ifc_psets.get_freecad_type(ptype)
+ if not pname in obj.PropertiesList:
+ obj.addProperty(fctype, pname, pset, ptype+":"+pname)
+ ifc_psets.edit_pset(obj, pname, force=True)
+ if pvalue:
+ setattr(obj, pname, pvalue)
+ elif not hasattr(obj, "IfcProperties"):
obj.addProperty(
"App::PropertyMap",
"IfcPRoperties",
@@ -413,11 +414,23 @@ class BIM_IfcProperties:
"App::Property", "IFC properties of this object"
),
)
- obj.IfcProperties = values[1]
+ if hasattr(obj, "IfcProperties"):
+ obj.IfcProperties = values[1]
if changed:
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
+ def getNativeIfcProperties(self, obj):
+ props = {}
+ for p in obj.PropertiesList:
+ pset = obj.getGroupOfProperty(p)
+ ttip = obj.getDocumentationOfProperty(p)
+ if ":" in ttip:
+ ptype, pname = ttip.split(":")
+ if pset not in ["Base", "IFC", "Geometry"]:
+ props[pname+";;"+pset] = ptype+";;"+str(getattr(obj,p))
+ return props
+
def getSearchResults(self, obj):
from PySide import QtCore, QtGui
diff --git a/src/Mod/BIM/nativeifc/ifc_observer.py b/src/Mod/BIM/nativeifc/ifc_observer.py
index bf4e7adc8c..71638c0ca4 100644
--- a/src/Mod/BIM/nativeifc/ifc_observer.py
+++ b/src/Mod/BIM/nativeifc/ifc_observer.py
@@ -31,7 +31,7 @@ params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/NativeIFC")
def add_observer():
- """Adds an observer to the running FreeCAD instance"""
+ """Adds this observer to the running FreeCAD instance"""
FreeCAD.BIMobserver = ifc_observer()
FreeCAD.addDocumentObserver(FreeCAD.BIMobserver)
@@ -123,6 +123,11 @@ class ifc_observer:
ifc_status.on_activate()
+ def slotRemoveDynamicProperty(self, obj, prop):
+
+ from nativeifc import ifc_psets
+ ifc_psets.remove_property(obj, prop)
+
# implementation methods
def fit_all(self):
diff --git a/src/Mod/BIM/nativeifc/ifc_psets.py b/src/Mod/BIM/nativeifc/ifc_psets.py
index 30b8f0112f..fac71f7135 100644
--- a/src/Mod/BIM/nativeifc/ifc_psets.py
+++ b/src/Mod/BIM/nativeifc/ifc_psets.py
@@ -23,10 +23,13 @@
"""This NativeIFC module deals with properties and property sets"""
+import os
import re
import FreeCAD
from nativeifc import ifc_tools
+translate = FreeCAD.Qt.translate
+
def has_psets(obj):
"""Returns True if an object has attached psets"""
@@ -148,75 +151,117 @@ def show_psets(obj):
setattr(obj, pname, value)
-def edit_pset(obj, prop, value=None):
- """Edits the corresponding property"""
+def edit_pset(obj, prop, value=None, force=False):
+ """Edits the corresponding property. If force is True,
+ the property is created even if it has no value"""
pset = obj.getGroupOfProperty(prop)
- ttip = obj.getDocumentationOfProperty(prop)
+ ptype = obj.getDocumentationOfProperty(prop)
if value is None:
value = getattr(obj, prop)
ifcfile = ifc_tools.get_ifcfile(obj)
element = ifc_tools.get_ifc_element(obj)
pset_exist = get_psets(element)
- if ttip.startswith("Ifc") and ":" in ttip:
- target_prop = ttip.split(":", 1)[-1]
+ target_prop = None
+ value_exist = None
+
+ # build prop name and type
+ if ptype.startswith("Ifc"):
+ if ":" in ptype:
+ target_prop = ptype.split(":", 1)[-1]
+ ptype = ptype.split(":", 1)[0]
else:
- # no tooltip set - try to build a name
+ ptype = obj.getTypeIdOfProperty(prop)
+ if ifcprop == "App::PropertyDistance":
+ ptype = "IfcLengthMeasure"
+ elif ifcprop == "App::PropertyLength":
+ ptype = "IfcPositiveLengthMeasure"
+ elif ifcprop == "App::PropertyBool":
+ ptype = "IfcBoolean"
+ elif ifcprop == "App::PropertyInteger":
+ ptype = "IfcInteger"
+ elif ifcprop == "App::PropertyFloat":
+ ptype = "IfcReal"
+ elif ifcprop == "App::PropertyArea":
+ ptype = "IfcAreaMeasure"
+ else:
+ # default
+ ptype = "IfcLabel"
+ if not target_prop:
+ # test if the prop exists under different forms (uncameled, unslashed...)
prop = prop.rstrip("_")
prop_uncamel = re.sub(r"(\w)([A-Z])", r"\1 \2", prop)
prop_unslash = re.sub(r"(\w)([A-Z])", r"\1\/\2", prop)
- target_prop = None
- if pset in pset_exist:
- if not target_prop:
+ if pset in pset_exist:
if prop in pset_exist[pset]:
target_prop = prop
elif prop_uncamel in pset_exist[pset]:
target_prop = prop_uncamel
elif prop_unslash in pset_exist[pset]:
target_prop = prop_unslash
- if target_prop:
- value_exist = pset_exist[pset][target_prop].split("(", 1)[1][:-1].strip("'")
- if value_exist in [".F.", ".U."]:
- value_exist = False
- elif value_exist in [".T."]:
- value_exist = True
- elif isinstance(value, int):
- value_exist = int(value_exist.strip("."))
- elif isinstance(value, float):
- value_exist = float(value_exist)
- elif isinstance(value, FreeCAD.Units.Quantity):
- if value.Unit.Type == "Angle":
- value_exist = float(value_exist)
- while value_exist > 360:
- value_exist = value_exist - 360
- value_exist = FreeCAD.Units.Quantity(float(value_exist), value.Unit)
- if value == value_exist:
- return False
- else:
- FreeCAD.Console.PrintLog(
- "IFC: property changed for "
- + obj.Label
- + " ("
- + str(obj.StepId)
- + ") : "
- + str(target_prop)
- + " : "
- + str(value)
- + " ("
- + str(type(value))
- + ") -> "
- + str(value_exist)
- + " ("
- + str(type(value_exist))
- + ")\n"
- )
- pset = get_pset(pset, element)
- else:
- pset = ifc_tools.api_run("pset.add_pset", ifcfile, product=element, name=pset)
if not target_prop:
target_prop = prop
+
+ # create pset if needed
+ if pset in pset_exist:
+ ifcpset = get_pset(pset, element)
+ if target_prop in pset_exist[pset]:
+ value_exist = pset_exist[pset][target_prop].split("(", 1)[1][:-1].strip("'")
+ else:
+ ifcpset = ifc_tools.api_run("pset.add_pset", ifcfile, product=element, name=pset)
+
+ # value conversions
+ if value_exist in [".F.", ".U."]:
+ value_exist = False
+ elif value_exist in [".T."]:
+ value_exist = True
+ elif isinstance(value, int):
+ if value_exist:
+ value_exist = int(value_exist.strip("."))
+ elif isinstance(value, float):
+ if value_exist:
+ value_exist = float(value_exist)
+ elif isinstance(value, FreeCAD.Units.Quantity):
+ if value_exist:
+ value_exist = float(value_exist)
+ if value.Unit.Type == "Angle":
+ if value_exist:
+ while value_exist > 360:
+ value_exist = value_exist - 360
+ value = value.getValueAs("deg")
+ elif value.Unit.Type == "Length":
+ value = value.getValueAs("mm").Value * ifc_tools.get_scale(ifcfile)
+ else:
+ print("DEBUG: unhandled quantity type:",value, value.Unit.Type)
+ return False
+ if value == value_exist:
+ return False
+ if not force and not value and not value_exist:
+ return False
+ FreeCAD.Console.PrintLog(
+ "IFC: property changed for "
+ + obj.Label
+ + " ("
+ + str(obj.StepId)
+ + "): "
+ + str(target_prop)
+ + ": "
+ + str(value_exist)
+ + " ("
+ + type(value_exist).__name__
+ + ") -> "
+ + str(value)
+ + " ("
+ + type(value).__name__
+ + ")\n"
+ )
+
+ # run the change
+ # TODO the property type is automatically determined by ifcopenhell
+ # https://docs.ifcopenshell.org/autoapi/ifcopenshell/api/pset/edit_pset/index.html
+ # and is therefore wrong for Quantity types. Research a way to overcome that
ifc_tools.api_run(
- "pset.edit_pset", ifcfile, pset=pset, properties={target_prop: value}
+ "pset.edit_pset", ifcfile, pset=ifcpset, properties={target_prop: value}
)
# TODO manage quantities
return True
@@ -252,3 +297,62 @@ def add_property(ifcfile, pset, name, value=""):
To force a certain type, value can also be an IFC element such as IfcLabel"""
ifc_tools.api_run("pset.edit_pset", ifcfile, pset=pset, properties={name: value})
+
+
+def get_freecad_type(ptype):
+ """Returns a FreeCAD property type correspinding to an IFC property type"""
+
+ conv = read_properties_conversion()
+ for key, values in conv.items():
+ if ptype.lower() in [v.lower() for v in values.split(":")]:
+ return key
+ return "App::PropertyString"
+
+
+def get_ifc_type(fctype):
+ """Returns an IFC property type correspinding to a FreeCAD property type"""
+
+ conv = read_properties_conversion()
+ for key, values in conv.items():
+ if fctype.lower() == key.lower():
+ return values.split(":")[0]
+ return "IfcLabel"
+
+
+def read_properties_conversion():
+ """Reads the properties conversion table"""
+
+ import csv
+ csvfile = os.path.join(
+ FreeCAD.getResourceDir(), "Mod", "BIM", "Presets", "properties_conversion.csv"
+ )
+ 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 remove_property(obj, prop):
+ """Removes a custom property"""
+
+ from nativeifc import ifc_tools
+ ifcfile = ifc_tools.get_ifcfile(obj)
+ if not ifcfile:
+ return
+ element = ifc_tools.get_ifc_element(obj, ifcfile)
+ if not element:
+ return
+ psets = get_psets(element)
+ for psetname, props in psets.items():
+ if prop in props:
+ pset = get_pset(psetname, element)
+ if pset:
+ FreeCAD.Console.PrintMessage(translate("BIM","Removing property")+": "+prop)
+ ifc_tools.api_run("pset.edit_pset", ifcfile, pset=pset, properties={prop: None})
+ if len(props) == 1:
+ # delete the pset too
+ FreeCAD.Console.PrintMessage(translate("BIM","Removing property set")+": "+psetname)
+ ifc_tools.api_run("pset.remove_pset", ifcfile, product=element, pset=pset)
diff --git a/src/Mod/BIM/nativeifc/ifc_status.py b/src/Mod/BIM/nativeifc/ifc_status.py
index a6e1581f96..fc4ad5a68c 100644
--- a/src/Mod/BIM/nativeifc/ifc_status.py
+++ b/src/Mod/BIM/nativeifc/ifc_status.py
@@ -24,6 +24,8 @@
"""This contains nativeifc status widgets and functionality"""
+import os
+import csv
import FreeCAD
import FreeCADGui
@@ -56,6 +58,169 @@ def set_status_widget(statuswidget):
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 # lazy loading
+
+ 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 QtCore, QtGui # lazy loading
+ from nativeifc 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")
+ # center the dialog over FreeCAD window
+ 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))
+ # TODO check for name duplicates while typing
+ # execute
+ 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:
+ # TODO disable the OK button if empty
+ 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 PySide import QtCore, QtGui # lazy loading
+ from nativeifc import ifc_psets
+ obj = sel[0]
+ mw = FreeCADGui.getMainWindow()
+ # read standard psets
+ 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")
+ # center the dialog over FreeCAD window
+ form.move(mw.frameGeometry().topLeft() + mw.rect().center() - form.rect().center())
+ form.field_pset.clear()
+ form.field_pset.addItems(psetkeys)
+ # execute
+ 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):
diff --git a/src/Mod/BIM/nativeifc/ifc_tools.py b/src/Mod/BIM/nativeifc/ifc_tools.py
index 132eefcb13..da35ef8616 100644
--- a/src/Mod/BIM/nativeifc/ifc_tools.py
+++ b/src/Mod/BIM/nativeifc/ifc_tools.py
@@ -745,6 +745,21 @@ def set_attribute(ifcfile, element, attribute, value):
# This function can become pure IFC
+ def differs(val1, val2):
+ if val1 == val2:
+ return False
+ if not val1 and not val2:
+ return False
+ if val1 is None and "NOTDEFINED" in str(val2).upper():
+ return False
+ if val1 is None and "UNDEFINED" in str(val2).upper():
+ return False
+ if val2 is None and "NOTDEFINED" in str(val1).upper():
+ return False
+ if val2 is None and "UNDEFINED" in str(val1).upper():
+ return False
+ return True
+
if not ifcfile or not element:
return False
if isinstance(value, FreeCAD.Units.Quantity):
@@ -774,7 +789,7 @@ def set_attribute(ifcfile, element, attribute, value):
):
# do not consider default FreeCAD names given to unnamed alements
return False
- if getattr(element, attribute) != value:
+ if differs(getattr(element, attribute, None),value):
FreeCAD.Console.PrintLog(
"Changing IFC attribute value of "
+ str(attribute)
@@ -802,22 +817,25 @@ def set_colors(obj, colors):
else:
colors = [abs(c) for c in colors]
if hasattr(vobj, "ShapeColor"):
- if isinstance(colors[0], (tuple, list)):
- vobj.ShapeColor = colors[0][:3]
- # do not set transparency when the object has more than one color
- #if len(colors[0]) > 3:
- # vobj.Transparency = int(colors[0][3] * 100)
- else:
- vobj.ShapeColor = colors[:3]
- if len(colors) > 3:
- vobj.Transparency = int(colors[3] * 100)
- if hasattr(vobj, "DiffuseColor"):
- # strip out transparency value because it currently gives ugly
- # results in FreeCAD when combining transparent and non-transparent objects
- if all([len(c) > 3 and c[3] != 0 for c in colors]):
- vobj.DiffuseColor = colors
- else:
- vobj.DiffuseColor = [c[:3] for c in colors]
+ # 1.0 materials
+ if not isinstance(colors[0], (tuple, list)):
+ colors = [colors]
+ # set the first color to opaque otherwise it spoils object transparency
+ if len(colors) > 1:
+ #colors[0] = colors[0][:3] + (0.0,)
+ # TEMP HACK: if multiple colors, set everything to opaque because it looks wrong
+ colors = [color[:3] + (0.0,) for color in colors]
+ sapp = []
+ for color in colors:
+ sapp_mat = FreeCAD.Material()
+ if len(color) < 4:
+ sapp_mat.DiffuseColor = color + (1.0,)
+ else:
+ sapp_mat.DiffuseColor = color[:3] + (1.0 - color[3],)
+ sapp_mat.Transparency = color[3] if len(color) > 3 else 0.0
+ sapp.append(sapp_mat)
+ #print(vobj.Object.Label,[[m.DiffuseColor,m.Transparency] for m in sapp])
+ vobj.ShapeAppearance = sapp
def get_body_context_ids(ifcfile):