diff --git a/src/Mod/Arch/ArchCommands.py b/src/Mod/Arch/ArchCommands.py
index e010d10b16..81dbe0b5df 100644
--- a/src/Mod/Arch/ArchCommands.py
+++ b/src/Mod/Arch/ArchCommands.py
@@ -730,6 +730,19 @@ def pruneIncluded(objectslist,strict=False):
newlist.append(obj)
return newlist
+def getAllChildren(objectlist):
+ "getAllChildren(objectlist): returns all the children of all the object sin the list"
+ obs = []
+ for o in objectlist:
+ if not o in obs:
+ obs.append(o)
+ if o.OutList:
+ l = getAllChildren(o.OutList)
+ for c in l:
+ if not c in obs:
+ obs.append(c)
+ return obs
+
class _SurveyObserver:
"an observer for the survey() function"
def __init__(self,callback):
diff --git a/src/Mod/Arch/Resources/ui/preferences-ifc.ui b/src/Mod/Arch/Resources/ui/preferences-ifc.ui
index abfae8f376..db2cd1da48 100644
--- a/src/Mod/Arch/Resources/ui/preferences-ifc.ui
+++ b/src/Mod/Arch/Resources/ui/preferences-ifc.ui
@@ -7,7 +7,7 @@
0
0
456
- 666
+ 699
@@ -490,6 +490,26 @@
+ -
+
+
-
+
+
+ If this is checked, all FreeCAD object properties will be stored inside the exported objects, allowing to recreate a full parametric model on reimport.
+
+
+ Export full FreeCAD parametric model
+
+
+ IfcExportFreeCADProperties
+
+
+ Mod/Arch
+
+
+
+
+
diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py
index caad1fd66c..67714a1de9 100644
--- a/src/Mod/Arch/importIFC.py
+++ b/src/Mod/Arch/importIFC.py
@@ -139,7 +139,7 @@ def getPreferences():
global ROOT_ELEMENT, GET_EXTRUSIONS, MERGE_MATERIALS
global MERGE_MODE_ARCH, MERGE_MODE_STRUCT, CREATE_CLONES
global FORCE_BREP, IMPORT_PROPERTIES, STORE_UID, SERIALIZE
- global SPLIT_LAYERS, EXPORT_2D
+ global SPLIT_LAYERS, EXPORT_2D, FULL_PARAMETRIC
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
if FreeCAD.GuiUp and p.GetBool("ifcShowDialog",False):
import FreeCADGui
@@ -165,6 +165,7 @@ def getPreferences():
SERIALIZE = p.GetBool("ifcSerialize",False)
SPLIT_LAYERS = p.GetBool("ifcSplitLayers",False)
EXPORT_2D = p.GetBool("ifcExport2D",True)
+ FULL_PARAMETRIC = p.GetBool("IfcExportFreeCADProperties",False)
def explore(filename=None):
@@ -355,8 +356,8 @@ def insert(filename,docname,skip=[],only=[],root=None):
FreeCAD.ActiveDocument = doc
if DEBUG: print("done.")
-
- global ROOT_ELEMENT
+
+ global ROOT_ELEMENT, parametrics
if root:
ROOT_ELEMENT = root
@@ -395,6 +396,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
structshapes = {} # { id:shaoe } only used for merge mode
mattable = {} # { objid:matid }
sharedobjects = {} # { representationmapid:object }
+ parametrics = [] # a list of imported objects whose parametric relationships need processing after all objects have been created
for r in ifcfile.by_type("IfcRelContainedInSpatialStructure"):
additions.setdefault(r.RelatingStructure.id(),[]).extend([e.id() for e in r.RelatedElements])
for r in ifcfile.by_type("IfcRelAggregates"):
@@ -478,6 +480,16 @@ def insert(filename,docname,skip=[],only=[],root=None):
guid = product.GlobalId
ptype = product.is_a()
if DEBUG: print(count+1,"/",len(products)," creating object #",pid," : ",ptype,end="")
+
+ # checking for full FreeCAD parametric definition, overriding everything else
+ if pid in properties.keys():
+ if "FreeCADPropertySet" in properties[pid].keys():
+ if DEBUG: print(" restoring from parametric definition")
+ obj = createFromProperties(properties[pid],ifcfile)
+ if obj:
+ objects[pid] = obj
+ continue
+
name = str(ptype[3:])
if product.Name:
name = product.Name.encode("utf8")
@@ -872,7 +884,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
if DEBUG: print("adding ",len(cobs), " object(s) to ", objects[host].Label)
Arch.addComponents(cobs,objects[host])
if DEBUG: FreeCAD.ActiveDocument.recompute()
-
+
if DEBUG: print("done.")
FreeCAD.ActiveDocument.recompute()
@@ -918,7 +930,7 @@ def insert(filename,docname,skip=[],only=[],root=None):
count += 1
FreeCAD.ActiveDocument.recompute()
-
+
if DEBUG and annotations: print("done.")
# Materials
@@ -954,6 +966,12 @@ def insert(filename,docname,skip=[],only=[],root=None):
if DEBUG and materials: print("done")
+ # restore links from full parametric definitions
+ for p in parametrics:
+ l = FreeCAD.ActiveDocument.getObject(p[2])
+ if l:
+ setattr(p[0],p[1],l)
+
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
@@ -1020,6 +1038,8 @@ def export(exportList,filename):
annotations.append(obj)
objectslist = [obj for obj in objectslist if not obj in annotations]
objectslist = Arch.pruneIncluded(objectslist)
+ if FULL_PARAMETRIC:
+ objectslist = Arch.getAllChildren(objectslist)
products = {} # { Name: IfcEntity, ... }
surfstyles = {} # { (r,g,b): IfcEntity, ... }
clones = {} # { Basename:[Clonename1,Clonename2,...] }
@@ -1035,7 +1055,7 @@ def export(exportList,filename):
b = Draft.getCloneBase(o,strict=True)
if b:
clones.setdefault(b.Name,[]).append(o.Name)
-
+
#print("clones table: ",clones)
#print(objectslist)
@@ -1234,6 +1254,68 @@ def export(exportList,filename):
if not ifcprop:
#if DEBUG : print("no ifc properties to export")
pass
+ if FULL_PARAMETRIC:
+ # exporting all the object properties
+ FreeCADProps = []
+ FreeCADGuiProps = []
+ FreeCADProps.append(ifcfile.createIfcPropertySingleValue("FreeCADType",None,ifcfile.create_entity("IfcText",obj.TypeId),None))
+ FreeCADProps.append(ifcfile.createIfcPropertySingleValue("FreeCADName",None,ifcfile.create_entity("IfcText",obj.Name),None))
+ for realm,ctx in [("App",obj),("Gui",obj.ViewObject)]:
+ if ctx:
+ for prop in ctx.PropertiesList:
+ if hasattr(ctx,"Proxy"):
+ if ctx.Proxy:
+ if realm == "App":
+ FreeCADProps.append(ifcfile.createIfcPropertySingleValue("FreeCADAppObject",None,ifcfile.create_entity("IfcText",str(ctx.Proxy.__class__)),None))
+ else:
+ FreeCADGuiProps.append(ifcfile.createIfcPropertySingleValue("FreeCADGuiObject",None,ifcfile.create_entity("IfcText",str(ctx.Proxy.__class__)),None))
+ if not(prop in ["IfcProperties","IfcAttributes","Shape","Proxy","ExpressionEngine","AngularDeflection","BoundingBox"]):
+ try:
+ ptype = ctx.getTypeIdOfProperty(prop)
+ except AttributeError:
+ ptype = "Unknown"
+ itype = None
+ ivalue = None
+ if ptype in ["App::PropertyString","App::PropertyEnumeration"]:
+ itype = "IfcText"
+ ivalue = getattr(ctx,prop)
+ elif ptype == "App::PropertyInteger":
+ itype = "IfcInteger"
+ ivalue = getattr(ctx,prop)
+ elif ptype == "App::PropertyFloat":
+ itype = "IfcReal"
+ ivalue = float(getattr(ctx,prop))
+ elif ptype == "App::PropertyBool":
+ itype = "IfcBoolean"
+ ivalue = getattr(ctx,prop)
+ elif ptype in ["App::PropertyVector","App::PropertyPlacement"]:
+ itype = "IfcText"
+ ivalue = str(getattr(ctx,prop))
+ elif ptype in ["App::PropertyLength","App::PropertyDistance"]:
+ itype = "IfcReal"
+ ivalue = float(getattr(ctx,prop).getValueAs("m"))
+ elif ptype == "App::PropertyArea":
+ itype = "IfcReal"
+ ivalue = float(getattr(ctx,prop).getValueAs("m^2"))
+ elif ptype == "App::PropertyLink":
+ t = getattr(ctx,prop)
+ if t:
+ itype = "IfcText"
+ ivalue = "FreeCADLink_" + t.Name
+ else:
+ if DEBUG: print("Unable to encode property ",prop," of type ",ptype)
+ if itype:
+ # TODO add description
+ if realm == "Gui":
+ FreeCADGuiProps.append(ifcfile.createIfcPropertySingleValue("FreeCADGui_"+prop,None,ifcfile.create_entity(itype,ivalue),None))
+ else:
+ FreeCADProps.append(ifcfile.createIfcPropertySingleValue("FreeCAD_"+prop,None,ifcfile.create_entity(itype,ivalue),None))
+ if FreeCADProps:
+ pset = ifcfile.createIfcPropertySet(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'FreeCADPropertySet',None,FreeCADProps)
+ ifcfile.createIfcRelDefinesByProperties(ifcopenshell.guid.compress(uuid.uuid1().hex),history,None,None,[product],pset)
+ if FreeCADGuiProps:
+ pset = ifcfile.createIfcPropertySet(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'FreeCADGuiPropertySet',None,FreeCADGuiProps)
+ ifcfile.createIfcRelDefinesByProperties(ifcopenshell.guid.compress(uuid.uuid1().hex),history,None,None,[product],pset)
count += 1
@@ -1429,7 +1511,7 @@ def export(exportList,filename):
defaulthost = ifcfile.createIfcBuildingStorey(ifcopenshell.guid.compress(uuid.uuid1().hex),history,"Default Storey",'',None,None,None,None,"ELEMENT",None)
ifcfile.createIfcRelAggregates(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'DefaultStoreyLink','',buildings[0],[defaulthost])
ifcfile.createIfcRelContainedInSpatialStructure(ifcopenshell.guid.compress(uuid.uuid1().hex),history,'AnnotationsLink','',annos,defaulthost)
-
+
if DEBUG: print("writing ",filename,"...")
@@ -1444,6 +1526,70 @@ def export(exportList,filename):
os.remove(templatefile)
+def createFromProperties(propsets,ifcfile):
+ "creates a FreeCAD parametric object from a set of properties"
+ obj = None
+ sets = []
+ global parametrics
+ if "FreeCADPropertySet" in propsets.keys():
+ appset = propsets["FreeCADPropertySet"]
+ if "FreeCADType" in appset:
+ if "FreeCADName" in appset:
+ obj = FreeCAD.ActiveDocument.addObject(appset["FreeCADType"],appset["FreeCADName"])
+ if "FreeCADAppObject" in appset:
+ mod,cla = appset["FreeCADAppObject"].split(".")
+ import importlib
+ mod = importlib.import_module(mod)
+ getattr(mod,cla)(obj)
+ sets.append(("App",appset))
+ if FreeCAD.GuiUp:
+ if "FreeCADGuiPropertySet" in propsets.keys():
+ guiset = propsets["FreeCADGuiPropertySet"]
+ if "FreeCADGuiObject" in guiset:
+ mod,cla = guiset["FreeCADGuiObject"].split(".")
+ import importlib
+ mod = importlib.import_module(mod)
+ getattr(mod,cla)(obj.ViewObject)
+ sets.append(("Gui",guiset))
+ if obj and sets:
+ for realm,pset in sets:
+ if realm == "App":
+ target = obj
+ else:
+ target = obj.ViewObject
+ for pid in pset:
+ ient = ifcfile[pid]
+ if ient.is_a("IfcPropertySingleValue"):
+ if ient.Name.startswith("FreeCAD_"):
+ name = ient.Name.split("_")
+ if name in target.PropertiesList:
+ ptype = target.getTypeIdOfProperty(name)
+ if ptype in ["App::PropertyString","App::PropertyEnumeration","App::PropertyInteger","App::PropertyFloat"]:
+ setattr(target,name,ient.NominalValue)
+ elif ptype in ["App::PropertyLength","App:PropertyDistance"]:
+ setattr(target,name,ient.NominalValue*1000)
+ elif ptype == "App::PropertyBool":
+ if ient.NominalValue == ".T.":
+ setattr(target,name,True)
+ else:
+ setattr(target,name,True)
+ elif ptype == "App::PropertyVector":
+ setattr(target,name,FreeCAD.Vector([float(s) for s in ient.NominalValue.split("(")[1].strip(")").split(",")]))
+ elif ptype == "App::PropertyArea":
+ setattr(target,name,ient.NominalValue*1000000)
+ elif ptype == "App::PropertyPlacement":
+ data = ient.NominalValue.split("[")[1].strip("]").split("(")
+ v = FreeCAD.Vector([float(s) for s in data[1].strip(")").split(",")])
+ r = FreeCAD.Rotation([float(s) for s in data[3].strip(")").split(",")])
+ setattr(target,name,FreeCAD.Placement(v,r))
+ elif ptype == "App::PropertyLink":
+ link = ient.NominalValue.split("_")[1]
+ parametrics.append([target,name,link])
+ else:
+ print("Unhandled FreeCAD property:",name," of type:",ptype)
+ return obj
+
+
def createCurve(ifcfile,wire):
"creates an IfcCompositeCurve from a shape"
diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py
index 86d608f7ec..5be322cc4b 100644
--- a/src/Mod/Draft/DraftGui.py
+++ b/src/Mod/Draft/DraftGui.py
@@ -100,6 +100,7 @@ class todo:
QtCore.QTimer.singleShot(0,doTodo).'''
itinerary = []
commitlist = []
+ afteritinerary = []
@staticmethod
def doTasks():
@@ -134,6 +135,17 @@ class todo:
if hasattr(FreeCADGui,"Snapper"):
FreeCADGui.Snapper.restack()
todo.commitlist = []
+ for f, arg in todo.afteritinerary:
+ try:
+ # print("debug: executing",f)
+ if arg:
+ f(arg)
+ else:
+ f()
+ except:
+ wrn = "[Draft.todo.tasks] Unexpected error:", sys.exc_info()[0], "in ", f, "(", arg, ")"
+ FreeCAD.Console.PrintWarning (wrn)
+ todo.afteritinerary = []
@staticmethod
def delay (f, arg):
@@ -148,6 +160,13 @@ class todo:
QtCore.QTimer.singleShot(0, todo.doTasks)
todo.commitlist = cl
+ @staticmethod
+ def delayAfter (f, arg):
+ # print("debug: delaying",f)
+ if todo.afteritinerary == []:
+ QtCore.QTimer.singleShot(0, todo.doTasks)
+ todo.afteritinerary.append((f,arg))
+
#---------------------------------------------------------------------------
# UNITS handling
#---------------------------------------------------------------------------
diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py
index 703cb6f91b..e1d3affc61 100644
--- a/src/Mod/Draft/DraftTools.py
+++ b/src/Mod/Draft/DraftTools.py
@@ -2319,11 +2319,10 @@ class Move(Modifier):
def finish(self,closed=False,cont=False):
if self.ghost:
self.ghost.finalize()
- Modifier.finish(self)
if cont and self.ui:
if self.ui.continueMode:
- FreeCADGui.Selection.clearSelection()
- self.Activated()
+ todo.delayAfter(self.Activated,[])
+ Modifier.finish(self)
def move(self,delta,copy=False):
"moving the real shapes"
@@ -2467,7 +2466,7 @@ class Rotate(Modifier):
self.step = 0
self.center = None
self.ui.arcUi()
- self.ui.isCopy.show()
+ self.ui.modUi()
self.ui.setTitle("Rotate")
self.arctrack = arcTracker()
self.ghost = ghostTracker(self.sel)
@@ -2476,17 +2475,16 @@ class Rotate(Modifier):
def finish(self,closed=False,cont=False):
"finishes the arc"
- Modifier.finish(self)
if self.arctrack:
self.arctrack.finalize()
if self.ghost:
self.ghost.finalize()
- if self.doc:
- self.doc.recompute()
if cont and self.ui:
if self.ui.continueMode:
- FreeCADGui.Selection.clearSelection()
- self.Activated()
+ todo.delayAfter(self.Activated,[])
+ Modifier.finish(self)
+ if self.doc:
+ self.doc.recompute()
def rot (self,angle,copy=False):
"rotating the real shapes"