Arch: Lightweight mode for Arch References

This commit is contained in:
Yorik van Havre
2019-07-11 19:37:10 -03:00
parent ba9d945308
commit 8932d9296e
3 changed files with 248 additions and 69 deletions

View File

@@ -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())

View File

@@ -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 ("<Object name=" in line) and (part in line):
writemode1 = True
elif writemode1 and ("<Property name=\"SavedInventor\"" in line):
writemode1 = False
writemode2 = True
elif writemode2 and ("<FileIncluded file=" in line):
n = re.findall('file=\"(.*?)\"',line)
if n:
ivfile = n[0]
break
if not ivfile:
return None
if not ivfile in zdoc.namelist():
return None
f = zdoc.open(ivfile)
buf = f.read()
if sys.version_info.major >= 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

View File

@@ -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':