diff --git a/src/Mod/Arch/ArchBuildingPart.py b/src/Mod/Arch/ArchBuildingPart.py index 3f053fbd7d..9f68cf5115 100644 --- a/src/Mod/Arch/ArchBuildingPart.py +++ b/src/Mod/Arch/ArchBuildingPart.py @@ -22,7 +22,14 @@ #* * #*************************************************************************** -import FreeCAD,Draft,ArchCommands,DraftVecUtils,sys,ArchIFC +import FreeCAD +import Draft +import ArchCommands +import DraftVecUtils +import sys +import ArchIFC +import tempfile +import os if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui @@ -35,7 +42,6 @@ else: def QT_TRANSLATE_NOOP(ctxt,txt): return txt # \endcond -import sys if sys.version_info.major >= 3: unicode = str @@ -335,6 +341,9 @@ class BuildingPart: obj.addProperty("App::PropertyString","Tag","Component",QT_TRANSLATE_NOOP("App::Property","An optional tag for this component")) if not "Shape" in pl: obj.addProperty("Part::PropertyPartShape","Shape","BuildingPart",QT_TRANSLATE_NOOP("App::Property","The shape of this object")) + if not "SavedInventor" in pl: + obj.addProperty("App::PropertyFileIncluded","SavedInventor","BuildingPart",QT_TRANSLATE_NOOP("App::Property","This property stores an inventor representation for this object")) + obj.setEditorMode("SavedInventor",2) self.Type = "BuildingPart" @@ -368,7 +377,7 @@ class BuildingPart: elif prop == "Placement": if hasattr(self,"oldPlacement"): - if self.oldPlacement: + if self.oldPlacement and (self.oldPlacement != obj.Placement): deltap = obj.Placement.Base.sub(self.oldPlacement.Base) if deltap.Length == 0: deltap = None @@ -399,8 +408,14 @@ class BuildingPart: # gather all the child shapes into a compound shapes = self.getShapes(obj) if shapes: + f = [] + for s in shapes: + f.extend(s.Faces) + #print("faces before compound:",len(f)) import Part - obj.Shape = Part.makeCompound(shapes) + obj.Shape = Part.makeCompound(f) + #print("faces after compound:",len(obj.Shape.Faces)) + #print("recomputing ",obj.Label) obj.Area = self.getArea(obj) def getArea(self,obj): @@ -421,18 +436,9 @@ class BuildingPart: "recursively get the shapes of objects inside this BuildingPart" shapes = [] - if obj.isDerivedFrom("Part::Feature") and obj.Shape and (not obj.Shape.isNull()): - shapes.append(obj.Shape) - if hasattr(obj,"Group"): - for child in obj.Group: - shapes.extend(self.getShapes(child)) - for i in obj.InList: - if hasattr(i,"Hosts"): - if obj in i.Hosts: - shapes.extend(self.getShapes(i)) - elif hasattr(i,"Host"): - if obj == i.Host: - shapes.extend(self.getShapes(i)) + for child in Draft.getGroupContents(obj): + if child.isDerivedFrom("Part::Feature"): + shapes.extend(child.Shape.Faces) return shapes def getSpaces(self,obj): @@ -514,7 +520,6 @@ class ViewProviderBuildingPart: vobj.ChildrenLineColor = (float((c>>24)&0xFF)/255.0,float((c>>16)&0xFF)/255.0,float((c>>8)&0xFF)/255.0,0.0) if not "ChildrenTransparency" in pl: vobj.addProperty("App::PropertyPercent","ChildrenTransparency","Children",QT_TRANSLATE_NOOP("App::Property","The transparency of child objects")) - if not "CutView" in pl: vobj.addProperty("App::PropertyBool","CutView","Clip",QT_TRANSLATE_NOOP("App::Property","Cut the view above this level")) if not "CutMargin" in pl: @@ -522,6 +527,8 @@ class ViewProviderBuildingPart: vobj.CutMargin = 1600 if not "AutoCutView" in pl: vobj.addProperty("App::PropertyBool","AutoCutView","Clip",QT_TRANSLATE_NOOP("App::Property","Turn cutting on when activating this level")) + if not "SaveInventor" in pl: + vobj.addProperty("App::PropertyBool","SaveInventor","BuildingPart",QT_TRANSLATE_NOOP("App::Property","If this is enabled, the inventor representation of this object will be saved in the FreeCAD file, allowing to reference it in other file sin lightweight mode.")) def onDocumentRestored(self,vobj): @@ -591,6 +598,9 @@ class ViewProviderBuildingPart: if len(colors) == len(obj.Shape.Faces): if colors != obj.ViewObject.DiffuseColor: obj.ViewObject.DiffuseColor = colors + self.writeInventor(obj) + #else: + #print("color mismatch:",len(colors),"colors,",len(obj.Shape.Faces),"faces") elif prop == "Group": self.onChanged(obj.ViewObject,"ChildrenOverride") elif prop == "Label": @@ -601,23 +611,14 @@ class ViewProviderBuildingPart: "recursively get the colors of objects inside this BuildingPart" colors = [] - if obj.isDerivedFrom("Part::Feature") and obj.Shape and (not obj.Shape.isNull()): - if hasattr(obj.ViewObject,"DiffuseColor") and (len(obj.ViewObject.DiffuseColor) == len(obj.Shape.Faces)): - colors.extend(obj.ViewObject.DiffuseColor) - elif hasattr(obj.ViewObject,"ShapeColor"): - c = obj.ViewObject.ShapeColor[:3]+(obj.ViewObject.Transparency/100.0,) - for i in range(len(obj.Shape.Faces)): - colors.append(c) - if hasattr(obj,"Group"): - for child in obj.Group: - colors.extend(self.getColors(child)) - for i in obj.InList: - if hasattr(i,"Hosts"): - if obj in i.Hosts: - colors.extend(self.getColors(i)) - elif hasattr(i,"Host"): - if obj == i.Host: - colors.extend(self.getColors(i)) + for child in Draft.getGroupContents(obj): + if child.isDerivedFrom("Part::Feature"): + if len(child.ViewObject.DiffuseColor) == len(child.Shape.Faces): + colors.extend(child.ViewObject.DiffuseColor) + else: + c = child.ViewObject.ShapeColor[:3]+(child.ViewObject.Transparency/100.0,) + for i in range(len(child.Shape.Faces)): + colors.append(c) return colors def onChanged(self,vobj,prop): @@ -728,6 +729,8 @@ class ViewProviderBuildingPart: # turn clipping off when turning the object off if hasattr(vobj,"Visibility") and not(vobj.Visibility) and hasattr(vobj,"CutView"): vobj.CutView = False + elif prop == "SaveInventor": + self.writeInventor(vobj.Object) def onDelete(self,vobj,subelements): @@ -737,6 +740,7 @@ class ViewProviderBuildingPart: for o in Draft.getGroupContents(vobj.Object.Group,walls=True): if hasattr(o.ViewObject,"Lighting"): o.ViewObject.Lighting = "Two side" + return True def doubleClicked(self,vobj): @@ -865,5 +869,36 @@ class ViewProviderBuildingPart: def __setstate__(self,state): return None + def writeInventor(self,obj): + + def callback(match): + return next(callback.v) + + if hasattr(obj.ViewObject,"SaveInventor") and obj.ViewObject.SaveInventor: + if obj.Shape and obj.Shape.Faces and hasattr(obj,"SavedInventor"): + colors = obj.ViewObject.DiffuseColor + if len(colors) != len(obj.Shape.Faces): + print("Debug: Colors mismatch in",obj.Label) + colors = None + iv = self.Object.Shape.writeInventor() + import re + if colors: + if len(re.findall("IndexedFaceSet",iv)) == len(obj.Shape.Faces): + # convert colors to iv representations + colors = ["Material { diffuseColor "+str(color[0])+" "+str(color[1])+" "+str(color[2])+"}\n IndexedFaceSet" for color in colors] + # replace + callback.v=iter(colors) + iv = re.sub("IndexedFaceSet",callback,iv) + else: + print("Debug: IndexedFaceSet mismatch in",obj.Label) + # save embedded file + tf = tempfile.mkstemp(prefix=obj.Name,suffix=".iv")[1] + f = open(tf,"w") + f.write(iv) + f.close() + obj.SavedInventor = tf + os.remove(tf) + + if FreeCAD.GuiUp: FreeCADGui.addCommand('Arch_BuildingPart',CommandBuildingPart()) diff --git a/src/Mod/Arch/ArchReference.py b/src/Mod/Arch/ArchReference.py index ff5edabf86..c0bddce152 100644 --- a/src/Mod/Arch/ArchReference.py +++ b/src/Mod/Arch/ArchReference.py @@ -25,7 +25,11 @@ __author__ = "Yorik van Havre" __url__ = "http://www.freecadweb.org" -import FreeCAD,os,zipfile,re,sys +import FreeCAD +import os +import zipfile +import re +import sys if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui @@ -91,14 +95,23 @@ class ArchReference: obj.addProperty("App::PropertyFile","File","Reference",QT_TRANSLATE_NOOP("App::Property","The base file this component is built upon")) if not "Part" in pl: obj.addProperty("App::PropertyString","Part","Reference",QT_TRANSLATE_NOOP("App::Property","The part to use from the base file")) - if not "TransientReference" in pl: - obj.addProperty("App::PropertyBool","TransientReference","Reference",QT_TRANSLATE_NOOP("App::Property","If True, the shape will be discarded when turning visibility off, resulting in a lighter file, but with an additional loading time when turning the object back on")) + if not "ReferenceMode" in pl: + obj.addProperty("App::PropertyEnumeration","ReferenceMode","Reference",QT_TRANSLATE_NOOP("App::Property","The way the referenced objects are included in the current document. 'Normal' includes the shape, 'Transient' discards the shape when the object is switched off (smaller filesize), 'Lightweight' does not import the shape but only the OpenInventor representation")) + obj.ReferenceMode = ["Normal","Transient","Lightweight"] + if "TransientReference" in pl: + if obj.TransientReference: + obj.ReferenceMode = "Transient" + obj.removeProperty("TransientReference") + FreeCAD.Console.PrintMessage("Upgrading "+obj.Label+" TransientReference property to ReferenceMode\n") self.Type = "Reference" def onDocumentRestored(self,obj): ArchReference.setProperties(self,obj) self.reload = False + if obj.ReferenceMode == "Lightweight": + if obj.ViewObject and obj.ViewObject.Proxy: + obj.ViewObject.Proxy.loadInventor(obj) def __getstate__(self): @@ -112,26 +125,31 @@ class ArchReference: if prop in ["File","Part"]: self.reload = True - elif prop == "TransientReference": - if obj.TransientReference: + elif prop == "ReferenceMode": + if obj.ReferenceMode == "Normal": + if obj.ViewObject and obj.ViewObject.Proxy: + obj.ViewObject.Proxy.unloadInventor(obj) if (not obj.Shape) or obj.Shape.isNull(): self.reload = True obj.touch() - else: - if obj.ViewObject: - obj.ViewObject.Visibility = False - else: - self.reload = False - import Part - pl = obj.Placement - obj.Shape = Part.Shape() - obj.Placement = pl + elif obj.ReferenceMode == "Transient": + if obj.ViewObject and obj.ViewObject.Proxy: + obj.ViewObject.Proxy.unloadInventor(obj) + self.reload = False + elif obj.ReferenceMode == "Lightweight": + self.reload = False + import Part + pl = obj.Placement + obj.Shape = Part.Shape() + obj.Placement = pl + if obj.ViewObject and obj.ViewObject.Proxy: + obj.ViewObject.Proxy.loadInventor(obj) def execute(self,obj): pl = obj.Placement filename = self.getFile(obj) - if filename and obj.Part and self.reload: + if filename and obj.Part and self.reload and obj.ReferenceMode in ["Normal","Transient"]: self.parts = self.getPartsList(obj) if self.parts: zdoc = zipfile.ZipFile(filename) @@ -175,7 +193,7 @@ class ArchReference: else: # search for subpaths in current folder altfile = None - subdirs = splitall(os.path.dirname(filename)) + subdirs = self.splitall(os.path.dirname(filename)) for i in range(len(subdirs)): subpath = [currentdir]+subdirs[-i:]+[basename] altfile = os.path.join(*subpath) @@ -185,6 +203,8 @@ class ArchReference: return filename def getPartsList(self,obj,filename=None): + + "returns a list of Part-based objects in a FCStd file" parts = {} filename = self.getFile(obj,filename) @@ -226,6 +246,8 @@ class ArchReference: def getColors(self,obj): + "returns the DiffuseColor of the referenced object" + filename = self.getFile(obj) if not filename: return None @@ -269,6 +291,24 @@ class ArchReference: return colors return None + def splitall(self,path): + + "splits a path between its components" + + allparts = [] + while 1: + parts = os.path.split(path) + if parts[0] == path: # sentinel for absolute paths + allparts.insert(0, parts[0]) + break + elif parts[1] == path: # sentinel for relative paths + allparts.insert(0, parts[1]) + break + else: + path = parts[0] + allparts.insert(0, parts[1]) + return allparts + class ViewProviderArchReference: @@ -380,7 +420,7 @@ class ViewProviderArchReference: vobj.Object.Proxy.reload = True vobj.Object.Proxy.execute(vobj.Object) else: - if hasattr(vobj.Object,"TransientReference") and vobj.Object.TransientReference: + if hasattr(vobj.Object,"ReferenceMode") and vobj.Object.ReferenceMode == "Transient": vobj.Object.Proxy.reload = False import Part pl = vobj.Object.Placement @@ -420,11 +460,127 @@ class ViewProviderArchReference: if self.Object.File: FreeCAD.openDocument(self.Object.File) + def loadInventor(self,obj): + + "loads an openinventor file and replace the root node of this object" + + # check inventor contents + ivstring = self.getInventorString(obj) + if not ivstring: + FreeCAD.Console.PrintWarning("Unable to get lightWeight node for object referenced in "+obj.Label+"\n") + return + from pivy import coin + inputnode = coin.SoInput() + inputnode.setBuffer(ivstring) + lwnode = coin.SoDB.readAll(inputnode) + if not isinstance(lwnode,coin.SoSeparator): + FreeCAD.Console.PrintError("Invalid lightWeight node for object referenced in "+obj.Label+"\n") + return + if lwnode.getNumChildren() < 2: + FreeCAD.Console.PrintError("Invalid lightWeight node for object referenced in "+obj.Label+"\n") + return + flatlines = lwnode + shaded = lwnode.getChild(0) + wireframe = lwnode.getChild(1) + + # check node contents + rootnode = obj.ViewObject.RootNode + if rootnode.getNumChildren() < 3: + FreeCAD.Console.PrintError("Invalid root node in "+obj.Label+"\n") + return + switch = rootnode.getChild(2) + if switch.getNumChildren() != 4: + FreeCAD.Console.PrintError("Invalid root node in "+obj.Label+"\n") + return + + # keep a copy of the original nodes + self.orig_flatlines = switch.getChild(0).copy() + self.orig_shaded = switch.getChild(1).copy() + self.orig_wireframe = switch.getChild(2).copy() + + # replace root node of object + switch.replaceChild(0,flatlines) + switch.replaceChild(1,shaded) + switch.replaceChild(2,wireframe) + + def unloadInventor(self,obj): + + "restore original nodes" + + if (not hasattr(self,"orig_flatlines")) or (not self.orig_flatlines): + return + if (not hasattr(self,"orig_shaded")) or (not self.orig_shaded): + return + if (not hasattr(self,"orig_wireframe")) or (not self.orig_wireframe): + return + + # check node contents + rootnode = obj.ViewObject.RootNode + if rootnode.getNumChildren() < 3: + FreeCAD.Console.PrintError("Invalid root node in "+obj.Label+"\n") + return + switch = rootnode.getChild(2) + if switch.getNumChildren() != 4: + FreeCAD.Console.PrintError("Invalid root node in "+obj.Label+"\n") + return + + # replace root node of object + switch.replaceChild(0,self.orig_flatlines) + switch.replaceChild(1,self.orig_shaded) + switch.replaceChild(2,self.orig_wireframe) + + # discard old content + self.orig_flatlines = None + self.orig_shaded = None + self.orig_wireframe = None + + def getInventorString(self,obj): + + "locates and loads an iv file saved together with an object, if existing" + + filename = obj.Proxy.getFile(obj) + if not filename: + return None + part = obj.Part + if not obj.Part: + return None + zdoc = zipfile.ZipFile(filename) + if not "Document.xml" in zdoc.namelist(): + return None + ivfile = None + with zdoc.open("Document.xml") as docf: + writemode1 = False + writemode2 = False + for line in docf: + if sys.version_info.major >= 3: + line = line.decode("utf8") + if ("= 3: + buf = buf.decode("utf8") + f.close() + buf = buf.replace("lineWidth 2","lineWidth "+str(int(obj.ViewObject.LineWidth))) + return buf + class ArchReferenceTaskPanel: - '''The editmode TaskPanel for Axis objects''' + '''The editmode TaskPanel for Reference objects''' def __init__(self,obj): @@ -539,21 +695,7 @@ class ArchReferenceCommand: FreeCAD.ActiveDocument.commitTransaction() FreeCADGui.doCommand("obj.ViewObject.Document.setEdit(obj.ViewObject, 0)") + + if FreeCAD.GuiUp: FreeCADGui.addCommand('Arch_Reference', ArchReferenceCommand()) - - -def splitall(path): - allparts = [] - while 1: - parts = os.path.split(path) - if parts[0] == path: # sentinel for absolute paths - allparts.insert(0, parts[0]) - break - elif parts[1] == path: # sentinel for relative paths - allparts.insert(0, parts[1]) - break - else: - path = parts[0] - allparts.insert(0, parts[1]) - return allparts diff --git a/src/Mod/Draft/WorkingPlane.py b/src/Mod/Draft/WorkingPlane.py index dea7f036b6..7a1c598df0 100644 --- a/src/Mod/Draft/WorkingPlane.py +++ b/src/Mod/Draft/WorkingPlane.py @@ -207,7 +207,9 @@ class plane: def alignToCurve(self, shape, offset): - if shape.ShapeType == 'Edge': + if shape.isNull(): + return False + elif shape.ShapeType == 'Edge': #??? TODO: process curve here. look at shape.edges[0].Curve return False elif shape.ShapeType == 'Wire':