www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | LICENSE

commit a0175805b31b32219bf41a0bb27856daf1d7f7f4
parent 3192eec49ec0e6a8da17dfe30f1c94411af406f5
Author: Suzanne Soy <ligo@suzanne.soy>
Date:   Wed, 27 Jan 2021 02:54:45 +0000

Create object, interpret XML and create <input/> fields with the correct type

Diffstat:
MExternalAppsList.py | 16+---------------
MToolCommand.py | 3++-
AToolXML.py | 15+++++++++++++++
AXternalAppsParametricTool.py | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MmyTool.xforms | 19+++++++++++--------
5 files changed, 122 insertions(+), 24 deletions(-)

diff --git a/ExternalAppsList.py b/ExternalAppsList.py @@ -9,21 +9,7 @@ from PySide import QtCore from xml.etree import ElementTree from MyX11Utils import * - -ns={ -# 'my':"http://github.com/jsmaniac/XternalApps/myTool", - 'XternalApps':"http://github.com/jsmaniac/XternalApps", - 'xforms':"http://www.w3.org/2002/xforms", - 'xsd':"http://www.w3.org/2001/XMLSchema", -} - -def getSingletonFromXML(xml, path): - # TODO: error-checking and a proper message here if there is no matching element or more than one. - elem = xml.find(path, ns) - if elem is None: - raise Exception('Error: could not find ' + path + ' in tool xforms') - else: - return elem +from ToolXML import * class Tool(): def __init__(self, *, appName, toolName, xForms, toolTip, icon, extendedDescription, openHelpFile): diff --git a/ToolCommand.py b/ToolCommand.py @@ -7,6 +7,7 @@ from PySide import QtCore import ExternalAppsList import Embed +import XternalAppsParametricTool class ToolCommand(): def __init__(self, appName, toolName): @@ -21,7 +22,7 @@ class ToolCommand(): } def Activated(self): - print("tool " + self.Tool.ToolName + " of " + self.Tool.AppName + " was activated with xforms" + str(self.Tool.XForms)) + XternalAppsParametricTool.create(self.Tool.AppName, self.Tool.ToolName) def IsActive(self): # return false to grey out the command in the menus, toolbars etc. diff --git a/ToolXML.py b/ToolXML.py @@ -0,0 +1,15 @@ +def getSingletonFromXML(xml, path): + # TODO: error-checking and a proper message here if there is no matching element or more than one. + elem = xml.find(path, ns) + if elem is None: + raise Exception('Error: could not find ' + path + ' in tool xforms') + else: + return elem + +ns={ +# 'my':"http://github.com/jsmaniac/XternalApps/myTool", + 'XternalApps':"http://github.com/jsmaniac/XternalApps", + 'xforms':"http://www.w3.org/2002/xforms", + 'xsd':"http://www.w3.org/2001/XMLSchema", +} + diff --git a/XternalAppsParametricTool.py b/XternalAppsParametricTool.py @@ -0,0 +1,93 @@ +import FreeCAD as App +#from xml.etree import ElementTree +from lxml import etree +import ExternalAppsList +from ToolXML import * +import re + +def create(appName, toolName): + name = appName + toolName + obj = App.ActiveDocument.addObject("App::DocumentObjectGroupPython", name) + XternalAppsParametricTool(obj, appName, toolName) + return obj + +# TODO: read-only/immutable +typeToFreeCADTypeDict = { + # TODO:do an XML namespace lookup instead of comparing a constant. + 'xsd:decimal': 'App::PropertyFloat', + 'xsd:string': 'App::PropertyString', +} + +def typeToFreeCADType(type): + if type.startswith('mime:'): + return MIMETypeToFreeCADType(MIMEType[5:]) + if type in typeToFreeCADTypeDict: + return typeToFreeCADTypeDict[type] + else: + raise ArgumentException('Unsupported XForms type') + +def MIMETypeToFreeCADType(MIMEType): + if MIMEType == 'image/svg+xml': + return 'App::PropertyLink' + else: + raise ArgumentException('Unsupported MIME type') + +class XternalAppsParametricTool(): + def __init__(self, obj, appName, toolName): + self.Type = "XternalAppsParametricTool" + self.AppName = appName + self.ToolName = toolName + obj.Proxy = self + self.createPropertiesFromXML(obj) + + def interpretXML(self): + types = {} + modelInstance = {} + inputs = {} + + xml = etree.parse(self.Tool.XForms) + model = xml.find('./xforms:model', ns) + instanceDocument = etree.ElementTree(model.find('./xforms:instance/*', ns)) + + # Traverse the XForms instance and register all elements in modelInstance[pathToElement] + for element in instanceDocument.findall('.//*'): + path = instanceDocument.getpath(element) + modelInstance[path] = element.text + + # register all xform:bind to types[pathToTargetElement] + for bind in model.findall('xforms:bind', ns): + for bound in instanceDocument.findall(bind.attrib['ref'], namespaces=bind.nsmap): + path = instanceDocument.getpath(bound) + # TODO: if has attrib type then … + type = bind.attrib['type'] + # TODO: I guess XForms implicitly allows intersection types by using several bind statements? + types[path] = type + # TODO: "required" field + + # register all inputs to inputs[pathToElement] + for group in xml.findall('./xforms:group', ns): + for input in group.findall('./xforms:input', ns): + # TODO: is it safe to pass input unprotected here? + modelElement = instanceDocument.find(input.attrib['ref'], namespaces=input.nsmap) + if modelElement is None: + raise Exception('Could not find ' + input.attrib['ref'] + ' in instance document with namespaces=' + repr(input.nsmap)) + type = types[instanceDocument.getpath(modelElement)] + inputs[xml.getpath(input)] = (input, modelElement, type) + return (xml, types, modelInstance, inputs) + + def createPropertiesFromXML(self, obj): + xml, types, modelInstance, inputs = self.interpretXML() + for (input, modelElement, type) in inputs.values(): + simpleName = re.sub(r'( |[^-a-zA-Z0-9])+', ' ', input.attrib['label']).title().replace(' ', '') + input.xpath('ancestor-or-self::group') + obj.addProperty(typeToFreeCADType(type), + simpleName, + "/".join(input.xpath('ancestor-or-self::xforms:group/xforms:label/text()', namespaces=ns)) or None, + input.attrib['label'] + '\nA value of type ' + type) + + @property + def Tool(self): + return ExternalAppsList.apps[self.AppName].Tools[self.ToolName] + +def execute(self, obj): + """This is called when the object is recomputed""" diff --git a/myTool.xforms b/myTool.xforms @@ -21,26 +21,29 @@ <xforms:bind ref="my:svgfile" type="xsd:anyURI" required="true()"/> --> <!-- use XternalApps:pipe to have the file piped directly into the command being run --> - <xforms:bind ref="my:svgfile" type="XternalApps:pipe" required="true()"/> + <xforms:bind ref="my:svgfile" type="mime:image/svg+xml" required="true()"/> <xforms:bind ref="my:option1" type="xsd:decimal" required="true()"/> <xforms:bind ref="my:option2" type="xsd:string" required="true()"/> - <xforms:submission action="myTool.py" method="exec-double-dash" /> - <XternalApps:returns method="pipe" type="image/svg+xml" /> + <!--<xforms:submission action="myTool.py" method="exec-double-dash" />--> + <XternalApps:command medhod="exec" style="double-dash"> + <XternalApps:exception ref="my:svgfile" style="pipe" /> + <XternalApps:returns style="pipe" type="image/svg+xml" /> + </XternalApps:command> </xforms:model> <!-- Description of the user interface follows: --> <xforms:group> <xforms:label>Page 1</xforms:label> - <xforms:input ref="option1" label="Option One ∀"/> - <xforms:input ref="option2" label="Option Two π"/> - <xforms:upload ref="svgfile" accept="image/svg+xml"> + <xforms:input ref="my:option1" label="Option One ∀"/> + <xforms:input ref="my:option2" label="Option Two π"/> + <xforms:upload ref="my:svgfile" accept="image/svg+xml"> <xforms:label>Input image</xforms:label> <xforms:filename ref="@filename" /> </xforms:upload> </xforms:group> <xforms:group> <xforms:label>Page 2</xforms:label> - <xforms:input ref="option2" label="Option Two"/> - <xforms:select1 ref="option3" label="Option Three"> + <xforms:input ref="my:option2" label="Option Two"/> + <xforms:select1 ref="my:option3" label="Option Three"> <xforms:item label="Foo label" value="foo"/> <xforms:item label="Bar label" value="bar"/> </xforms:select1>