From 352accabd99e2b75539e782bf48b8867dd3f7c88 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Wed, 26 Jul 2017 12:51:52 -0300 Subject: [PATCH] Draft: Fixed Move & Rotate continue mode --- src/Mod/Arch/ArchCommands.py | 13 ++ src/Mod/Arch/Resources/ui/preferences-ifc.ui | 22 ++- src/Mod/Arch/importIFC.py | 160 ++++++++++++++++++- src/Mod/Draft/DraftGui.py | 19 +++ src/Mod/Draft/DraftTools.py | 16 +- 5 files changed, 213 insertions(+), 17 deletions(-) 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"