WIP, XternalApps mostly works, currently investigating SearchTools
This commit is contained in:
parent
b9b228b8cd
commit
36aa4e2642
|
@ -21,7 +21,7 @@ class AppCommand():
|
||||||
}
|
}
|
||||||
|
|
||||||
def Activated(self):
|
def Activated(self):
|
||||||
p = Embed.ExternalAppInstance(self.appName)
|
p = Embed.XternalAppInstance(self.appName)
|
||||||
p.waitForWindow()
|
p.waitForWindow()
|
||||||
|
|
||||||
def IsActive(self):
|
def IsActive(self):
|
||||||
|
|
14
Embed.py
14
Embed.py
|
@ -13,10 +13,10 @@ from MyX11Utils import *
|
||||||
# def closeEvent
|
# def closeEvent
|
||||||
|
|
||||||
class EmbeddedWindow(QtCore.QObject):
|
class EmbeddedWindow(QtCore.QObject):
|
||||||
def __init__(self, app, externalAppInstance, processId, windowId):
|
def __init__(self, app, xternalAppInstance, processId, windowId):
|
||||||
super(EmbeddedWindow, self).__init__()
|
super(EmbeddedWindow, self).__init__()
|
||||||
self.app = app
|
self.app = app
|
||||||
self.externalAppInstance = externalAppInstance
|
self.xternalAppInstance = xternalAppInstance
|
||||||
self.processId = processId
|
self.processId = processId
|
||||||
self.windowId = windowId
|
self.windowId = windowId
|
||||||
self.mdi = Gui.getMainWindow().findChild(QtGui.QMdiArea)
|
self.mdi = Gui.getMainWindow().findChild(QtGui.QMdiArea)
|
||||||
|
@ -66,12 +66,12 @@ class EmbeddedWindow(QtCore.QObject):
|
||||||
self.xwd.setParent(None)
|
self.xwd.setParent(None)
|
||||||
self.timer.stop()
|
self.timer.stop()
|
||||||
# remove from dictionary of found windows
|
# remove from dictionary of found windows
|
||||||
self.externalAppInstance.foundWindows.pop(self.windowId, None)
|
self.xternalAppInstance.foundWindows.pop(self.windowId, None)
|
||||||
# avoid GC
|
# avoid GC
|
||||||
self.externalAppInstance.closedWindows[self.windowId] = self
|
self.xternalAppInstance.closedWindows[self.windowId] = self
|
||||||
# re-attach in case it didn't close (confirmation dialog etc.)
|
# re-attach in case it didn't close (confirmation dialog etc.)
|
||||||
print('waitForWindow')
|
print('waitForWindow')
|
||||||
self.externalAppInstance.waitForWindow()
|
self.xternalAppInstance.waitForWindow()
|
||||||
# try:
|
# try:
|
||||||
# self.xw = QtGui.QWindow.fromWinId(self.windowId)
|
# self.xw = QtGui.QWindow.fromWinId(self.windowId)
|
||||||
# self.xwd = QtGui.QWidget.createWindowContainer(self.xw)
|
# self.xwd = QtGui.QWidget.createWindowContainer(self.xw)
|
||||||
|
@ -119,9 +119,9 @@ def deleted(widget):
|
||||||
except:
|
except:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class ExternalAppInstance(QtCore.QObject):
|
class XternalAppInstance(QtCore.QObject):
|
||||||
def __init__(self, appName):
|
def __init__(self, appName):
|
||||||
super(ExternalAppInstance, self).__init__()
|
super(XternalAppInstance, self).__init__()
|
||||||
self.app = XternalAppsList.apps[appName]
|
self.app = XternalAppsList.apps[appName]
|
||||||
# Start the application
|
# Start the application
|
||||||
# TODO: popen_process shouldn't be exposed to in-document scripts, it would allow them to redirect output etc.
|
# TODO: popen_process shouldn't be exposed to in-document scripts, it would allow them to redirect output etc.
|
||||||
|
|
|
@ -72,8 +72,8 @@ class XternalAppsWorkbench(Workbench):
|
||||||
for toolName in XternalAppsList.apps[self.appName].Tools]
|
for toolName in XternalAppsList.apps[self.appName].Tools]
|
||||||
|
|
||||||
# Create menus and toolbars
|
# Create menus and toolbars
|
||||||
self.appendMenu("ExternalApplications", self.list)
|
self.appendMenu("XternalApplications", self.list)
|
||||||
self.appendToolbar("ExternalApplications", self.list)
|
self.appendToolbar("XternalApplications", self.list)
|
||||||
|
|
||||||
def Activated(self):
|
def Activated(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -26,7 +26,7 @@ class ReloadCommand():
|
||||||
def GetResources(self):
|
def GetResources(self):
|
||||||
return {
|
return {
|
||||||
'Pixmap': os.path.dirname(__file__) + '/icons/' + "reload.svg",
|
'Pixmap': os.path.dirname(__file__) + '/icons/' + "reload.svg",
|
||||||
'Accel': "Shit+R", # R for Reload
|
'Accel': "Ctrl+R", # R for Reload
|
||||||
'MenuText': "Reload XternalApps (developper tool)",
|
'MenuText': "Reload XternalApps (developper tool)",
|
||||||
'ToolTip': "Reload some modules of the XternalApps workbenches, needed for development only.",
|
'ToolTip': "Reload some modules of the XternalApps workbenches, needed for development only.",
|
||||||
}
|
}
|
||||||
|
|
106
SearchTools/SearchTools.py
Normal file
106
SearchTools/SearchTools.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
if True:
|
||||||
|
from PySide import QtGui
|
||||||
|
mw = Gui.getMainWindow()
|
||||||
|
mdi = mw.findChild(QtGui.QMdiArea)
|
||||||
|
|
||||||
|
mw.findChildren(QtGui.QToolBar, 'XternalApplications')
|
||||||
|
mw.findChildren(QtGui.QToolBar, 'XternalApplications')[0]
|
||||||
|
|
||||||
|
wdg = QtGui.QWidget()
|
||||||
|
lay = QtGui.QGridLayout(wdg)
|
||||||
|
mwx = QtGui.QMainWindow()
|
||||||
|
|
||||||
|
sea = QtGui.QLineEdit()
|
||||||
|
lay.addWidget(sea)
|
||||||
|
|
||||||
|
lsv = QtGui.QListView()
|
||||||
|
sim = QtGui.QStandardItemModel()
|
||||||
|
flt = QtCore.QSortFilterProxyModel()
|
||||||
|
flt.setSourceModel(sim)
|
||||||
|
flt.setFilterCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive)
|
||||||
|
sea.textChanged.connect(flt.setFilterWildcard)
|
||||||
|
# make the QListView non-editable
|
||||||
|
lsv.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
|
||||||
|
lsv.setModel(flt)
|
||||||
|
lay.addWidget(lsv)
|
||||||
|
|
||||||
|
mwx.setCentralWidget(wdg)
|
||||||
|
mdi.addSubWindow(mwx)
|
||||||
|
|
||||||
|
all_tbs = set()
|
||||||
|
for wbname, workbench in Gui.listWorkbenches().items():
|
||||||
|
try:
|
||||||
|
tbs = workbench.listToolbars()
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
# careful, tbs contains all the toolbars of the workbench, including shared toolbars
|
||||||
|
for tb in tbs:
|
||||||
|
all_tbs.add(tb)
|
||||||
|
|
||||||
|
for toolbar_name in all_tbs:
|
||||||
|
for the_toolbar in mw.findChildren(QtGui.QToolBar, toolbar_name):
|
||||||
|
#header = QtGui.QPushButton(toolbar_name)
|
||||||
|
#lay.addWidget(header)
|
||||||
|
|
||||||
|
#sim.insertRow(sim.rowCount())
|
||||||
|
#sim.setData(sim.index(sim.rowCount() - 1, 0), toolbar_name)
|
||||||
|
sim.appendRow(QtGui.QStandardItem(toolbar_name))
|
||||||
|
|
||||||
|
for bt in the_toolbar.findChildren(QtGui.QToolButton):
|
||||||
|
text = bt.text()
|
||||||
|
if text != '':
|
||||||
|
print(text)
|
||||||
|
# TODO: there also is the tooltip
|
||||||
|
icon = bt.icon()
|
||||||
|
|
||||||
|
# To preview the icon, assign it as the icon of a dummy button.
|
||||||
|
#but3 = QtGui.QPushButton(text)
|
||||||
|
#but3.setIcon(icon)
|
||||||
|
#lay.addWidget(but3)
|
||||||
|
|
||||||
|
#slm.insertRow(slm.rowCount())
|
||||||
|
#slm.setData(slm.index(slm.rowCount() - 1, 0), icon)
|
||||||
|
#slm.setData(slm.index(slm.rowCount() - 1, 1), text)
|
||||||
|
sim.appendRow(QtGui.QStandardItem(icon, text))
|
||||||
|
|
||||||
|
#mwx = QtGui.QMainWindow()
|
||||||
|
#mwx.show()
|
||||||
|
#mdi.addSubWindow(mwx)
|
||||||
|
#mdi.setWindowIcon(icon) # probably sets the default icon to use for windows without an icon?
|
||||||
|
mwx.setWindowIcon(icon) # untested
|
||||||
|
mwx.show()
|
||||||
|
|
||||||
|
# for wbname, workbench in Gui.listWorkbenches().items():
|
||||||
|
# try:
|
||||||
|
# tbs = workbench.listToolbars()
|
||||||
|
# # careful, tbs contains all the toolbars of the workbench, including shared toolbars
|
||||||
|
# for tb in mw.findChildren(QtGui.QToolBar, 'XternalApplications'):
|
||||||
|
# for bt in tb.findChildren(QtGui.QToolButton):
|
||||||
|
# text = bt.text()
|
||||||
|
# if text != '':
|
||||||
|
# # TODO: there also is the tooltip
|
||||||
|
# icon = bt.icon()
|
||||||
|
|
||||||
|
# # To preview the icon, assign it as the icon of a dummy window.
|
||||||
|
# mdi.setWindowIcon(icon) # probably sets the default icon to use for windows without an icon?
|
||||||
|
# mwx.setWindowIcon(icon) # untested
|
||||||
|
# except:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from PySide import QtGui
|
||||||
|
qwd = QtGui.QWidget()
|
||||||
|
but1 = QtGui.QPushButton("hi")
|
||||||
|
but2 = QtGui.QPushButton("hello")
|
||||||
|
lay = QtGui.QGridLayout(qwd)
|
||||||
|
lay.addWidget(but1)
|
||||||
|
lay.addWidget(but2)
|
||||||
|
mwx = QtGui.QMainWindow()
|
||||||
|
mwx.setCentralWidget(qwd)
|
||||||
|
mw = Gui.getMainWindow()
|
||||||
|
mdi = mw.findChild(QtGui.QMdiArea)
|
||||||
|
mdi.addSubWindow(mwx)
|
||||||
|
mwx.show()
|
||||||
|
but3 = QtGui.QPushButton("XXX")
|
||||||
|
lay.addWidget(but3)
|
|
@ -30,7 +30,7 @@ class Tool():
|
||||||
return Tool(appName=appName,
|
return Tool(appName=appName,
|
||||||
toolName = getSingletonFromXML(xml, './XternalApps:name').text,
|
toolName = getSingletonFromXML(xml, './XternalApps:name').text,
|
||||||
xForms = xForms,
|
xForms = xForms,
|
||||||
toolTip = getSingletonFromXML(xml, './XternalApps:tooltip').text,
|
toolTip = getSingletonFromXML(xml, './XternalApps:tooltip').text or '',
|
||||||
icon = os.path.dirname(__file__) + '/icons/' + appName + '/' + getSingletonFromXML(xml, './XternalApps:icon').text,
|
icon = os.path.dirname(__file__) + '/icons/' + appName + '/' + getSingletonFromXML(xml, './XternalApps:icon').text,
|
||||||
extendedDescription = getSingletonFromXML(xml, './XternalApps:extended-description').text,
|
extendedDescription = getSingletonFromXML(xml, './XternalApps:extended-description').text,
|
||||||
openHelpFile = None)
|
openHelpFile = None)
|
||||||
|
|
|
@ -12,12 +12,24 @@ import Part
|
||||||
|
|
||||||
parser = etree.XMLParser(resolve_entities=False)
|
parser = etree.XMLParser(resolve_entities=False)
|
||||||
|
|
||||||
FreeCADType = namedtuple('FreeCADType', ['type', 'defaultForType', 'maybeEnumValues', 'maybeMIMEType'])
|
FreeCADType = namedtuple('FreeCADType', ['type', 'defaultForType', 'maybeEnumValues', 'maybeMIMEType', 'fromString'])
|
||||||
|
|
||||||
XFormsInput = namedtuple('XFormsInput', ['modelElementPath', 'label', 'simpleName', 'maybeEnum', 'groupName', 'relevance']) #'type', 'input', 'InputValueToModelValue', 'ModelValueToInputValue'
|
XFormsInput = namedtuple('XFormsInput', ['modelElementPath', 'label', 'simpleName', 'maybeEnum', 'groupName', 'relevance']) #'type', 'input', 'InputValueToModelValue', 'ModelValueToInputValue'
|
||||||
XFormsEnum = namedtuple('XFormsEnum', ['labels', 'values'])
|
XFormsEnum = namedtuple('XFormsEnum', ['labels', 'values'])
|
||||||
InterpretedXML = namedtuple('InterpretedXML', ['xml', 'types', 'inputs']) # Parsed XML, dictionary(modelElementPath) -> type, dictionary(formElementPath) -> XFormsInput
|
InterpretedXML = namedtuple('InterpretedXML', ['xml', 'types', 'inputs']) # Parsed XML, dictionary(modelElementPath) -> type, dictionary(formElementPath) -> XFormsInput
|
||||||
|
|
||||||
|
# Safe printing of unknown strings
|
||||||
|
# This does not aim to have an exact representation of the string, just enough to display in error messages
|
||||||
|
def safeErr(s):
|
||||||
|
s = str(s)
|
||||||
|
result = ''
|
||||||
|
for c in s:
|
||||||
|
if c in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :_-':
|
||||||
|
result += c
|
||||||
|
else:
|
||||||
|
result += ('\\u%04x' % + ord(c))
|
||||||
|
return result
|
||||||
|
|
||||||
def CreateCommand(appName, toolName):
|
def CreateCommand(appName, toolName):
|
||||||
App.ActiveDocument.openTransaction('Create parametric %s from %s'%(toolName, appName))
|
App.ActiveDocument.openTransaction('Create parametric %s from %s'%(toolName, appName))
|
||||||
FreeCADGui.addModule("XternalAppsParametricTool")
|
FreeCADGui.addModule("XternalAppsParametricTool")
|
||||||
|
@ -27,15 +39,17 @@ def CreateCommand(appName, toolName):
|
||||||
def create(appName, toolName):
|
def create(appName, toolName):
|
||||||
sel = FreeCADGui.Selection.getSelection()
|
sel = FreeCADGui.Selection.getSelection()
|
||||||
name = appName + toolName
|
name = appName + toolName
|
||||||
obj = App.ActiveDocument.addObject("App::DocumentObjectGroupPython", name)
|
#obj = App.ActiveDocument.addObject("App::DocumentObjectGroupPython", name)
|
||||||
|
obj = App.ActiveDocument.addObject("Part::FeaturePython", name)
|
||||||
XternalAppsParametricTool(obj, appName, toolName, sel)
|
XternalAppsParametricTool(obj, appName, toolName, sel)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
# TODO: read-only/immutable
|
# TODO: read-only/immutable
|
||||||
typeToFreeCADTypeDict = {
|
typeToFreeCADTypeDict = {
|
||||||
# TODO:do an XML namespace lookup instead of comparing a constant.
|
# TODO:do an XML namespace lookup instead of comparing a constant.
|
||||||
'xsd:decimal': FreeCADType(type='App::PropertyFloat', defaultForType=0.0, maybeEnumValues=None, maybeMIMEType=None),
|
'xsd:decimal': FreeCADType(type='App::PropertyFloat', defaultForType=0.0, maybeEnumValues=None, maybeMIMEType=None, fromString=float),
|
||||||
'xsd:string': FreeCADType(type='App::PropertyString', defaultForType='', maybeEnumValues=None, maybeMIMEType=None),
|
'xsd:string': FreeCADType(type='App::PropertyString', defaultForType='', maybeEnumValues=None, maybeMIMEType=None, fromString=lambda x: x),
|
||||||
|
'xsd:integer': FreeCADType(type='App::PropertyInteger', defaultForType=0, maybeEnumValues=None, maybeMIMEType=None, fromString=int),
|
||||||
}
|
}
|
||||||
|
|
||||||
def getShortPath(root, elem, root_needs_dot = True):
|
def getShortPath(root, elem, root_needs_dot = True):
|
||||||
|
@ -69,7 +83,7 @@ def typeToFreeCADType(type, namespacesAtTypeElement, maybeSchema):
|
||||||
schemaType = schemaTypes[0];
|
schemaType = schemaTypes[0];
|
||||||
return schemaTypeToFreeCADType(schemaType)
|
return schemaTypeToFreeCADType(schemaType)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unsupported XForms type')
|
raise ValueError('Unsupported XForms type ' + safeErr(type))
|
||||||
|
|
||||||
def schemaTypeToFreeCADType(schemaType):
|
def schemaTypeToFreeCADType(schemaType):
|
||||||
if schemaType.tag == "{http://www.w3.org/2001/XMLSchema}simpleType":
|
if schemaType.tag == "{http://www.w3.org/2001/XMLSchema}simpleType":
|
||||||
|
@ -82,13 +96,13 @@ def schemaTypeToFreeCADType(schemaType):
|
||||||
raise ValueError('only restrictions of xsd:string (a.k.a. enums) are supported')
|
raise ValueError('only restrictions of xsd:string (a.k.a. enums) are supported')
|
||||||
enumCases = restriction.findall('./xsd:enumeration', ns)
|
enumCases = restriction.findall('./xsd:enumeration', ns)
|
||||||
enumValues = [enumCase.attrib['value'] for enumCase in enumCases]
|
enumValues = [enumCase.attrib['value'] for enumCase in enumCases]
|
||||||
return FreeCADType(type = 'App::PropertyEnumeration', defaultForType = (enumValues[0] if len(enumValues) > 0 else None), maybeEnumValues = enumValues, maybeMIMEType=None)
|
return FreeCADType(type = 'App::PropertyEnumeration', defaultForType = (enumValues[0] if len(enumValues) > 0 else None), maybeEnumValues = enumValues, maybeMIMEType=None, fromString=lambda x: x)
|
||||||
elif schemaType.tag == "{http://www.w3.org/2001/XMLSchema}complexType":
|
elif schemaType.tag == "{http://www.w3.org/2001/XMLSchema}complexType":
|
||||||
return ValueError("Complex XML chema types are not supported")
|
return ValueError("Complex XML chema types are not supported")
|
||||||
|
|
||||||
def MIMETypeToFreeCADType(MIMEType):
|
def MIMETypeToFreeCADType(MIMEType):
|
||||||
if MIMEType == 'image/svg+xml':
|
if MIMEType == 'image/svg+xml':
|
||||||
return FreeCADType(type='App::PropertyLink', defaultForType=None, maybeEnumValues=None, maybeMIMEType = MIMEType)
|
return FreeCADType(type='App::PropertyLink', defaultForType=None, maybeEnumValues=None, maybeMIMEType = MIMEType, fromString=lambda x: x)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unsupported MIME type')
|
raise ValueError('Unsupported MIME type')
|
||||||
|
|
||||||
|
@ -129,28 +143,134 @@ def exportSVG(obj, svgfile):
|
||||||
|
|
||||||
# TODO: modify the SVG to set a fake Inkscape version, to avoid the pop-up dialog.
|
# TODO: modify the SVG to set a fake Inkscape version, to avoid the pop-up dialog.
|
||||||
|
|
||||||
|
class XternalAppsParametricToolViewProvider():
|
||||||
|
def __init__(self, vobj):
|
||||||
|
"""
|
||||||
|
Set this object to the proxy object of the actual view provider
|
||||||
|
"""
|
||||||
|
|
||||||
|
vobj.Proxy = self
|
||||||
|
|
||||||
|
def attach(self, vobj):
|
||||||
|
"""
|
||||||
|
Setup the scene sub-graph of the view provider, this method is mandatory
|
||||||
|
"""
|
||||||
|
self.ViewObject = vobj
|
||||||
|
self.Object = vobj.Object
|
||||||
|
|
||||||
|
def updateData(self, fp, prop):
|
||||||
|
"""
|
||||||
|
If a property of the handled feature has changed we have the chance to handle this here
|
||||||
|
"""
|
||||||
|
print('VVVVVVVVVVVVVVVVVV', repr(fp), repr(prop))
|
||||||
|
return
|
||||||
|
|
||||||
|
def getDisplayModes(self,vobj):
|
||||||
|
"""
|
||||||
|
Return a list of display modes.
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
def getDefaultDisplayMode(self):
|
||||||
|
"""
|
||||||
|
Return the name of the default display mode. It must be defined in getDisplayModes.
|
||||||
|
"""
|
||||||
|
return "Shaded"
|
||||||
|
|
||||||
|
def setDisplayMode(self,mode):
|
||||||
|
"""
|
||||||
|
Map the display mode defined in attach with those defined in getDisplayModes.
|
||||||
|
Since they have the same names nothing needs to be done.
|
||||||
|
This method is optional.
|
||||||
|
"""
|
||||||
|
return mode
|
||||||
|
|
||||||
|
def onChanged(self, vp, prop):
|
||||||
|
"""
|
||||||
|
Print the name of the property that has changed
|
||||||
|
"""
|
||||||
|
App.Console.PrintMessage("Change property: " + str(prop) + "\n")
|
||||||
|
|
||||||
|
def getIcon(self):
|
||||||
|
"""
|
||||||
|
Return the icon in XMP format which will appear in the tree view. This method is optional and if not defined a default icon is shown.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return """
|
||||||
|
/* XPM */
|
||||||
|
static const char * ViewProviderBox_xpm[] = {
|
||||||
|
"16 16 6 1",
|
||||||
|
" c None",
|
||||||
|
". c #141010",
|
||||||
|
"+ c #615BD2",
|
||||||
|
"@ c #C39D55",
|
||||||
|
"# c #000000",
|
||||||
|
"$ c #57C355",
|
||||||
|
" ........",
|
||||||
|
" ......++..+..",
|
||||||
|
" .@@@@.++..++.",
|
||||||
|
" .@@@@.++..++.",
|
||||||
|
" .@@ .++++++.",
|
||||||
|
" ..@@ .++..++.",
|
||||||
|
"###@@@@ .++..++.",
|
||||||
|
"##$.@@$#.++++++.",
|
||||||
|
"#$#$.$$$........",
|
||||||
|
"#$$####### ",
|
||||||
|
"#$$#$$$$$# ",
|
||||||
|
"#$$#$$$$$# ",
|
||||||
|
"#$$#$$$$$# ",
|
||||||
|
" #$#$$$$$# ",
|
||||||
|
" ##$$$$$# ",
|
||||||
|
" ####### "};
|
||||||
|
"""
|
||||||
|
|
||||||
|
def claimChildren(self):
|
||||||
|
return self.Object.Proxy.getChildren(self.Object)
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
"""
|
||||||
|
Called during document saving.
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __setstate__(self,state):
|
||||||
|
"""
|
||||||
|
Called during document restore.
|
||||||
|
"""
|
||||||
|
print('AAAAAAAAAAAAAAAAAAAYYYYYYYYYYYYYYYYYYYYYYYYY', repr(self), repr(state))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def onDocumentRestored(self, obj):
|
||||||
|
print('AAAAAAAAAAAAAAAAAAAXXXXXXXXXXXXXXXXXXXXXXXXX', repr(self), repr(obj))
|
||||||
|
|
||||||
class XternalAppsParametricTool():
|
class XternalAppsParametricTool():
|
||||||
def __init__(self, obj, appName, toolName, sel=[]):
|
def init1(self, appName, toolName):
|
||||||
self.Type = "XternalAppsParametricTool"
|
self.Type = "XternalAppsParametricTool"
|
||||||
self.AppName = appName
|
self.AppName = appName
|
||||||
self.ToolName = toolName
|
self.ToolName = toolName
|
||||||
self.MonitorChanges = False;
|
self.MonitorChanges = False
|
||||||
|
|
||||||
|
def init2(self, obj):
|
||||||
|
self.Object = obj
|
||||||
obj.Proxy = self
|
obj.Proxy = self
|
||||||
|
|
||||||
self.types = self.xmlTypesToPython(self.Tool.XForms)
|
self.types = self.xmlTypesToPython(self.Tool.XForms)
|
||||||
self.defaults = self.xmlDefaultsToPython(self.Tool.XForms, self.types)
|
self.defaults = self.xmlDefaultsToPython(self.Tool.XForms, self.types)
|
||||||
self.form = self.xmlFormToPython(self.Tool.XForms, self.types)
|
self.form = self.xmlFormToPython(self.Tool.XForms, self.types)
|
||||||
|
|
||||||
|
# TODO: on restore, reload the model instance from obj (or recompute it on the fly)
|
||||||
self.ModelInstance = self.defaults
|
self.ModelInstance = self.defaults
|
||||||
self.createProperties(obj, self.types, self.ModelInstance, self.form)
|
|
||||||
|
|
||||||
self.oldExpressionEngine = obj.ExpressionEngine
|
self.oldExpressionEngine = obj.ExpressionEngine
|
||||||
|
|
||||||
|
def init3(self, obj):
|
||||||
self.MonitorChanges = True
|
self.MonitorChanges = True
|
||||||
|
|
||||||
#pprint.pprint(self.types)
|
def __init__(self, obj, appName, toolName, sel=[]):
|
||||||
#pprint.pprint(self.form)
|
self.init1(appName, toolName)
|
||||||
|
self.init2(obj)
|
||||||
|
self.createProperties(obj, self.types, self.ModelInstance, self.form)
|
||||||
|
self.init3(obj)
|
||||||
|
|
||||||
# Special treatment for the "primary" form field
|
# Special treatment for the "primary" form field
|
||||||
primary = [input for input in self.form.values() if input.relevance == 'primary']
|
primary = [input for input in self.form.values() if input.relevance == 'primary']
|
||||||
|
@ -160,30 +280,62 @@ class XternalAppsParametricTool():
|
||||||
|
|
||||||
# Display the contents of the primary form element as children in the tree view
|
# Display the contents of the primary form element as children in the tree view
|
||||||
if type in ['App::PropertyLink', 'App::PropertyLinkList']:
|
if type in ['App::PropertyLink', 'App::PropertyLinkList']:
|
||||||
self.form['FreeCADGroup'] = XFormsInput(modelElementPath=primary.modelElementPath, label='Group', simpleName='Group', maybeEnum=primary.maybeEnum, groupName='Base', relevance='primary')
|
#self.form['FreeCADGroup'] = XFormsInput(modelElementPath=primary.modelElementPath, label='Group', simpleName='Group', maybeEnum=primary.maybeEnum, groupName='Base', relevance='primary')
|
||||||
|
pass
|
||||||
|
|
||||||
if type == 'App::PropertyLink' and len(sel) >= 1:
|
if type == 'App::PropertyLink' and len(sel) >= 1:
|
||||||
setattr(obj, primary.simpleName, sel[0])
|
setattr(obj, primary.simpleName, sel[0])
|
||||||
elif type == 'App::PropertyLinkList':
|
elif type == 'App::PropertyLinkList':
|
||||||
setattr(obj, primary.simpleName, sel)
|
setattr(obj, primary.simpleName, sel)
|
||||||
|
|
||||||
return
|
XternalAppsParametricToolViewProvider(obj.ViewObject)
|
||||||
|
self.execute(obj)
|
||||||
|
|
||||||
|
def getChildren(self, obj):
|
||||||
|
primary = [input for input in self.form.values() if input.relevance == 'primary']
|
||||||
|
if len(primary) == 1:
|
||||||
|
primary = primary[0]
|
||||||
|
type = self.types[primary.modelElementPath].type
|
||||||
|
if type == 'App::PropertyLink':
|
||||||
|
return [getattr(obj, primary.simpleName)]
|
||||||
|
elif type == 'App::PropertyLinkList':
|
||||||
|
return getattr(obj, primary.simpleName)
|
||||||
|
return []
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
|
return { "AppName": self.AppName, "ToolName": self.ToolName }
|
||||||
|
|
||||||
|
print('XXX_GET_STATE')
|
||||||
|
print('self', repr(self))
|
||||||
copied = self.__dict__.copy()
|
copied = self.__dict__.copy()
|
||||||
copied['ModelInstance'] = list(copied['ModelInstance'].items())
|
copied['ModelInstance'] = list(copied['ModelInstance'].items())
|
||||||
del copied['types']
|
del copied['types']
|
||||||
del copied['defaults']
|
del copied['defaults']
|
||||||
del copied['form']
|
del copied['form']
|
||||||
|
print('copied', repr(copied))
|
||||||
return copied
|
return copied
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
|
print('XXX_SET_STATE')
|
||||||
|
print(repr(self), repr(state))
|
||||||
if state:
|
if state:
|
||||||
state['ModelInstance'] = dict(state['ModelInstance'])
|
# TODO: always sanitize data that is restored, for security reasons.
|
||||||
self.__dict__ = state
|
#self.AppName = str(state['AppName'])
|
||||||
self.types = self.xmlTypesToPython(self.Tool.XForms)
|
#self.ToolName = str(state['ToolName'])
|
||||||
self.defaults = self.xmlDefaultsToPython(self.Tool.XForms, self.types)
|
#self.__init__(obj, self.AppName, self.ToolName, sel=[])
|
||||||
self.form = self.xmlFormToPython(self.Tool.XForms, self.types)
|
self.init1(str(state['AppName']), str(state['ToolName']))
|
||||||
|
#state['ModelInstance'] = dict(state['ModelInstance'])
|
||||||
|
#self.__dict__ = state
|
||||||
|
#self.types = self.xmlTypesToPython(self.Tool.XForms)
|
||||||
|
#self.defaults = self.xmlDefaultsToPython(self.Tool.XForms, self.types)
|
||||||
|
#self.form = self.xmlFormToPython(self.Tool.XForms, self.types)
|
||||||
|
|
||||||
|
def onDocumentRestored(self, obj):
|
||||||
|
print('XXX_ON_DOCUMENT_RESTORED')
|
||||||
|
self.init2(obj)
|
||||||
|
self.reloadProperties(obj, self.form)
|
||||||
|
self.init3(obj)
|
||||||
|
#self.__init__(obj, self.AppName, self.ToolName, sel=[])
|
||||||
|
|
||||||
def onChanged(self, obj, prop):
|
def onChanged(self, obj, prop):
|
||||||
import sys
|
import sys
|
||||||
|
@ -215,32 +367,38 @@ class XternalAppsParametricTool():
|
||||||
obj.setExpression(other.simpleName, None)
|
obj.setExpression(other.simpleName, None)
|
||||||
self.oldExpressionEngine = set([k for k, v in obj.ExpressionEngine])
|
self.oldExpressionEngine = set([k for k, v in obj.ExpressionEngine])
|
||||||
|
|
||||||
_, input = lookup(self.form, lambda input: input.simpleName, prop)
|
#######################################
|
||||||
if input:
|
self.setModelFromInput(obj, prop)
|
||||||
newModelValue = getattr(obj, prop)
|
#######################################
|
||||||
if input.maybeEnum:
|
|
||||||
newModelValue = input.maybeEnum[newModelValue]
|
|
||||||
# The Group property always contains a list, but we may use it to represent a link to a single element.
|
|
||||||
if prop == 'Group' and self.types[input.modelElementPath].type == 'App::PropertyLink':
|
|
||||||
if len(newModelValue) > 0:
|
|
||||||
newModelValue = newModelValue[0]
|
|
||||||
else:
|
|
||||||
newModelValue = None
|
|
||||||
self.ModelInstance[input.modelElementPath] = newModelValue
|
|
||||||
for other in self.form.values():
|
|
||||||
if other.modelElementPath == input.modelElementPath and other.simpleName != input.simpleName:
|
|
||||||
newFormValue = newModelValue
|
|
||||||
#print('newModelValue', newModelValue)
|
|
||||||
if other.maybeEnum:
|
|
||||||
newFormValue = [f for f, m in other.maybeEnum.items() if m == newModelValue][0]
|
|
||||||
#print(prop, newFormValue, other.simpleName, dict(obj.ExpressionEngine).get(prop), dict(obj.ExpressionEngine).get(other.simpleName))
|
|
||||||
#obj.setExpression(other.simpleName, dict(obj.ExpressionEngine).get(prop))
|
|
||||||
#print(obj, other.simpleName, newFormValue)
|
|
||||||
setattr(obj, other.simpleName, newFormValue)
|
|
||||||
finally:
|
finally:
|
||||||
#print('MonitorChanges = ' + str(restoreMonitorChanges))
|
#print('MonitorChanges = ' + str(restoreMonitorChanges))
|
||||||
self.MonitorChanges = restoreMonitorChanges
|
self.MonitorChanges = restoreMonitorChanges
|
||||||
|
|
||||||
|
def setModelFromInput(self, obj, prop):
|
||||||
|
_, input = lookup(self.form, lambda input: input.simpleName, prop)
|
||||||
|
if input:
|
||||||
|
newModelValue = getattr(obj, prop)
|
||||||
|
if input.maybeEnum:
|
||||||
|
newModelValue = input.maybeEnum[newModelValue]
|
||||||
|
# The Group property always contains a list, but we may use it to represent a link to a single element.
|
||||||
|
if prop == 'Group' and self.types[input.modelElementPath].type == 'App::PropertyLink':
|
||||||
|
if len(newModelValue) > 0:
|
||||||
|
newModelValue = newModelValue[0]
|
||||||
|
else:
|
||||||
|
newModelValue = None
|
||||||
|
print(self.ModelInstance, input.modelElementPath, newModelValue)
|
||||||
|
self.ModelInstance[input.modelElementPath] = newModelValue
|
||||||
|
for other in self.form.values():
|
||||||
|
if other.modelElementPath == input.modelElementPath and other.simpleName != input.simpleName:
|
||||||
|
newFormValue = newModelValue
|
||||||
|
#print('newModelValue', newModelValue)
|
||||||
|
if other.maybeEnum:
|
||||||
|
newFormValue = [f for f, m in other.maybeEnum.items() if m == newModelValue][0]
|
||||||
|
#print(prop, newFormValue, other.simpleName, dict(obj.ExpressionEngine).get(prop), dict(obj.ExpressionEngine).get(other.simpleName))
|
||||||
|
#obj.setExpression(other.simpleName, dict(obj.ExpressionEngine).get(prop))
|
||||||
|
#print(obj, other.simpleName, newFormValue)
|
||||||
|
setattr(obj, other.simpleName, newFormValue)
|
||||||
|
|
||||||
def interpretFormElement(self, xmlXFormsElement, xml, instanceDocument, types):
|
def interpretFormElement(self, xmlXFormsElement, xml, instanceDocument, types):
|
||||||
# TODO: is it safe to pass input unprotected here?
|
# TODO: is it safe to pass input unprotected here?
|
||||||
modelElement = instanceDocument.find(xmlXFormsElement.attrib['ref'],
|
modelElement = instanceDocument.find(xmlXFormsElement.attrib['ref'],
|
||||||
|
@ -348,7 +506,7 @@ class XternalAppsParametricTool():
|
||||||
default = modelElement.text
|
default = modelElement.text
|
||||||
if default is None:
|
if default is None:
|
||||||
default = types[path].defaultForType
|
default = types[path].defaultForType
|
||||||
defaults[path] = default
|
defaults[path] = types[path].fromString(default)
|
||||||
return defaults
|
return defaults
|
||||||
|
|
||||||
def createProperties(self, obj, types, defaults, form):
|
def createProperties(self, obj, types, defaults, form):
|
||||||
|
@ -362,155 +520,264 @@ class XternalAppsParametricTool():
|
||||||
setattr(obj, simpleName, list(maybeEnum.keys()))
|
setattr(obj, simpleName, list(maybeEnum.keys()))
|
||||||
# TODO: use a bidirectional dict
|
# TODO: use a bidirectional dict
|
||||||
default = [k for k, v in maybeEnum.items() if v == default][0]
|
default = [k for k, v in maybeEnum.items() if v == default][0]
|
||||||
setattr(obj, simpleName, default)
|
try:
|
||||||
|
setattr(obj, simpleName, default)
|
||||||
|
except:
|
||||||
|
raise ValueError('Could not set ' + safeErr(obj) + "." + safeErr(simpleName) + " = " + safeErr(repr(default)))
|
||||||
|
|
||||||
|
def reloadProperties(self, obj, form):
|
||||||
|
for key, (modelElementPath, label, simpleName, maybeEnum, groupName, relevance) in form.items():
|
||||||
|
self.setModelFromInput(obj, simpleName)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def Tool(self):
|
def Tool(self):
|
||||||
return XternalAppsList.apps[self.AppName].Tools[self.ToolName]
|
return XternalAppsList.apps[self.AppName].Tools[self.ToolName]
|
||||||
|
|
||||||
def xmlCommandToPython(self, document):
|
def xmlCommandToPython(self, obj, document):
|
||||||
"""Parse the .xml document, and return
|
"""Parse the .xml document, and return
|
||||||
a pair of dictionaries accepts[model_path] = style and returns[model_path] = style."""
|
a pair of dictionaries accepts[model_path] = style and returns[model_path] = style."""
|
||||||
|
|
||||||
|
print('A')
|
||||||
|
|
||||||
xml = etree.parse(self.Tool.XForms, parser=parser)
|
xml = etree.parse(self.Tool.XForms, parser=parser)
|
||||||
|
|
||||||
model_root = xml.find('./xforms:model', ns)
|
model_root = xml.find('./xforms:model', ns)
|
||||||
instanceDocument = etree.ElementTree(model_root.find('./xforms:instance/*', ns))
|
instanceDocument = etree.ElementTree(model_root.find('./xforms:instance/*', ns))
|
||||||
|
|
||||||
|
print('B')
|
||||||
command = xml.find('./XternalApps:command', ns)
|
command = xml.find('./XternalApps:command', ns)
|
||||||
method = command.attrib['method']
|
method = command.attrib['method']
|
||||||
commandName = command.attrib['name']
|
commandName = command.attrib['name']
|
||||||
|
|
||||||
accepts = command.find('./XternalApps:accepts', ns)
|
print('C')
|
||||||
returns = command.find('./XternalApps:returns', ns)
|
# Step 1: get the list of all fields
|
||||||
|
|
||||||
maybeDefault = accepts.findall('./XternalApps:default', ns)
|
|
||||||
if len(maybeDefault) == 1:
|
|
||||||
default_style = maybeDefault[0].attrib['style']
|
|
||||||
elif len(maybeDefault) > 1:
|
|
||||||
raise ValueError('The accepts tag should contain at most one default tag')
|
|
||||||
else:
|
|
||||||
default_style = None
|
|
||||||
|
|
||||||
styles = {}
|
|
||||||
optionNames = {}
|
optionNames = {}
|
||||||
# Put all the model fields in accepts[path]
|
|
||||||
for modelElement in instanceDocument.findall('//*', ns):
|
for modelElement in instanceDocument.findall('//*', ns):
|
||||||
path = instanceDocument.getelementpath(modelElement)
|
# Put all the model fields in optionNames[path] = optionName
|
||||||
styles[path] = default_style
|
modelElementPath = instanceDocument.getelementpath(modelElement)
|
||||||
optionNames[path] = etree.QName(modelElement).localname
|
optionNames[modelElementPath] = etree.QName(modelElement).localname
|
||||||
|
|
||||||
style_is_default = {k: True for k in styles.keys()}
|
print('D')
|
||||||
for exception in accepts.findall('./XternalApps:exception', ns):
|
|
||||||
ref = exception.attrib['ref']
|
|
||||||
style = exception.attrib['style']
|
|
||||||
for modelElement in instanceDocument.findall(ref, exception.nsmap):
|
|
||||||
path = instanceDocument.getelementpath(modelElement)
|
|
||||||
if style_is_default[path]:
|
|
||||||
style_is_default[path] = False
|
|
||||||
styles[path] = style
|
|
||||||
else:
|
|
||||||
ValueError('overlapping exceptions in command/accepts/exception')
|
|
||||||
|
|
||||||
pipe_in = None
|
|
||||||
positionalArguments = {}
|
|
||||||
namedArguments = []
|
|
||||||
tempfiles = []
|
tempfiles = []
|
||||||
tempdirs = []
|
tempdirs = []
|
||||||
for modelElementPath, value in self.ModelInstance.items():
|
try:
|
||||||
style = styles[modelElementPath]
|
commandLine = [commandName]
|
||||||
type = self.types[modelElementPath]
|
default = None
|
||||||
|
pipeIn = None
|
||||||
|
# Step 2: generate most of the command-line, leaving a placeholder for the fields that use the default behaviour
|
||||||
|
print('E')
|
||||||
|
def formatTemplateElement(isInput, style, key, type, value, tempdirs, tempfiles):
|
||||||
|
print('K3X')
|
||||||
|
# Convert to the type expected by the tool
|
||||||
|
if type.type == 'App::PropertyLink' and type.maybeMIMEType == 'image/svg+xml':
|
||||||
|
print('K31')
|
||||||
|
import tempfile, os
|
||||||
|
d = tempfile.mkdtemp()
|
||||||
|
tempdirs += [d]
|
||||||
|
svgfile = os.path.join(d, "sketch.svg")
|
||||||
|
tempfiles += [svgfile]
|
||||||
|
print("exportSVG", repr(value), repr(svgfile))
|
||||||
|
exportSVG(value, svgfile)
|
||||||
|
value = svgfile
|
||||||
|
else:
|
||||||
|
print('K32')
|
||||||
|
# TODO ################# convert the value from FreeCAD to what the program supports ###################
|
||||||
|
value = str(value)
|
||||||
|
|
||||||
# Convert to the type expected by the tool
|
if style == 'value':
|
||||||
if type.type == 'App::PropertyLink' and type.maybeMIMEType == 'image/svg+xml':
|
if isInput:
|
||||||
import tempfile, os
|
return [value]
|
||||||
d = tempfile.mkdtemp()
|
else:
|
||||||
svgfile = os.path.join(d, "sketch.svg")
|
pass # TODO: e.g. ['temporary_output_file']
|
||||||
tempfiles += [svgfile]
|
elif style == 'double-dash':
|
||||||
tempdirs += [d]
|
if isInput:
|
||||||
exportSVG(value, svgfile)
|
return ['--' + key, value]
|
||||||
value = svgfile
|
else:
|
||||||
else:
|
pass # TODO: e.g. ['-o', 'temporary_output_file']
|
||||||
# TODO ################# convert the value from FreeCAD to what the program supports ###################
|
elif style == 'pipe':
|
||||||
value = str(value)
|
if isInput:
|
||||||
|
if pipeIn is not None:
|
||||||
|
raise ValueError('Only one parameter can be passed as a pipe')
|
||||||
|
pipeIn = value
|
||||||
|
else:
|
||||||
|
pass # TODO: output
|
||||||
|
return []
|
||||||
|
elif style == 'exitcode':
|
||||||
|
if isInput:
|
||||||
|
raise ValueError('the exitcode style can only be used for the output direction')
|
||||||
|
else:
|
||||||
|
pass # TODO: output
|
||||||
|
else:
|
||||||
|
raise ValueError('Unsupported argument-passing or value-returning style')
|
||||||
|
print('F')
|
||||||
|
for templateElement in command.findall('./*', ns):
|
||||||
|
print('G')
|
||||||
|
direction = templateElement.attrib['direction']
|
||||||
|
style = templateElement.attrib['style']
|
||||||
|
tag = templateElement.tag
|
||||||
|
|
||||||
if style == 'double-dash':
|
print('H')
|
||||||
namedArguments += ['--' + optionNames[modelElementPath], value]
|
tagPrefix = '{'+ns['XternalApps']+'}'
|
||||||
elif style == 'pipe':
|
if not tag.startswith(tagPrefix):
|
||||||
if pipe_in != None:
|
continue
|
||||||
raise ValueError('more then one option uses a "pipe" style')
|
tag = tag[len(tagPrefix):]
|
||||||
pipe_in = value
|
|
||||||
elif style == 'positional':
|
|
||||||
pos = unknown_todo() ######################################### TODO ############################
|
|
||||||
positionalArguments[pos] = value
|
|
||||||
elif style == 'exitcode':
|
|
||||||
raise ValueError('exitcode is supported only for the output of the command, not for its input')
|
|
||||||
else:
|
|
||||||
raise ValueError('unsupported argument-passing style')
|
|
||||||
|
|
||||||
positionalArguments = [v for i, vs in sorted(positionalArguments.items()) for v in vs]
|
print('I')
|
||||||
if pipe_in is not None:
|
if direction == 'input':
|
||||||
pipe_in_handle = open(pipe_in)
|
isInput = True
|
||||||
|
elif direction == 'output':
|
||||||
|
isInput = False
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid value for direction attribute')
|
||||||
|
|
||||||
# TODO: use the XML for this
|
print('J')
|
||||||
d = tempfile.mkdtemp()
|
if tag == 'constant':
|
||||||
result_filename = os.path.join(d, "result.svg")
|
print('K1')
|
||||||
pipe_out = result_filename
|
if isInput:
|
||||||
tempfiles += [pipe_out]
|
key = templateElement.attrib.get('key', None)
|
||||||
tempdirs += [d]
|
type = typeToFreeCADTypeDict['xsd:string']
|
||||||
pipe_out_handle = open(pipe_out, 'w')
|
value = templateElement.attrib['value']
|
||||||
|
commandLine += formatTemplateElement(isInput, style, key, type, value, tempdirs, tempfiles)
|
||||||
|
else:
|
||||||
|
raise ValueError('constant elements of a command-line input can only be part of the input, not of the output')
|
||||||
|
elif tag == 'default':
|
||||||
|
print('K2')
|
||||||
|
if isInput:
|
||||||
|
if default is not None:
|
||||||
|
raise ValueError('Only one default tag can be specified for a given direction')
|
||||||
|
default = {'style':style, 'position':len(commandLine)}
|
||||||
|
else:
|
||||||
|
pass # TODO: output
|
||||||
|
elif tag == 'exception':
|
||||||
|
print('K3')
|
||||||
|
ref = templateElement.attrib['ref']
|
||||||
|
found = False
|
||||||
|
if isInput:
|
||||||
|
for modelElement in instanceDocument.findall(ref, templateElement.nsmap):
|
||||||
|
found = True
|
||||||
|
modelElementPath = instanceDocument.getelementpath(modelElement)
|
||||||
|
key = optionNames[modelElementPath]
|
||||||
|
value = self.ModelInstance[modelElementPath]
|
||||||
|
type = self.types[modelElementPath]
|
||||||
|
commandLine += formatTemplateElement(isInput, style, key, type, value, tempdirs, tempfiles)
|
||||||
|
if modelElementPath in optionNames:
|
||||||
|
del(optionNames[modelElementPath])
|
||||||
|
else:
|
||||||
|
raise ValueError('In command-line template, the same field is referenced by two tags (e.g. exception and ignore)')
|
||||||
|
else:
|
||||||
|
found = True
|
||||||
|
# TODO: output
|
||||||
|
pass
|
||||||
|
if not found:
|
||||||
|
raise ValueError('Could not resolve reference in command-line template: ' + safeErr(ref))
|
||||||
|
elif tag == 'ignore':
|
||||||
|
print('K4')
|
||||||
|
ref = templateElement.attrib['ref']
|
||||||
|
found = False
|
||||||
|
if isInput:
|
||||||
|
for modelElement in instanceDocument.findall(ref, templateElement.nsmap):
|
||||||
|
found = True
|
||||||
|
modelElementPath = instanceDocument.getelementpath(modelElement)
|
||||||
|
if modelElementPath in optionNames:
|
||||||
|
del(optionNames[modelElementPath])
|
||||||
|
else:
|
||||||
|
raise ValueError('In command-line template, the same field is referenced by two tags (e.g. exception and ignore)')
|
||||||
|
else:
|
||||||
|
found = True
|
||||||
|
# TODO: output
|
||||||
|
pass
|
||||||
|
if not found:
|
||||||
|
raise ValueError('Could not resolve reference in command-line template')
|
||||||
|
else:
|
||||||
|
print('K5')
|
||||||
|
raise ValueError('Unexpected tag in command-line template:' + safeErr(tag))
|
||||||
|
|
||||||
import subprocess
|
# Step 3: replace the placeholder with the remaining input fields
|
||||||
print([commandName] + positionalArguments + namedArguments + ['stdin=' + pipe_in, 'stdout=' + pipe_out])
|
print('L')
|
||||||
proc = subprocess.Popen([commandName] + positionalArguments + namedArguments, stdin=pipe_in_handle, stdout=pipe_out_handle)
|
commandLineDefault = []
|
||||||
proc.communicate()
|
for modelElementPath, key in optionNames.items():
|
||||||
exitcode = proc.returncode
|
value = self.ModelInstance[modelElementPath]
|
||||||
|
type = self.types[modelElementPath]
|
||||||
|
if default is None:
|
||||||
|
raise ValueError('Some fields are not included in the command-line template, and no default is present. To ignore a field, use the ignore tag.')
|
||||||
|
style = default['style']
|
||||||
|
commandLineDefault += formatTemplateElement(True, style, key, type, value, tempdirs, tempfiles)
|
||||||
|
if default is not None:
|
||||||
|
position = default['position']
|
||||||
|
commandLine[position:position] = commandLineDefault
|
||||||
|
|
||||||
pipe_in_handle.close()
|
# Step 4: call the command
|
||||||
with open(result_filename, 'rb') as result_file:
|
#for modelElementPath, value in self.ModelInstance.items():
|
||||||
result = result_file.read()
|
# style = styles[modelElementPath]
|
||||||
pipe_out_handle.close()
|
# type = self.types[modelElementPath]
|
||||||
|
|
||||||
# Circumvent bug which leaves App.ActiveDocument to an incorrect value after the newDocument + closeDocument
|
pipeInHandle = None
|
||||||
oldActiveDocumentName = App.ActiveDocument.Name
|
if pipeIn is not None:
|
||||||
tempDocument = App.newDocument('load_svg', hidden=True)
|
pipeInHandle = open(pipeIn)
|
||||||
import importSVG
|
|
||||||
importSVG.insert(pipe_out,'load_svg')
|
|
||||||
solids = []
|
|
||||||
for o in tempDocument.Objects:
|
|
||||||
shape = o.Shape
|
|
||||||
wire = Part.Wire(shape.Edges)
|
|
||||||
#face = Part.Face(wire)
|
|
||||||
solids += [wire] #face
|
|
||||||
p = Part.makeCompound(solids)
|
|
||||||
for obj in tempDocument.Objects:
|
|
||||||
print("remove:" + obj.Name)
|
|
||||||
tempDocument.removeObject(obj.Name)
|
|
||||||
Part.show(p)
|
|
||||||
for o in tempDocument.Objects:
|
|
||||||
o2 = document.copyObject(o, False, False)
|
|
||||||
App.closeDocument('load_svg')
|
|
||||||
App.setActiveDocument(oldActiveDocumentName)
|
|
||||||
|
|
||||||
for tempfile in tempfiles:
|
# TODO: use the XML for this
|
||||||
os.remove(tempfile)
|
import tempfile, os
|
||||||
for tempdir in tempdirs:
|
d = tempfile.mkdtemp()
|
||||||
os.rmdir(tempdir)
|
resultFilename = os.path.join(d, "result.svg")
|
||||||
|
pipeOut = resultFilename
|
||||||
|
tempfiles += [pipeOut]
|
||||||
|
tempdirs += [d]
|
||||||
|
pipeOutHandle = open(pipeOut, 'w')
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
#print(commandLine + ['stdin=' + str(pipeIn), 'stdout=' + str(pipeOut)])
|
||||||
|
proc = subprocess.Popen(commandLine, stdin=pipeInHandle, stdout=pipeOutHandle)
|
||||||
|
proc.communicate()
|
||||||
|
exitcode = proc.returncode
|
||||||
|
|
||||||
|
if pipeInHandle is not None:
|
||||||
|
pipeInHandle.close()
|
||||||
|
with open(resultFilename, 'rb') as resultFile:
|
||||||
|
result = resultFile.read()
|
||||||
|
pipeOutHandle.close()
|
||||||
|
|
||||||
|
# Circumvent bug which leaves App.ActiveDocument to an incorrect value after the newDocument + closeDocument
|
||||||
|
oldActiveDocumentName = App.ActiveDocument.Name
|
||||||
|
tempDocument = App.newDocument('load_svg', hidden=True)
|
||||||
|
import importSVG
|
||||||
|
importSVG.insert(pipeOut,'load_svg')
|
||||||
|
solids = []
|
||||||
|
for o in tempDocument.Objects:
|
||||||
|
shape = o.Shape
|
||||||
|
wire = Part.Wire(shape.Edges)
|
||||||
|
#face = Part.Face(wire)
|
||||||
|
solids += [wire] #face
|
||||||
|
p = Part.makeCompound(solids)
|
||||||
|
for o in tempDocument.Objects:
|
||||||
|
print("remove:" + o.Name)
|
||||||
|
tempDocument.removeObject(o.Name)
|
||||||
|
Part.show(p)
|
||||||
|
print("===============================================================")
|
||||||
|
for o in tempDocument.Objects:
|
||||||
|
#o2 = document.copyObject(o, False, False)
|
||||||
|
#print(o2.Name)
|
||||||
|
obj.Shape = o.Shape
|
||||||
|
obj.ViewObject.DisplayMode = o.ViewObject.DisplayMode
|
||||||
|
break
|
||||||
|
#document.removeObject(o2.Name)
|
||||||
|
print("===============================================================!!")
|
||||||
|
App.closeDocument('load_svg')
|
||||||
|
App.setActiveDocument(oldActiveDocumentName)
|
||||||
|
finally:
|
||||||
|
pass
|
||||||
|
#for tempfile in tempfiles:
|
||||||
|
# try:
|
||||||
|
# os.remove(tempfile)
|
||||||
|
# except:
|
||||||
|
# pass
|
||||||
|
#for tempdir in tempdirs:
|
||||||
|
# try:
|
||||||
|
# os.rmdir(tempdir)
|
||||||
|
# except:
|
||||||
|
# pass
|
||||||
|
|
||||||
print(exitcode, result)
|
print(exitcode, result)
|
||||||
|
|
||||||
def execute(self, obj):
|
def execute(self, obj):
|
||||||
print("""This is called when the object is recomputed""")
|
print("""This is called when the object is recomputed""")
|
||||||
self.xmlCommandToPython(obj.Document)
|
self.xmlCommandToPython(obj, obj.Document)
|
||||||
|
|
||||||
#<XternalApps:accepts>
|
|
||||||
# <XternalApps:default ref=".//*" style="double-dash">
|
|
||||||
# <XternalApps:exception ref="my:svgfile" style="pipe" />
|
|
||||||
#</XternalApps:accepts>
|
|
||||||
|
|
||||||
#<XternalApps:returns>
|
|
||||||
# <XternalApps:exception ref="my:output-svgfile" style="pipe" />
|
|
||||||
# <XternalApps:exception ref="my:output-exitcode" style="exitcode" />
|
|
||||||
#</XternalApps:returns>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
70
exampleTool.xforms
Normal file
70
exampleTool.xforms
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<XternalApps:tool xmlns:my="http://github.com/jsmaniac/XternalApps/myTool" xmlns:XternalApps="http://github.com/jsmaniac/XternalApps/v1" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||||
|
<XternalApps:name>Fractalize</XternalApps:name>
|
||||||
|
<XternalApps:tooltip></XternalApps:tooltip>
|
||||||
|
<XternalApps:icon>MyTool.svg</XternalApps:icon>
|
||||||
|
<XternalApps:extended-description>
|
||||||
|
</XternalApps:extended-description>
|
||||||
|
<!-- Internal model and default values follow: -->
|
||||||
|
<xforms:model>
|
||||||
|
<xforms:instance>
|
||||||
|
<my:tool>
|
||||||
|
<my:svgfile filename="" />
|
||||||
|
<my:option1>default value</my:option1>
|
||||||
|
<my:option2/>
|
||||||
|
<my:option3>bar</my:option3>
|
||||||
|
</my:tool>
|
||||||
|
</xforms:instance>
|
||||||
|
<!-- to get a filename on the command-line, use:
|
||||||
|
<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="mime:image/svg+xml" required="true()"/>
|
||||||
|
<xforms:bind ref="my:option1" type="xsd:string" required="true()"/>
|
||||||
|
<xforms:bind ref="my:option2" type="xsd:decimal" required="true()"/>
|
||||||
|
<xforms:bind ref="my:option3" type="my:enum-option3" required="true()"/>
|
||||||
|
<!--<xforms:submission action="myTool.py" method="exec-double-dash" />-->
|
||||||
|
<xsd:schema targetNamespace="http://github.com/jsmaniac/XternalApps/myTool" xmlns:my="http://github.com/jsmaniac/XternalApps/myTool">
|
||||||
|
<xsd:simpleType name="enum-option3">
|
||||||
|
<xsd:restriction base="xsd:string">
|
||||||
|
<xsd:enumeration value="foo" />
|
||||||
|
<xsd:enumeration value="bar" />
|
||||||
|
</xsd:restriction>
|
||||||
|
</xsd:simpleType>
|
||||||
|
</xsd:schema>
|
||||||
|
</xforms:model>
|
||||||
|
|
||||||
|
<XternalApps:command method="exec" name="/home/suzanne/perso/projects/paper-craft/py/Mod/XternalApps/myTool.py">
|
||||||
|
<XternalApps:accepts>
|
||||||
|
<XternalApps:default style="double-dash" />
|
||||||
|
<XternalApps:exception ref="my:svgfile" style="pipe" />
|
||||||
|
</XternalApps:accepts>
|
||||||
|
<XternalApps:returns>
|
||||||
|
<XternalApps:exception ref="my:output-svgfile" style="pipe" />
|
||||||
|
<XternalApps:exception ref="my:output-exitcode" style="exitcode" />
|
||||||
|
</XternalApps:returns>
|
||||||
|
</XternalApps:command>
|
||||||
|
|
||||||
|
<!-- Description of the user interface follows: -->
|
||||||
|
<xforms:group>
|
||||||
|
<xforms:label>Page 1</xforms:label>
|
||||||
|
<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" XternalApps:relevance="primary">
|
||||||
|
<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="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>
|
||||||
|
<xforms:select1 ref="my:option3" label="Option Three (alt labels)">
|
||||||
|
<xforms:item label="Alt foo label" value="foo"/>
|
||||||
|
<xforms:item label="Alt bar label" value="bar"/>
|
||||||
|
</xforms:select1>
|
||||||
|
</xforms:group>
|
||||||
|
</XternalApps:tool>
|
|
@ -1,20 +1,17 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<XternalApps:tool xmlns:my="http://github.com/jsmaniac/XternalApps/myTool" xmlns:XternalApps="http://github.com/jsmaniac/XternalApps/v1" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
<XternalApps:tool xmlns:my="http://github.com/jsmaniac/XternalApps/myTool" xmlns:XternalApps="http://github.com/jsmaniac/XternalApps/v1" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||||
<XternalApps:name>MyTool</XternalApps:name>
|
<XternalApps:name>Fractalize</XternalApps:name>
|
||||||
<XternalApps:tooltip>This tool is my tool, it is very useful in a toolset.</XternalApps:tooltip>
|
<XternalApps:tooltip></XternalApps:tooltip>
|
||||||
<XternalApps:icon>MyTool.svg</XternalApps:icon>
|
<XternalApps:icon>MyTool.svg</XternalApps:icon>
|
||||||
<XternalApps:extended-description>
|
<XternalApps:extended-description>
|
||||||
Lots of text,
|
|
||||||
blah blha bhal
|
|
||||||
</XternalApps:extended-description>
|
</XternalApps:extended-description>
|
||||||
<!-- Internal model and default values follow: -->
|
<!-- Internal model and default values follow: -->
|
||||||
<xforms:model>
|
<xforms:model>
|
||||||
<xforms:instance>
|
<xforms:instance>
|
||||||
<my:tool>
|
<my:tool>
|
||||||
<my:svgfile filename="" />
|
<my:svgfile filename="" />
|
||||||
<my:option1>default value</my:option1>
|
<my:subdivs>6</my:subdivs>
|
||||||
<my:option2/>
|
<my:smooth>4.0</my:smooth>
|
||||||
<my:option3>bar</my:option3>
|
|
||||||
</my:tool>
|
</my:tool>
|
||||||
</xforms:instance>
|
</xforms:instance>
|
||||||
<!-- to get a filename on the command-line, use:
|
<!-- to get a filename on the command-line, use:
|
||||||
|
@ -22,51 +19,27 @@
|
||||||
-->
|
-->
|
||||||
<!-- use XternalApps:pipe to have the file piped directly into the command being run -->
|
<!-- use XternalApps:pipe to have the file piped directly into the command being run -->
|
||||||
<xforms:bind ref="my:svgfile" type="mime:image/svg+xml" required="true()"/>
|
<xforms:bind ref="my:svgfile" type="mime:image/svg+xml" required="true()"/>
|
||||||
<xforms:bind ref="my:option1" type="xsd:string" required="true()"/>
|
<xforms:bind ref="my:subdivs" type="xsd:integer" required="true()"/>
|
||||||
<xforms:bind ref="my:option2" type="xsd:decimal" required="true()"/>
|
<xforms:bind ref="my:smooth" type="xsd:decimal" required="true()"/>
|
||||||
<xforms:bind ref="my:option3" type="my:enum-option3" required="true()"/>
|
|
||||||
<!--<xforms:submission action="myTool.py" method="exec-double-dash" />-->
|
<!--<xforms:submission action="myTool.py" method="exec-double-dash" />-->
|
||||||
<xsd:schema targetNamespace="http://github.com/jsmaniac/XternalApps/myTool" xmlns:my="http://github.com/jsmaniac/XternalApps/myTool">
|
|
||||||
<xsd:simpleType name="enum-option3">
|
|
||||||
<xsd:restriction base="xsd:string">
|
|
||||||
<xsd:enumeration value="foo" />
|
|
||||||
<xsd:enumeration value="bar" />
|
|
||||||
</xsd:restriction>
|
|
||||||
</xsd:simpleType>
|
|
||||||
</xsd:schema>
|
|
||||||
</xforms:model>
|
</xforms:model>
|
||||||
|
|
||||||
<XternalApps:command method="exec" name="/home/suzanne/perso/projects/paper-craft/py/Mod/XternalApps/myTool.py">
|
<XternalApps:command method="exec" name="/home/suzanne/perso/projects/paper-craft/py/Mod/XternalApps/run-inkscape-plugin.py">
|
||||||
<XternalApps:accepts>
|
<XternalApps:constant direction="input" value="fractalize.py" style="value" />
|
||||||
<XternalApps:default style="double-dash" />
|
<XternalApps:default direction="input" style="double-dash" />
|
||||||
<XternalApps:exception ref="my:svgfile" style="pipe" />
|
<XternalApps:exception direction="input" ref="my:svgfile" style="value" />
|
||||||
</XternalApps:accepts>
|
<XternalApps:exception direction="output" ref="my:output-svgfile" style="pipe" />
|
||||||
<XternalApps:returns>
|
<XternalApps:exception direction="output" ref="my:output-exitcode" style="exitcode" />
|
||||||
<XternalApps:exception ref="my:output-svgfile" style="pipe" />
|
|
||||||
<XternalApps:exception ref="my:output-exitcode" style="exitcode" />
|
|
||||||
</XternalApps:returns>
|
|
||||||
</XternalApps:command>
|
</XternalApps:command>
|
||||||
|
|
||||||
<!-- Description of the user interface follows: -->
|
<!-- Description of the user interface follows: -->
|
||||||
<xforms:group>
|
<xforms:group>
|
||||||
<xforms:label>Page 1</xforms:label>
|
<xforms:label>Fractalize</xforms:label>
|
||||||
<xforms:input ref="my:option1" label="Option One ∀"/>
|
<xforms:input ref="my:subdivs" label="Subdivisions"/>
|
||||||
<xforms:input ref="my:option2" label="Option Two π"/>
|
<xforms:input ref="my:smooth" label="Smoothness"/>
|
||||||
<xforms:upload ref="my:svgfile" accept="image/svg+xml" XternalApps:relevance="primary">
|
<xforms:upload ref="my:svgfile" accept="image/svg+xml" XternalApps:relevance="primary">
|
||||||
<xforms:label>Input image</xforms:label>
|
<xforms:label>Input path</xforms:label>
|
||||||
<xforms:filename ref="@filename" />
|
<xforms:filename ref="@filename" />
|
||||||
</xforms:upload>
|
</xforms:upload>
|
||||||
</xforms:group>
|
</xforms:group>
|
||||||
<xforms:group>
|
|
||||||
<xforms:label>Page 2</xforms:label>
|
|
||||||
<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>
|
|
||||||
<xforms:select1 ref="my:option3" label="Option Three (alt labels)">
|
|
||||||
<xforms:item label="Alt foo label" value="foo"/>
|
|
||||||
<xforms:item label="Alt bar label" value="bar"/>
|
|
||||||
</xforms:select1>
|
|
||||||
</xforms:group>
|
|
||||||
</XternalApps:tool>
|
</XternalApps:tool>
|
||||||
|
|
91
run-inkscape-plugin.py
Executable file
91
run-inkscape-plugin.py
Executable file
|
@ -0,0 +1,91 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Does not work with inkscape 1.1
|
||||||
|
#extension_directory="$(inkscape --extension-directory)"
|
||||||
|
|
||||||
|
appImage = '/home/suzanne/perso/dl/software/dessin/Inkscape-16c8184-x86_64.AppImage'
|
||||||
|
|
||||||
|
import sys, os, subprocess, signal, tempfile, re
|
||||||
|
mountProcess = subprocess.Popen([appImage, '--appimage-mount'], stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
# os.path.join, but forbit .. and other tricks that would get out of the base directory.
|
||||||
|
def joinNoDotDot(base, rel):
|
||||||
|
rel = os.fsencode(rel)
|
||||||
|
absBase = os.path.abspath(base)
|
||||||
|
relRequested = os.path.relpath(os.path.join(absBase, rel), start=absBase)
|
||||||
|
absRequested = os.path.abspath(os.path.join(absBase, relRequested))
|
||||||
|
commonPrefix = os.path.commonprefix([absRequested, absBase])
|
||||||
|
if relRequested.startswith(os.fsencode(os.pardir)):
|
||||||
|
raise ValueError("security check failed: requested inkscape extension is outside of inkscape extension directory 1" + repr(relRequested))
|
||||||
|
elif commonPrefix != absBase:
|
||||||
|
raise ValueError("security check failed: requested inkscape extension is outside of inkscape extension directory 2")
|
||||||
|
else:
|
||||||
|
return absRequested
|
||||||
|
|
||||||
|
tempfiles=[]
|
||||||
|
tempdirs=[]
|
||||||
|
try:
|
||||||
|
d = tempfile.mkdtemp()
|
||||||
|
tempdirs += [d]
|
||||||
|
pathfile = os.path.join(d, "as_path.svg")
|
||||||
|
tempfiles += [pathfile]
|
||||||
|
|
||||||
|
# Convert all objects (incl. circles) to paths and extract the IDs (not all FreeCAD objects in the SVG have an ID, converting to paths seems to assign them an ID)
|
||||||
|
processAsPath = subprocess.Popen([appImage, sys.argv[-1], '--actions=select-all;object-to-path;select-list', '--export-type=svg', '--export-filename=' + pathfile], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
asPath = processAsPath.communicate()
|
||||||
|
if processAsPath.returncode != 0:
|
||||||
|
# TODO: forward the stderr and use subprocess.CalledProcessError
|
||||||
|
raise ValueError("Child process failed: could not execute Inkscape to convert SVG objects to paths")
|
||||||
|
lines = asPath[0].split(b'\n')
|
||||||
|
if lines[0] == b'Run experimental bundle that bundles everything':
|
||||||
|
lines = lines[1:]
|
||||||
|
ids = [line.split(b' ')[0] for line in lines]
|
||||||
|
|
||||||
|
try:
|
||||||
|
if mountProcess.poll() is None:
|
||||||
|
mountPoint = mountProcess.stdout.readline().rstrip(b'\r\n') # TODO: this is slightly wrong (removes multiple occurrences)
|
||||||
|
else:
|
||||||
|
raise "Could not mount AppImage"
|
||||||
|
|
||||||
|
apprun = os.path.join(mountPoint, b'AppRun')
|
||||||
|
extensionDirectory = os.path.join(mountPoint, b'usr/share/inkscape/extensions')
|
||||||
|
e = os.environb.copy()
|
||||||
|
e['PYTHONPATH'] = e.get('PYTHONPATH', b'') + os.path.pathsep.encode('utf-8') + os.path.join(mountPoint, b'usr/lib/python3/dist-packages/')
|
||||||
|
extensionPy = joinNoDotDot(extensionDirectory, sys.argv[1])
|
||||||
|
cmd = [apprun, 'python3.8', extensionPy] + [b'--id='+id for id in ids] + sys.argv[2:-1] + [pathfile]
|
||||||
|
process = subprocess.Popen(cmd, env=e, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
result = process.communicate()[0]
|
||||||
|
if process.returncode != 0:
|
||||||
|
# TODO: forward the stderr and use subprocess.CalledProcessError
|
||||||
|
raise ValueError("Child process failed: could not execute Inkscape extension with the given parameters")
|
||||||
|
lines = result.split(b'\n')
|
||||||
|
if lines[0] == b'Run experimental bundle that bundles everything':
|
||||||
|
lines = lines[1:]
|
||||||
|
# TODO: use lxml instead
|
||||||
|
with open('/tmp/fooaaa', 'w') as debug:
|
||||||
|
debug.write('trollollo')
|
||||||
|
debug.write('1' + str(lines[0])+'\n\n')
|
||||||
|
lines[0] = re.sub(rb' version="([.0-9]*)"', rb' version="\1" inkscape:version="\1"', lines[0])
|
||||||
|
debug.write('2' + str(lines[0])+'\n\n')
|
||||||
|
#sed -i -e '1 s/ version="1.1"/ inkscape:version="1.1"&/'
|
||||||
|
sys.stdout.buffer.write(b'\n'.join(lines))
|
||||||
|
# --id=rect815 --subdivs 6 --smooth 4.0 /tmp/a.svg 2>/dev/null | sed -e '/Run experimental bundle that bundles everything/,1 d'
|
||||||
|
|
||||||
|
finally:
|
||||||
|
mountProcess.send_signal(signal.SIGINT)
|
||||||
|
|
||||||
|
#LD_LIBRARY_PATH= PYTHONPATH="$extension_directory:$PYTHONPATH" python2 "$extension_directory"/fractalize.py "$@"
|
||||||
|
#/nix/store/zr4xihxd720pq3n0a58ixrqa243hx7an-python-2.7.17-env/bin/python2.7
|
||||||
|
|
||||||
|
#./perso/dl/software/dessin/Inkscape-3bc2e81-x86_64.AppImage --appimage-mount
|
||||||
|
finally:
|
||||||
|
for tempfile in tempfiles:
|
||||||
|
try:
|
||||||
|
os.remove(tempfile)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
for tempdir in tempdirs:
|
||||||
|
try:
|
||||||
|
os.rmdir(tempdir)
|
||||||
|
except:
|
||||||
|
pass
|
Loading…
Reference in New Issue
Block a user