XternalAppsParametricTool.py (3868B)
1 import FreeCAD as App 2 #from xml.etree import ElementTree 3 from lxml import etree 4 import ExternalAppsList 5 from ToolXML import * 6 import re 7 8 def create(appName, toolName): 9 name = appName + toolName 10 obj = App.ActiveDocument.addObject("App::DocumentObjectGroupPython", name) 11 XternalAppsParametricTool(obj, appName, toolName) 12 return obj 13 14 # TODO: read-only/immutable 15 typeToFreeCADTypeDict = { 16 # TODO:do an XML namespace lookup instead of comparing a constant. 17 'xsd:decimal': 'App::PropertyFloat', 18 'xsd:string': 'App::PropertyString', 19 } 20 21 def typeToFreeCADType(type): 22 if type.startswith('mime:'): 23 return MIMETypeToFreeCADType(MIMEType[5:]) 24 if type in typeToFreeCADTypeDict: 25 return typeToFreeCADTypeDict[type] 26 else: 27 raise ArgumentException('Unsupported XForms type') 28 29 def MIMETypeToFreeCADType(MIMEType): 30 if MIMEType == 'image/svg+xml': 31 return 'App::PropertyLink' 32 else: 33 raise ArgumentException('Unsupported MIME type') 34 35 class XternalAppsParametricTool(): 36 def __init__(self, obj, appName, toolName): 37 self.Type = "XternalAppsParametricTool" 38 self.AppName = appName 39 self.ToolName = toolName 40 obj.Proxy = self 41 self.createPropertiesFromXML(obj) 42 43 def interpretXML(self): 44 types = {} 45 modelInstance = {} 46 inputs = {} 47 48 xml = etree.parse(self.Tool.XForms) 49 model = xml.find('./xforms:model', ns) 50 instanceDocument = etree.ElementTree(model.find('./xforms:instance/*', ns)) 51 52 # Traverse the XForms instance and register all elements in modelInstance[pathToElement] 53 for element in instanceDocument.findall('.//*'): 54 path = instanceDocument.getpath(element) 55 modelInstance[path] = element.text 56 57 # register all xform:bind to types[pathToTargetElement] 58 for bind in model.findall('xforms:bind', ns): 59 for bound in instanceDocument.findall(bind.attrib['ref'], namespaces=bind.nsmap): 60 path = instanceDocument.getpath(bound) 61 # TODO: if has attrib type then … 62 type = bind.attrib['type'] 63 # TODO: I guess XForms implicitly allows intersection types by using several bind statements? 64 types[path] = type 65 # TODO: "required" field 66 67 # register all inputs to inputs[pathToElement] 68 for group in xml.findall('./xforms:group', ns): 69 for input in group.findall('./xforms:input', ns): 70 # TODO: is it safe to pass input unprotected here? 71 modelElement = instanceDocument.find(input.attrib['ref'], namespaces=input.nsmap) 72 if modelElement is None: 73 raise Exception('Could not find ' + input.attrib['ref'] + ' in instance document with namespaces=' + repr(input.nsmap)) 74 type = types[instanceDocument.getpath(modelElement)] 75 inputs[xml.getpath(input)] = (input, modelElement, type) 76 return (xml, types, modelInstance, inputs) 77 78 def createPropertiesFromXML(self, obj): 79 xml, types, modelInstance, inputs = self.interpretXML() 80 for (input, modelElement, type) in inputs.values(): 81 simpleName = re.sub(r'( |[^-a-zA-Z0-9])+', ' ', input.attrib['label']).title().replace(' ', '') 82 input.xpath('ancestor-or-self::group') 83 obj.addProperty(typeToFreeCADType(type), 84 simpleName, 85 "/".join(input.xpath('ancestor-or-self::xforms:group/xforms:label/text()', namespaces=ns)) or None, 86 input.attrib['label'] + '\nA value of type ' + type) 87 88 @property 89 def Tool(self): 90 return ExternalAppsList.apps[self.AppName].Tools[self.ToolName] 91 92 def execute(self, obj): 93 """This is called when the object is recomputed"""