Arch: Lightweight mode for Arch References
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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':
|
||||
|
||||
Reference in New Issue
Block a user