diff --git a/CadQuery/Examples/Ex027_Remote_Enclosure.py b/CadQuery/Examples/Ex027_Remote_Enclosure.py new file mode 100644 index 0000000..ae014a3 --- /dev/null +++ b/CadQuery/Examples/Ex027_Remote_Enclosure.py @@ -0,0 +1,90 @@ +# This example is meant to be used from within the CadQuery module of FreeCAD. +import cadquery as cq +import Part + +exploded = False # when true, moves the base away from the top so we see +showTop = True # When true, the top is rendered. +showCover = True # When true, the cover is rendered + +width = 2.2 # Nominal x dimension of the part +height = 0.5 # Height from bottom top to the top of the top :P +length = 1.5 # Nominal y dimension of the part +trapezoidFudge = 0.7 # ratio of trapezoid bases. set to 1.0 for cube +xHoleOffset = 0.500 # Holes are distributed symetrically about each axis +yHoleOffset = 0.500 +zFilletRadius = 0.50 # Fillet radius of corners perp. to Z axis. +yFilletRadius = 0.250 # Fillet readius of the top edge of the case +lipHeight = 0.1 # The height of the lip on the inside of the cover +wallThickness = 0.06 # Wall thickness for the case +coverThickness = 0.2 # Thickness of the cover plate +holeRadius = 0.30 # Button hole radius +counterSyncAngle = 100 # Countersink angle. + +xyplane = cq.Workplane("XY") +yzplane = cq.Workplane("YZ") + + +def trapezoid(b1, b2, h): + "Defines a symetrical trapezoid in the XY plane." + + y = h / 2 + x1 = b1 / 2 + x2 = b2 / 2 + return (xyplane + .polyline([(-x1, y), + (x1, y), + (x2, -y), + (-x2, -y), + (-x1, y)])) + + +# Defines our base shape: a box with fillets around the vertical edges. +# This has to be a function because we need to create multiple copies of +# the shape. +def base(h): + global width, trapezoidFudge, length, zFilletRadius + return (trapezoid(width, width * trapezoidFudge, length) + .extrude(h) + .translate((0, 0, height / 2)) + .edges("Z") + .fillet(zFilletRadius)) + +# start with the base shape +top = (base(height) + # then fillet the top edge + .edges(">Z") + .fillet(yFilletRadius) + # shell the solid from the bottom face, with a .060" wall thickness + .faces("-Z") + .shell(-wallThickness) + # cut five button holes into the top face in a cross pattern. + .faces("+Z") + .workplane() + .pushPoints([(0, 0), + (-xHoleOffset, 0), + (0, -yHoleOffset), + (xHoleOffset, 0), + (0, yHoleOffset)]) + .cskHole(diameter=holeRadius, + cskDiameter=holeRadius * 1.5, + cskAngle=counterSyncAngle)) + +# the bottom cover begins with the same basic shape as the top +cover = (base(coverThickness) + # we need to move it upwards into the parent solid slightly. + .translate((0, 0, -coverThickness + lipHeight)) + # now we subtract the top from the cover. This produces a lip on the + # solid NOTE: that this does not account for mechanical tolerances. + # But it looks cool. + .cut(top) + # try to fillet the inner edge of the cover lip. Technically this + # fillets every edge perpendicular to the Z axis. + .edges("#Z") + .fillet(.020) + .translate((0, 0, -0.5 if exploded else 0))) + +# Conditionally render the parts +if showTop: + Part.show(top.toFreecad()) +if showCover: + Part.show(cover.toFreecad()) diff --git a/CadQuery/Gui/Command.py b/CadQuery/Gui/Command.py index 139092d..7ce7a81 100644 --- a/CadQuery/Gui/Command.py +++ b/CadQuery/Gui/Command.py @@ -1,17 +1,20 @@ """Adds all of the commands that are used for the menus of the CadQuery module""" # (c) 2014 Jeremy Wright LGPL v3 -import tempfile +import imp, os, sys, tempfile import FreeCAD, FreeCADGui from PySide import QtGui -import ExportCQ, ImportCQ, CadQuery_rc +import ExportCQ, ImportCQ +import module_locator #Distinguish python built-in open function from the one declared here if open.__module__ == '__builtin__': pythonopen = open -def clearAll(): +def clearActiveDocument(): + """Clears the currently active 3D view so that we can re-render""" + doc = FreeCAD.ActiveDocument #Make sure we have an active document to work with @@ -29,35 +32,47 @@ class CadQueryCloseScript: def IsActive(self): return True + # if FreeCAD.ActiveDocument is None: + # return False + # else: + # return True def Activated(self): - #Getting the main window will allow us to find the children we need to work with + #Grab our code editor so we can interact with it mw = FreeCADGui.getMainWindow() - - #We need this so we can load the file into it cqCodePane = mw.findChild(QtGui.QPlainTextEdit, "cqCodePane") - 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: + #If there's nothing open in the code pane, we don't need to close it + if len(cqCodePane.file.path) == 0: 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.file.path) == 0: - filename = QtGui.QFileDialog.getSaveFileName(mw, mw.tr("Save CadQuery Script As"), "/home/", - mw.tr("CadQuery Files (*.py)")) - else: - filename = cqCodePane.file.path + #Check to see if we need to save the script + if cqCodePane.dirty: + reply = QtGui.QMessageBox.question(cqCodePane, "Save CadQuery Script", "Save script before closing?", + QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | QtGui.QMessageBox.Cancel) - #Make sure we got a valid file name - if filename == None: - ExportCQ.save(filename) + 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.file.path) == 0: + filename = QtGui.QFileDialog.getSaveFileName(mw, mw.tr("Save CadQuery Script As"), "/home/", + mw.tr("CadQuery Files (*.py)")) + else: + filename = cqCodePane.file.path + + #Make sure we got a valid file name + if filename is not None: + ExportCQ.save(filename) + + #Close the matching 3D view if it's open + if cqCodePane.file.path is not None: + docname = os.path.splitext(os.path.basename(cqCodePane.file.path))[0] + FreeCAD.closeDocument(docname) #Clear our script and whatever was rendered by it out cqCodePane.file.close() - clearAll() class CadQueryExecuteScript: @@ -71,26 +86,32 @@ class CadQueryExecuteScript: def IsActive(self): return True - # if FreeCAD.ActiveDocument is None: - # return False - # else: - # return True def Activated(self): - #Getting the main window will allow us to find the children we need to work with + #Grab our code editor so we can interact with it mw = FreeCADGui.getMainWindow() - - #We need this so we can load the file into it cqCodePane = mw.findChild(QtGui.QPlainTextEdit, "cqCodePane") - clearAll() + #Clear the old render before re-rendering + clearActiveDocument() #Save our code to a tempfile and render it tempFile = tempfile.NamedTemporaryFile(delete=False) tempFile.write(cqCodePane.toPlainText()) tempFile.close() FreeCAD.Console.PrintMessage("\r\n") - execfile(tempFile.name) + + docname = os.path.splitext(os.path.basename(cqCodePane.file.path))[0] + + #If the matching 3D view has been closed, we need to open a new one + try: + FreeCAD.getDocument(docname) + except: + FreeCAD.newDocument(docname) + + + #We import this way because using execfile() causes non-standard script execution in some situations + imp.load_source('temp.module', tempFile.name) msg = QtGui.QApplication.translate( "cqCodeWidget", @@ -100,7 +121,29 @@ class CadQueryExecuteScript: FreeCAD.Console.PrintMessage("\r\n" + msg + cqCodePane.file.path) -class CadQueryOpenScript(): +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): + #We need to close any file that's already open in the editor window + CadQueryCloseScript().Activated() + + 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')) + + +class CadQueryOpenScript: """CadQuery's command to open a script file.""" previousPath = None @@ -112,20 +155,14 @@ class CadQueryOpenScript(): def IsActive(self): return True - # if FreeCAD.ActiveDocument is None: - # return False - # else: - # return True def Activated(self): - import os, sys - + #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: - import os, module_locator - + #Start off defaulting to the Examples directory module_base_path = module_locator.module_path() exs_dir_path = os.path.join(module_base_path, 'Examples') @@ -136,6 +173,9 @@ class CadQueryOpenScript(): #Make sure the user didn't click cancel if filename[0]: + #We need to close any file that's already open in the editor window + CadQueryCloseScript().Activated() + self.previousPath = filename[0] #Append this script's directory to sys.path @@ -144,6 +184,16 @@ class CadQueryOpenScript(): #We've created a library that FreeCAD can use as well to open CQ files ImportCQ.open(filename[0]) + docname = os.path.splitext(os.path.basename(filename[0]))[0] + FreeCAD.newDocument(docname) + + #Execute the script + CadQueryExecuteScript().Activated() + + #Get a nice view of our model + FreeCADGui.activeDocument().activeView().viewAxometric() + FreeCADGui.SendMsgToActiveView("ViewFit") + class CadQuerySaveScript: """CadQuery's command to save a script file""" @@ -156,20 +206,16 @@ class CadQuerySaveScript: def IsActive(self): return True - # if FreeCAD.ActiveDocument is None: - # return False - # else: - # return True def Activated(self): - #Getting the main window will allow us to find the children we need to work with + #Grab our code editor so we can interact with it mw = FreeCADGui.getMainWindow() - - #We need this so we can load the file into it cqCodePane = mw.findChild(QtGui.QPlainTextEdit, "cqCodePane") - #If the codepane doesn't have a filename, we need to present the save as dialog - if len(cqCodePane.file.path) == 0: + #If the code pane doesn't have a filename, we need to present the save as dialog + if len(cqCodePane.file.path) == 0 or os.path.basename(cqCodePane.file.path) == 'script_template.py': + FreeCAD.Console.PrintMessage("\r\nYou cannot save a blank file or save over a template file.") + CadQuerySaveAsScript().Activated() return @@ -189,13 +235,11 @@ class CadQuerySaveAsScript: def IsActive(self): return True - # if FreeCAD.ActiveDocument is None: - # return False - # else: - # return True def Activated(self): + #So we can open the save-as dialog mw = FreeCADGui.getMainWindow() + cqCodePane = mw.findChild(QtGui.QPlainTextEdit, "cqCodePane") #Try to keep track of the previous path used to open as a convenience to the user if self.previousPath is None: @@ -208,6 +252,14 @@ class CadQuerySaveAsScript: #Make sure the user didn't click cancel if filename[0]: - #Save the file before the re-render + #Close the 3D view for the original script if it's open + try: + docname = os.path.splitext(os.path.basename(cqCodePane.file.path))[0] + FreeCAD.closeDocument(docname) + except: + #Assume that there was no 3D view to close + pass + + #Save the file before closing the original and the re-rendering the new one ExportCQ.save(filename[0]) - execfile(filename[0]) \ No newline at end of file + CadQueryExecuteScript().Activated() diff --git a/CadQuery/Gui/ExportCQ.py b/CadQuery/Gui/ExportCQ.py index 655f51f..dbcbaf1 100644 --- a/CadQuery/Gui/ExportCQ.py +++ b/CadQuery/Gui/ExportCQ.py @@ -13,10 +13,8 @@ def save(filename=None): :param filename: The path and file name to save to. If not provided we try to pull it from the code pane itself """ - #Getting the main window will allow us to find the children we need to work with + #Grab our code editor so we can interact with it mw = FreeCADGui.getMainWindow() - - #We need this so we can load the file into it cqCodePane = mw.findChild(QtGui.QPlainTextEdit, "cqCodePane") #If we weren't provided a file name, we need to find it from the text field diff --git a/CadQuery/Gui/ImportCQ.py b/CadQuery/Gui/ImportCQ.py index e74a56d..a3cd04b 100644 --- a/CadQuery/Gui/ImportCQ.py +++ b/CadQuery/Gui/ImportCQ.py @@ -1,17 +1,15 @@ """Adds the ability to open files from disk to the CadQuery FreeCAD module""" # (c) 2014 Jeremy Wright LGPL v3 -import os, FreeCAD, FreeCADGui +import FreeCAD, FreeCADGui from PySide import QtGui #Distinguish python built-in open function from the one declared here if open.__module__ == '__builtin__': pythonopen = open -def open(filename): - docname = os.path.splitext(os.path.basename(filename))[0] - doc = FreeCAD.newDocument(docname) +def open(filename): #All of the Gui.* calls in the Python console break after opening if we don't do this FreeCADGui.doCommand("import FreeCADGui as Gui") @@ -24,8 +22,6 @@ def open(filename): #Pull the text of the CQ script file into our code pane cqCodePane.file.open(filename) - execfile(filename) - msg = QtGui.QApplication.translate( "cqCodeWidget", "Opened ", @@ -33,4 +29,4 @@ def open(filename): QtGui.QApplication.UnicodeUTF8) FreeCAD.Console.PrintMessage("\r\n" + msg + filename) - return doc \ No newline at end of file + return diff --git a/CadQuery/InitGui.py b/CadQuery/InitGui.py index 5597600..eedea2e 100644 --- a/CadQuery/InitGui.py +++ b/CadQuery/InitGui.py @@ -2,9 +2,10 @@ This adds a workbench with a scripting editor to FreeCAD's GUI.""" # (c) 2014 Jeremy Wright LGPL v3 -import FreeCAD -import FreeCADGui +import FreeCAD, FreeCADGui from Gui.Command import * +import CadQuery_rc + class CadQueryWorkbench (Workbench): """CadQuery workbench for FreeCAD""" @@ -27,13 +28,14 @@ class CadQueryWorkbench (Workbench): #logging.basicConfig(filename='/home/jwright/Documents/log.txt', level=logging.DEBUG) #We have our own CQ menu that's added when the user chooses our workbench - commands = ['CadQueryOpenScript', 'CadQuerySaveScript', 'CadQuerySaveAsScript', 'CadQueryExecuteScript', - 'CadQueryCloseScript'] + commands = ['CadQueryNewScript', 'CadQueryOpenScript', 'CadQuerySaveScript', 'CadQuerySaveAsScript', + 'CadQueryCloseScript', 'Separator', 'CadQueryExecuteScript'] self.appendMenu('CadQuery', commands) def Activated(self): import os, sys import module_locator + from Gui import Command, ImportCQ #Set up so that we can import from our embedded packages module_base_path = module_locator.module_path() @@ -53,7 +55,6 @@ class CadQueryWorkbench (Workbench): sys.path.insert(1, fc_bin_path) import cadquery - from Gui import ImportCQ from pyqode.python.widgets import PyCodeEdit from PySide import QtGui, QtCore @@ -87,7 +88,7 @@ class CadQueryWorkbench (Workbench): interpreter = 'python' #Getting the main window will allow us to start setting things up the way we want - mw = Gui.getMainWindow() + mw = FreeCADGui.getMainWindow() #Find all of the docks that are open so we can close them (except the Python console) dockWidgets = mw.findChildren(QtGui.QDockWidget) @@ -109,7 +110,7 @@ class CadQueryWorkbench (Workbench): #Set up the text area for our CQ code server_path = os.path.join(module_base_path, 'cq_server.py') - #Windows needs some exra help with paths + #Windows needs some extra help with paths if sys.platform.startswith('win'): codePane = PyCodeEdit(server_script=server_path, interpreter=interpreter , args=['-s', fc_lib_path, libs_dir_path]) @@ -122,25 +123,25 @@ class CadQueryWorkbench (Workbench): #Add the text area to our dock widget cqCodeWidget.setWidget(codePane) - #Open our introduction example + #Open and execute our introduction example example_path = os.path.join(module_base_path, 'Examples') example_path = os.path.join(example_path, 'Ex000_Introduction.py') ImportCQ.open(example_path) + docname = os.path.splitext(os.path.basename(example_path))[0] + FreeCAD.newDocument(docname) + Command.CadQueryExecuteScript().Activated() + + #Get a nice view of our example + FreeCADGui.activeDocument().activeView().viewAxometric() + FreeCADGui.SendMsgToActiveView("ViewFit") def Deactivated(self): - from Gui import ExportCQ - #Put the UI back the way we found it FreeCAD.Console.PrintMessage("\r\nCadQuery Workbench Deactivated\r\n") - #Rely on our export library to help us save the file - ExportCQ.save() - - #TODO: This won't work for now because the views are destroyed when they are hidden - # for widget in self.closedWidgets: - # FreeCAD.Console.PrintMessage(widget.objectName()) - # widget.setVisible(True) + Command.CadQueryCloseScript().Activated() +FreeCADGui.addCommand('CadQueryNewScript', CadQueryNewScript()) FreeCADGui.addCommand('CadQueryOpenScript', CadQueryOpenScript()) FreeCADGui.addCommand('CadQuerySaveScript', CadQuerySaveScript()) FreeCADGui.addCommand('CadQuerySaveAsScript', CadQuerySaveAsScript()) diff --git a/CadQuery/Templates/script_template.py b/CadQuery/Templates/script_template.py new file mode 100644 index 0000000..88dac12 --- /dev/null +++ b/CadQuery/Templates/script_template.py @@ -0,0 +1,5 @@ +# This is a CadQuery script template +# Add your script code below + +# Length units +length_uom = 'mm'