Arch: Allow to load and save full GuiDocument data from non-GUI mode

This commit is contained in:
Yorik van Havre
2019-07-08 17:30:50 -03:00
parent 2acda4884b
commit 2dd4781fc4

View File

@@ -103,6 +103,7 @@ import xml.sax
import zipfile
import tempfile
import inspect
import binascii
from pivy import coin
@@ -122,6 +123,7 @@ class FreeCADGuiHandler(xml.sax.ContentHandler):
self.properties = {}
self.currentprop = None
self.currentval = None
self.currenttype = None
# Call when an element starts
@@ -130,9 +132,8 @@ class FreeCADGuiHandler(xml.sax.ContentHandler):
if tag == "ViewProvider":
self.current = attributes["name"]
elif tag == "Property":
name = attributes["name"]
if name in ["Visibility","ShapeColor","Transparency","DiffuseColor"]:
self.currentprop = name
self.currenttype = attributes["type"]
self.currentprop = attributes["name"]
elif tag == "Bool":
if attributes["value"] == "true":
self.currentval = True
@@ -146,10 +147,27 @@ class FreeCADGuiHandler(xml.sax.ContentHandler):
self.currentval = (r,g,b)
elif tag == "Integer":
self.currentval = int(attributes["value"])
elif tag == "String":
self.currentval = int(attributes["value"])
elif tag == "Float":
self.currentval = float(attributes["value"])
elif tag == "ColorList":
self.currentval = attributes["file"]
elif tag == "PropertyMaterial":
a = int(attributes["ambientColor"])
d = int(attributes["diffuseColor"])
s = int(attributes["specularColor"])
e = int(attributes["emissiveColor"])
i = float(attributes["shininess"])
t = float(attributes["transparency"])
self.currentval = (a,d,s,e,i,t)
elif tag == "Enum":
if isinstance(self.currentval,int):
self.currentval = [self.currentval,attributes["value"]]
elif isinstance(self.currentval,list):
self.currentval.append(attributes["value"])
elif tag == "Python":
self.currentval = (attributes["value"],attributes["encoded"],attributes["module"],attributes["class"])
elif tag == "Camera":
self.guidata["GuiCameraSettings"] = attributes["settings"]
@@ -164,9 +182,10 @@ class FreeCADGuiHandler(xml.sax.ContentHandler):
self.properties = {}
elif tag == "Property":
if self.currentprop and (self.currentval != None):
self.properties[self.currentprop] = self.currentval
self.properties[self.currentprop] = {"type":self.currenttype,"value":self.currentval}
self.currentprop = None
self.currentval = None
self.currenttype = None
@@ -190,16 +209,20 @@ def getGuiData(filename):
for key,properties in guidata.items():
# open each diffusecolor files and retrieve values
# first 4 bytes are the array length, then each group of 4 bytes is abgr
if "DiffuseColor" in properties:
#print ("opening:",guidata[key]["DiffuseColor"])
df = zdoc.open(guidata[key]["DiffuseColor"])
buf = df.read()
#print (buf," length ",len(buf))
df.close()
cols = []
for i in range(1,int(len(buf)/4)):
cols.append((buf[i*4+3]/255.0,buf[i*4+2]/255.0,buf[i*4+1]/255.0,buf[i*4]/255.0))
guidata[key]["DiffuseColor"] = cols
# https://forum.freecadweb.org/viewtopic.php?t=29382
if isinstance(properties,dict):
for propname in properties.keys():
if properties[propname]["type"] == "App::PropertyColorList":
df = zdoc.open(guidata[key][propname]["value"])
buf = df.read()
df.close()
cols = []
for i in range(1,int(len(buf)/4)):
if sys.version_info.major < 3:
cols.append(ord(buf[i*4+3])/255.0,ord(buf[i*4+2])/255.0,ord(buf[i*4+1])/255.0,ord(buf[i*4])/255.0)
else:
cols.append((buf[i*4+3]/255.0,buf[i*4+2]/255.0,buf[i*4+1]/255.0,buf[i*4]/255.0))
guidata[key][propname]["value"] = cols
zdoc.close()
#print ("guidata:",guidata)
return guidata
@@ -217,17 +240,17 @@ def getColors(filename,nodiffuse=False):
This is a reduced version of getGuiData(), which returns more information.
If nodiffuse = True, DiffuseColor info is discarded, only ShapeColor is read."""
data = getGuiData(filename)
guidata = getGuiData(filename)
colors = {}
for k,v in data.items():
for k,v in guidata.items():
if ("DiffuseColor" in v) and (not nodiffuse):
if len(v["DiffuseColor"]) == 1:
if len(v["DiffuseColor"]["value"]) == 1:
# only one color in DiffuseColor: used for the whole object
colors[k] = v["DiffuseColor"][0]
colors[k] = v["DiffuseColor"]["value"][0]
else:
colors[k] = v["DiffuseColor"]
colors[k] = v["DiffuseColor"]["value"]
elif "ShapeColor" in v:
colors[k] = v["ShapeColor"]
colors[k] = v["ShapeColor"]["value"]
return colors
@@ -424,15 +447,31 @@ def viewer(scene=None,background=(1.0,1.0,1.0)):
def save(document,filename=None,colors=None,camera=None):
def save(document,filename=None,guidata=None,colors=None,camera=None):
"""save(document,filename=None,colors=None,camera=None): Saves the current document. If no filename
is given, the filename stored in the document (document.FileName) is used. A color dictionary of
objName:ShapeColorTuple or obj:DiffuseColorList pairs.can be provided, in that case the objects
will keep their colors when opened in the FreeCAD GUI. If given, camera is a string representing
a coin camera node."""
"""save(document,filename=None,guidata=None,colors=None,camera=None): Saves the current document. If no filename
is given, the filename stored in the document (document.FileName) is used.
You can provide a guidata dictionary, wich can be obtained by the getGuiData() function, and has the form:
{ "objectName" :
{ "propertyName" :
{ "type" : "App::PropertyString",
"value" : "My Value"
}
}
}
The type of the "value" contents depends on the type (int, string, float,tuple...) see inside the FreeCADGuiHandler
class to get an idea.
If guidata is provided, colors and camera attributes are discarded.
Alternatively, a color dictionary of objName:ShapeColorTuple or obj:DiffuseColorList pairs.can be provided,
in that case the objects will keep their colors when opened in the FreeCAD GUI. If given, camera is a string
representing a coin camera node."""
if filename:
if filename:
print("Saving as",filename)
document.saveAs(filename)
else:
@@ -443,21 +482,30 @@ def save(document,filename=None,colors=None,camera=None):
print("Unable to save this document. Please provide a file name")
return
if colors:
zf = zipfile.ZipFile(filename, mode='a')
guidoc = buildGuiDocument(document,colors,camera)
if guidata:
guidocs = buildGuiDocumentFromGuiData(document,guidata)
if guidocs:
zf = zipfile.ZipFile(filename, mode='a')
for guidoc in guidocs:
zf.write(guidoc[0],guidoc[1])
zf.close()
# delete the temp files
for guidoc in guidocs:
os.remove(guidoc[0])
elif colors:
guidoc = buildGuiDocumentFromColors(document,colors,camera)
if guidoc:
zf = zipfile.ZipFile(filename, mode='a')
zf.write(guidoc,'GuiDocument.xml')
zf.close()
# delete the temp file
os.remove(guidoc)
zf.close()
# delete the temp file
os.remove(guidoc)
def getunsigned(color):
def getUnsigned(color):
"""getunsigned(color): returns an unsigned int from a (r,g,b) color tuple"""
"""getUnsigned(color): returns an unsigned int from a (r,g,b) color tuple"""
if (color[0] <= 1) and (color[1] <= 1) and (color[2] <= 1):
# 0->1 float colors, convert to 0->255
@@ -471,9 +519,9 @@ def getunsigned(color):
def buildGuiDocument(document,colors,camera=None):
def buildGuiDocumentFromColors(document,colors,camera=None):
"""buildGuiDocument(document,colors,camera=None): Returns the path to a temporary GuiDocument.xml for the given document.
"""buildGuiDocumentFromColors(document,colors,camera=None): Returns the path to a temporary GuiDocument.xml for the given document.
Colors is a color dictionary of objName:ShapeColorTuple or obj:DiffuseColorList. Camera, if given, is a string representing
a coin camera. You must delete the temporary file after using it."""
@@ -481,6 +529,9 @@ def buildGuiDocument(document,colors,camera=None):
camera = "OrthographicCamera { viewportMapping ADJUST_CAMERA position 0 -0 20000 orientation 0.0, 0.8939966636005564, 0.0, -0.44807361612917324 nearDistance 7561.228 farDistance 63175.688 aspectRatio 1 focalDistance 35368.102 height 2883.365 }"
guidoc = "<?xml version='1.0' encoding='utf-8'?>\n"
guidoc += "<!--\n"
guidoc += " FreeCAD Document, see http://www.freecadweb.org for more information...\n"
guidoc += "-->\n"
guidoc += "<Document SchemaVersion=\"1\">\n"
vps = [obj for obj in document.Objects if obj.Name in colors]
@@ -497,11 +548,11 @@ def buildGuiDocument(document,colors,camera=None):
#guidoc += " <ColorList file=\"DiffuseColor\"/>\n"
#guidoc += " </Property>\n"
guidoc += " <Property name=\"ShapeColor\" type=\"App::PropertyColor\">\n"
guidoc += " <PropertyColor value=\""+getunsigned(vpcol[0])+"\"/>\n"
guidoc += " <PropertyColor value=\""+getUnsigned(vpcol[0])+"\"/>\n"
guidoc += " </Property>\n"
else:
guidoc += " <Property name=\"ShapeColor\" type=\"App::PropertyColor\">\n"
guidoc += " <PropertyColor value=\""+getunsigned(vpcol)+"\"/>\n"
guidoc += " <PropertyColor value=\""+getUnsigned(vpcol)+"\"/>\n"
guidoc += " </Property>\n"
guidoc += " <Property name=\"Visibility\" type=\"App::PropertyBool\">\n"
guidoc += " <Bool value=\"true\"/>\n"
@@ -534,6 +585,117 @@ def buildGuiDocument(document,colors,camera=None):
return tempxml
def buildGuiDocumentFromGuiData(document,guidata):
"""buildGuiDocumentFromColors(document,guidata): Returns the path to a temporary GuiDocument.xml for the given document.
GuiData is a dictionary, wich can be obtained by the getGuiData() function, and has the form:
{ "objectName" :
{ "propertyName" :
{ "type" : "App::PropertyString",
"value" : "My Value"
}
}
}
This function returns a list of (filepath,name) tuples, the first one named GuiDocument.xml and the next ones being color files
"""
files = []
colorindex = 1
guidoc = "<?xml version='1.0' encoding='utf-8'?>\n"
guidoc += "<!--\n"
guidoc += " FreeCAD Document, see http://www.freecadweb.org for more information...\n"
guidoc += "-->\n"
guidoc += "<Document SchemaVersion=\"1\">\n"
vps = [obj for obj in document.Objects if obj.Name in guidata]
if not vps:
return None
guidoc += " <ViewProviderData Count=\""+str(len(vps))+"\">\n"
for vp in vps:
properties = guidata[vp.Name]
guidoc += " <ViewProvider name=\""+vp.Name+"\" expanded=\"0\">\n"
guidoc += " <Properties Count=\""+str(len(properties))+"\">\n"
for name,prop in properties.items():
guidoc += " <Property name=\""+name+"\" type=\""+prop["type"]+"\">\n"
if prop["type"] in ["App::PropertyString","PropertyFont"]:
guidoc += " <String value=\""+prop["value"]+"\"/>\n"
elif prop["type"] in ["App::PropertyAngle","App::PropertyFloat","App::PropertyFloatConstraint","App::PropertyDistance","App::PropertyLength"]:
guidoc += " <Float value=\""+str(prop["value"])+"\"/>\n"
elif prop["type"] in ["App::PropertyInteger","App::PropertyPercent"]:
guidoc += " <Integer value=\""+str(prop["value"])+"\"/>\n"
elif prop["type"] in ["App::PropertyBool"]:
guidoc += " <Bool value=\""+str(prop["value"]).lower()+"\"/>\n"
elif prop["type"] in ["App::PropertyEnumeration"]:
if isinstance(prop["value"],int):
guidoc += " <Integer value=\""+str(prop["value"])+"\"/>\n"
elif isinstance(prop["value"],list):
guidoc += " <Integer value=\""+str(prop["value"][0])+"\" CustomEnum=\"true\"/>\n"
guidoc += " <CustomEnumList count=\""+str(len(prop["value"])-1)+"\">\n"
for v in prop["value"][1:]:
guidoc += " <Enum value=\""+v+"\"/>\n"
guidoc += " </CustomEnumList>\n"
elif prop["type"] in ["App::PropertyColorList"]:
# number of colors
buf = binascii.unhexlify(hex(len(prop["value"]))[2:].zfill(2))
# fill 3 other bytes (the number of colors occupies 4 bytes)
buf += binascii.unhexlify(hex(0)[2:].zfill(2))
buf += binascii.unhexlify(hex(0)[2:].zfill(2))
buf += binascii.unhexlify(hex(0)[2:].zfill(2))
# fill colors in abgr order
for color in prop["value"]:
if len(color) >= 4:
buf += binascii.unhexlify(hex(int(color[3]*255))[2:].zfill(2))
else:
buf += binascii.unhexlify(hex(0)[2:].zfill(2))
buf += binascii.unhexlify(hex(int(color[2]*255))[2:].zfill(2))
buf += binascii.unhexlify(hex(int(color[1]*255))[2:].zfill(2))
buf += binascii.unhexlify(hex(int(color[0]*255))[2:].zfill(2))
tempcolorfile = tempfile.mkstemp(suffix=".xml")[-1]
f = open(tempcolorfile,"wb")
f.write(buf)
f.close()
tempcolorname = "ColorFile" + str(colorindex)
colorindex += 1
guidoc += " <ColorList file=\""+tempcolorname+"\"/>\n"
files.append((tempcolorfile,tempcolorname))
elif prop["type"] in ["App::PropertyMaterial"]:
guidoc += " <PropertyMaterial ambientColor=\""+str(prop["value"][0])
guidoc += "\" diffuseColor=\""+str(prop["value"][1])+"\" specularColor=\""+str(prop["value"][2])
guidoc += "\" emissiveColor=\""+str(prop["value"][3])+"\" shininess=\""+str(prop["value"][4])
guidoc += "\" transparency=\""+str(prop["value"][5])+"\"/>\n"
elif prop["type"] in ["App::PropertyPythonObject"]:
guidoc += " <Python value=\""+str(prop["value"][0])+"\" encoded=\""
guidoc += str(prop["value"][1])+"\" module=\""+str(prop["value"][2])+"\" class=\""
guidoc += str(prop["value"][3])+"\"/>\n"
elif prop["type"] in ["App::PropertyColor"]:
guidoc += " <PropertyColor value=\""+str(getUnsigned(prop["value"]))+"\"/>\n"
guidoc += " </Property>\n"
guidoc += " </Properties>\n"
guidoc += " </ViewProvider>\n"
guidoc +=" </ViewProviderData>\n"
if "GuiCameraSettings" in guidata:
guidoc +=" <Camera settings=\" " + guidata["GuiCameraSettings"] + " \"/>\n"
guidoc += "</Document>\n"
# although the zipfile module has a writestr() function that should allow us to write the
# string above directly to the zip file, I couldn't manage to make it work.. So we rather
# use a temp file here, which works.
#print(guidoc)
tempxml = tempfile.mkstemp(suffix=".xml")[-1]
f = open(tempxml,"w")
f.write(guidoc)
f.close()
files.insert(0,(tempxml,"GuiDocument.xml"))
return files
def getViewProviderClass(obj):
"""getViewProviderClass(obj): tries to identify the associated view provider for a
@@ -562,3 +724,28 @@ def getViewProviderClass(obj):
return(mod,"_ViewProviderDraft")
print("Found no matching view provider for",mod,objclass)
return None
def extract(filename,inputpath,outputpath=None):
"""extract(filename,inputpath,outputpath=None): extracts 'inputpath' which is a filename
stored in infile (a FreeCAD or zip file). If outputpath is given, the file is saved as outputpath and
nothing is returned. If not, the contents of the inputfile are returned and nothing is saved."""
zdoc = zipfile.ZipFile(filename)
if zdoc:
if inputpath in zdoc.namelist():
gf = zdoc.open(inputpath)
data = gf.read()
gf.close()
if data:
if outputpath:
if isinstance(data,str):
of = open(outputpath,"w")
else:
of = open(outputpath,"wb")
of.write(data)
of.close()
else:
return data