Fixed numerous issues relating to opening, closing, newing up, and saving scripts. Also added an example contributed by @emdash.

This commit is contained in:
Jeremy Wright 2014-12-18 11:11:46 -05:00
parent 88b9b2d244
commit d8c2a53cf5
5 changed files with 165 additions and 70 deletions

View File

@ -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())

View File

@ -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,12 +32,14 @@ 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")
#If there's nothing open in the code pane, we don't need to close it
@ -61,10 +66,13 @@ class CadQueryCloseScript:
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()
#TODO Close the matching 3D view here instead of clearing it
clearAll()
class CadQueryExecuteScript:
@ -78,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",
@ -119,8 +133,6 @@ class CadQueryNewScript:
return True
def Activated(self):
import os, module_locator
#We need to close any file that's already open in the editor window
CadQueryCloseScript().Activated()
@ -143,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')
@ -178,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"""
@ -190,21 +206,13 @@ class CadQuerySaveScript:
def IsActive(self):
return True
# if FreeCAD.ActiveDocument is None:
# return False
# else:
# return True
def Activated(self):
import os
#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 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.")
@ -227,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:
@ -246,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])
CadQueryExecuteScript().Activated()

View File

@ -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

View File

@ -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
return

View File

@ -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"""
@ -34,6 +35,7 @@ class CadQueryWorkbench (Workbench):
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,28 +123,24 @@ 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
from Gui import Command
#Put the UI back the way we found it
FreeCAD.Console.PrintMessage("\r\nCadQuery Workbench Deactivated\r\n")
Command.CadQueryCloseScript().Activated()
#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)
FreeCADGui.addCommand('CadQueryNewScript', CadQueryNewScript())
FreeCADGui.addCommand('CadQueryOpenScript', CadQueryOpenScript())
FreeCADGui.addCommand('CadQuerySaveScript', CadQuerySaveScript())