-
Notifications
You must be signed in to change notification settings - Fork 39
/
Copy pathCommand.py
483 lines (363 loc) · 17.9 KB
/
Command.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# -*- coding: utf-8 -*-
"""Adds all of the commands that are used for the menus of the CadQuery module"""
# (c) 2014-2018 Jeremy Wright Apache 2.0 License
import imp, os, sys, tempfile
import FreeCAD, FreeCADGui
from PySide import QtGui, QtCore
try:
import ExportCQ
except:
from . import ExportCQ
try:
import ImportCQ
except:
from . import ImportCQ
import module_locator
import Shared
from random import random
from contextlib import contextmanager
from SettingsDialog import SettingsDialog
from cadquery import cqgi
from Helpers import show
# Distinguish python built-in open function from the one declared here
if open.__module__ == '__builtin__':
pythonopen = open
@contextmanager
def revert_sys_modules():
"""
Remove any new modules after context has exited
>>> with revert_sys_modules():
... import some_module
... some_module.do_something()
>>> some_module.do_something() # raises NameError: name 'some_module' is not defined
"""
modules_before = set(sys.modules.keys())
try:
yield
finally:
# irrespective of the success of the context's execution, new modules
# will be deleted upon exit
for mod_name in list(sys.modules.keys()):
if mod_name not in modules_before:
del sys.modules[mod_name]
class CadQueryClearOutput:
"""Allows the user to clear the reports view when it gets overwhelmed with output"""
def GetResources(self):
return {"MenuText": "Clear Output",
"Accel": "Shift+Alt+C",
"ToolTip": "Clears the script output from the Reports view",
"Pixmap": ":/icons/button_invalid.svg"}
def IsActive(self):
return True
def Activated(self):
# Grab our main window so we can interact with it
mw = FreeCADGui.getMainWindow()
reportView = mw.findChild(QtGui.QDockWidget, "Report view")
# Clear the view because it gets overwhelmed sometimes and won't scroll to the bottom
reportView.widget().clear()
class CadQueryCloseScript:
"""Allows the user to close a file without saving"""
def GetResources(self):
return {"MenuText": "Close Script",
"ToolTip": "Closes the CadQuery script",
"Pixmap": ":/icons/edit_Cancel.svg"}
def IsActive(self):
return True
def Activated(self):
mw = FreeCADGui.getMainWindow()
# Grab our code editor so we can interact with it
cqCodePane = Shared.getActiveCodePane()
# If there's nothing open in the code pane, we don't need to close it
if cqCodePane is None or len(cqCodePane.get_path()) == 0:
return
# Check to see if we need to save the script
if cqCodePane.is_dirty():
reply = QtGui.QMessageBox.question(cqCodePane, "Save CadQuery Script", "Save script before closing?",
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | QtGui.QMessageBox.Cancel)
if reply == QtGui.QMessageBox.Cancel:
return
if reply == QtGui.QMessageBox.Yes:
# If we've got a file name already save it there, otherwise give a save-as dialog
if len(cqCodePane.get_path()) == 0:
filename = QtGui.QFileDialog.getSaveFileName(mw, mw.tr("Save CadQuery Script As"), "/home/",
mw.tr("CadQuery Files (*.py)"))
else:
filename = cqCodePane.get_path()
# Make sure we got a valid file name
if filename is not None:
ExportCQ.save(filename)
Shared.closeActiveCodeWindow()
class CadQueryOpenExample:
exFile = None
def __init__(self, exFile):
self.exFile = str(exFile)
def GetResources(self):
return {"MenuText": str(self.exFile),
"Pixmap": ":/icons/accessories-text-editor.svg"}
def Activated(self):
FreeCAD.Console.PrintMessage(self.exFile + "\r\n")
# So we can open the "Open File" dialog
mw = FreeCADGui.getMainWindow()
# Start off defaulting to the Examples directory
module_base_path = module_locator.module_path()
exs_dir_path = os.path.join(module_base_path, 'Libs/cadquery/examples/FreeCAD')
# Append this script's directory to sys.path
sys.path.append(os.path.dirname(exs_dir_path))
# We've created a library that FreeCAD can use as well to open CQ files
ImportCQ.open(os.path.join(exs_dir_path, self.exFile))
class CadQueryExecuteScript:
"""CadQuery's command to execute a script file"""
def GetResources(self):
return {"MenuText": "Execute Script",
"Accel": FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/cadquery-freecad-module").GetString("executeKeybinding"),
"ToolTip": "Executes the CadQuery script",
"Pixmap": ":/icons/media-playback-start.svg"}
def IsActive(self):
return True
def Activated(self):
# Grab our code editor so we can interact with it
cqCodePane = Shared.getActiveCodePane()
# Clear the old render before re-rendering
Shared.clearActiveDocument()
scriptText = cqCodePane.toPlainText().encode('utf-8')
# Check to see if we are executig a CQGI compliant script
if b"show_object(" in scriptText or b"debug(" in scriptText:
FreeCAD.Console.PrintMessage("Executing CQGI-compliant script.\r\n")
# A repreentation of the CQ script with all the metadata attached
cqModel = cqgi.parse(scriptText)
# Allows us to present parameters to users later that they can alter
parameters = cqModel.metadata.parameters
build_parameters = {}
# Collect the build parameters from the Parameters Editor view, if they exist
mw = FreeCADGui.getMainWindow()
# Tracks whether or not we have already added the variables editor
isPresent = False
# If the widget is open, we need to close it
dockWidgets = mw.findChildren(QtGui.QDockWidget)
for widget in dockWidgets:
if widget.objectName() == "cqVarsEditor":
# Toggle the visibility of the widget
if not widget.visibleRegion().isEmpty():
# Find all of the controls that will have parameter values in them
valueControls = mw.findChildren(QtGui.QLineEdit)
for valueControl in valueControls:
objectName = valueControl.objectName()
# We only want text fields that will have parameter values in them
if objectName != None and objectName != '' and objectName.find('pcontrol_') >= 0:
# Associate the value in the text field with the variable name in the script
build_parameters[objectName.replace('pcontrol_', '')] = valueControl.text()
build_result = cqModel.build(build_parameters=build_parameters)
# if Settings.report_execute_time:
# FreeCAD.Console.PrintMessage("Script executed in " + str(build_result.buildTime) + " seconds\r\n")
# Make sure that the build was successful
if build_result.success:
# Display all the results that the user requested
for result in build_result.results:
# Apply options to the show function if any were provided
if result.options and result.options["rgba"]:
show(result.shape, result.options["rgba"])
else:
show(result.shape)
for debugObj in build_result.debugObjects:
# Mark this as a debug object
debugObj.shape.val().label = "Debug" + str(random())
# Apply options to the show function if any were provided
if debugObj.options and debugObj.options["rgba"]:
show(debugObj.shape, debugObj.options["rgba"])
else:
show(debugObj.shape, (255, 0, 0, 0.80))
else:
FreeCAD.Console.PrintError("Error executing CQGI-compliant script. " + str(build_result.exception) + "\r\n")
else:
# Save our code to a tempfile and render it
tempFile = tempfile.NamedTemporaryFile(delete=False)
tempFile.write(scriptText)
tempFile.close()
# Set some environment variables that may help the user
os.environ["MYSCRIPT_FULL_PATH"] = cqCodePane.get_path()
os.environ["MYSCRIPT_DIR"] = os.path.dirname(os.path.abspath(cqCodePane.get_path()))
# We import this way because using execfile() causes non-standard script execution in some situations
with revert_sys_modules():
imp.load_source('__cq_freecad_module__', tempFile.name)
msg = QtGui.QApplication.translate(
"cqCodeWidget",
"Executed ",
None)
FreeCAD.Console.PrintMessage(msg + cqCodePane.get_path() + "\r\n")
class CadQueryNewScript:
"""CadQuery's command to start a new script file."""
def GetResources(self):
return {"MenuText": "New Script",
"Accel": "Alt+N",
"ToolTip": "Starts a new CadQuery script",
"Pixmap": ":/icons/document-new.svg"}
def IsActive(self):
return True
def Activated(self):
module_base_path = module_locator.module_path()
templ_dir_path = os.path.join(module_base_path, 'Templates')
# Use the library that FreeCAD can use as well to open CQ files
ImportCQ.open(os.path.join(templ_dir_path, 'script_template.py'))
FreeCAD.Console.PrintMessage("Please save this template file as another name before creating any others.\r\n")
class CadQueryOpenScript:
"""CadQuery's command to open a script file."""
previousPath = None
def GetResources(self):
return {"MenuText": "Open Script",
"Accel": "Alt+O",
"ToolTip": "Opens a CadQuery script from disk",
"Pixmap": ":/icons/document-open.svg"}
def IsActive(self):
return True
def Activated(self):
# So we can open the "Open File" dialog
mw = FreeCADGui.getMainWindow()
# Try to keep track of the previous path used to open as a convenience to the user
if self.previousPath is None:
# Start off defaulting to the Examples directory
module_base_path = module_locator.module_path()
exs_dir_path = os.path.join(module_base_path, 'Libs/cadquery/examples/FreeCAD')
self.previousPath = exs_dir_path
filename = QtGui.QFileDialog.getOpenFileName(mw, mw.tr("Open CadQuery Script"), self.previousPath,
mw.tr("CadQuery Files (*.py)"))
# Make sure the user didn't click cancel
if filename[0]:
self.previousPath = filename[0]
# Append this script's directory to sys.path
sys.path.append(os.path.dirname(filename[0]))
# We've created a library that FreeCAD can use as well to open CQ files
ImportCQ.open(filename[0])
class CadQuerySaveScript:
"""CadQuery's command to save a script file"""
def GetResources(self):
return {"MenuText": "Save Script",
"Accel": "Alt+S",
"ToolTip": "Saves the CadQuery script to disk",
"Pixmap": ":/icons/document-save.svg"}
def IsActive(self):
return True
def Activated(self):
# Grab our code editor so we can interact with it
cqCodePane = Shared.getActiveCodePane()
# If there are no windows open there is nothing to save
if cqCodePane == None:
FreeCAD.Console.PrintError("Nothing to save.\r\n")
return
# If the code pane doesn't have a filename, we need to present the save as dialog
if len(cqCodePane.get_path()) == 0 or os.path.basename(cqCodePane.get_path()) == 'script_template.py' \
or os.path.split(cqCodePane.get_path())[0].endswith('FreeCAD'):
FreeCAD.Console.PrintError("You cannot save over a blank file, example file or template file.\r\n")
CadQuerySaveAsScript().Activated()
return
# Rely on our export library to help us save the file
ExportCQ.save()
# Execute the script if the user has asked for it
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/cadquery-freecad-module").GetBool("executeOnSave"):
CadQueryExecuteScript().Activated()
class CadQuerySaveAsScript:
"""CadQuery's command to save-as a script file"""
previousPath = None
def GetResources(self):
return {"MenuText": "Save Script As",
"Accel": "",
"ToolTip": "Saves the CadQuery script to disk in a location other than the original",
"Pixmap": ":/icons/document-save-as.svg"}
def IsActive(self):
return True
def Activated(self):
# So we can open the save-as dialog
mw = FreeCADGui.getMainWindow()
cqCodePane = Shared.getActiveCodePane()
if cqCodePane == None:
FreeCAD.Console.PrintError("Nothing to save.\r\n")
return
# Try to keep track of the previous path used to open as a convenience to the user
if self.previousPath is None:
self.previousPath = "/home/"
filename = QtGui.QFileDialog.getSaveFileName(mw, mw.tr("Save CadQuery Script As"), self.previousPath,
mw.tr("CadQuery Files (*.py)"))
self.previousPath = filename[0]
# Make sure the user didn't click cancel
if filename[0]:
# Close the 3D view for the original script if it's open
try:
docname = os.path.splitext(os.path.basename(cqCodePane.get_path()))[0]
FreeCAD.closeDocument(docname)
except:
# Assume that there was no 3D view to close
pass
# Change the name of our script window's tab
Shared.setActiveWindowTitle(os.path.basename(filename[0]))
# Save the file before closing the original and the re-rendering the new one
ExportCQ.save(filename[0])
CadQueryExecuteScript().Activated()
class ToggleParametersEditor:
"""If the user is running a CQGI-compliant script, they can edit variables through this edistor"""
def GetResources(self):
return {"MenuText": "Toggle Parameters Editor",
"Accel": "Shift+Alt+E",
"ToolTip": "Opens a live variables editor editor",
"Pixmap": ":/icons/edit-edit.svg"}
def IsActive(self):
return True
def Activated(self):
mw = FreeCADGui.getMainWindow()
# Tracks whether or not we have already added the variables editor
isPresent = False
# If the widget is open, we need to close it
dockWidgets = mw.findChildren(QtGui.QDockWidget)
for widget in dockWidgets:
if widget.objectName() == "cqVarsEditor":
# Toggle the visibility of the widget
if widget.visibleRegion().isEmpty():
widget.setVisible(True)
else:
widget.setVisible(False)
isPresent = True
if not isPresent:
cqVariablesEditor = QtGui.QDockWidget("CadQuery Variables Editor")
cqVariablesEditor.setObjectName("cqVarsEditor")
mw.addDockWidget(QtCore.Qt.LeftDockWidgetArea, cqVariablesEditor)
# Go ahead and populate the view if there are variables in the script
CadQueryValidateScript().Activated()
class CadQueryValidateScript:
"""Checks the script for the user without executing it and populates the variable editor, if needed"""
def GetResources(self):
return {"MenuText": "Validate Script",
"Accel": "F4",
"ToolTip": "Validates a CadQuery script",
"Pixmap": ":/icons/edit_OK.svg"}
def IsActive(self):
return True
def Activated(self):
# Grab our code editor so we can interact with it
cqCodePane = Shared.getActiveCodePane()
# If there is no script to check, ignore this command
if cqCodePane is None:
FreeCAD.Console.PrintMessage("There is no script to validate.")
return
# Clear the old render before re-rendering
Shared.clearActiveDocument()
scriptText = cqCodePane.toPlainText().encode('utf-8')
if (b"show_object(" not in scriptText) and (b"debug(" not in scriptText):
FreeCAD.Console.PrintError("Script did not call show_object or debug, no output available. Script must be CQGI compliant to get build output, variable editing and validation.\r\n")
return
# A repreentation of the CQ script with all the metadata attached
cqModel = cqgi.parse(scriptText)
# Allows us to present parameters to users later that they can alter
parameters = cqModel.metadata.parameters
Shared.populateParameterEditor(parameters)
class CadQuerySettings:
"""Opens a settings dialog, allowing the user to change the settings for this workbench"""
def GetResources(self):
return {"MenuText": "Settings",
"Accel": "",
"ToolTip": "Opens the settings dialog",
"Pixmap": ":/icons/preferences-general.svg"}
def IsActive(self):
return True
def Activated(self):
win = SettingsDialog()
win.exec_()
# if win.exec_() == QtGui.QDialog.Accepted:
# print("Settings finished")