Created shaft wizard (moved here from PartDesign branch)
This commit is contained in:
parent
0b5212e46b
commit
5c1948d8b6
|
@ -29,3 +29,28 @@ INSTALL(
|
|||
Mod/PartDesign/Scripts
|
||||
)
|
||||
|
||||
SET(WizardShaft_SRCS
|
||||
WizardShaft/__init__.py
|
||||
WizardShaft/WizardShaft.py
|
||||
WizardShaft/WizardShaftTable.py
|
||||
WizardShaft/Shaft.py
|
||||
WizardShaft/ShaftFeature.py
|
||||
WizardShaft/ShaftDiagram.py
|
||||
WizardShaft/SegmentFunction.py
|
||||
)
|
||||
SOURCE_GROUP("wizardshaft" FILES ${WizardShaft_SRCS})
|
||||
|
||||
SET(all_files ${WizardShaft_SRCS})
|
||||
|
||||
ADD_CUSTOM_TARGET(WizardShaft ALL
|
||||
SOURCES ${all_files}
|
||||
)
|
||||
|
||||
fc_copy_sources(Mod/PartDesign "${CMAKE_BINARY_DIR}/Mod/PartDesign" ${all_files})
|
||||
|
||||
INSTALL(
|
||||
FILES
|
||||
${WizardShaft_SRCS}
|
||||
DESTINATION
|
||||
Mod/PartDesign/WizardShaft
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# PartDesign gui init module
|
||||
# PartDesign gui init module
|
||||
# (c) 2003 Juergen Riegel
|
||||
#
|
||||
# Gathering all the information to start FreeCAD
|
||||
|
@ -29,11 +29,10 @@
|
|||
#* Juergen Riegel 2002 *
|
||||
#***************************************************************************/
|
||||
|
||||
|
||||
|
||||
class PartDesignWorkbench ( Workbench ):
|
||||
"PartDesign workbench object"
|
||||
Icon = """
|
||||
"PartDesign workbench object"
|
||||
from WizardShaft import WizardShaft
|
||||
Icon = """
|
||||
/* XPM */
|
||||
static char * partdesign_xpm[] = {
|
||||
"16 16 9 1",
|
||||
|
@ -62,15 +61,15 @@ class PartDesignWorkbench ( Workbench ):
|
|||
".+@@@####@.@.. ",
|
||||
" ......+++.. ",
|
||||
" ... "};
|
||||
"""
|
||||
"""
|
||||
MenuText = "Part Design"
|
||||
ToolTip = "Part Design workbench"
|
||||
|
||||
def Initialize(self):
|
||||
# load the module
|
||||
import PartDesignGui
|
||||
import PartDesign
|
||||
def GetClassName(self):
|
||||
return "PartDesignGui::Workbench"
|
||||
def Initialize(self):
|
||||
# load the module
|
||||
import PartDesignGui
|
||||
import PartDesign
|
||||
def GetClassName(self):
|
||||
return "PartDesignGui::Workbench"
|
||||
|
||||
Gui.addWorkbench(PartDesignWorkbench())
|
||||
|
|
76
src/Mod/PartDesign/WizardShaft/InitGui.py
Normal file
76
src/Mod/PartDesign/WizardShaft/InitGui.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
# PartDesign gui init module
|
||||
# (c) 2003 Juergen Riegel
|
||||
#
|
||||
# Gathering all the information to start FreeCAD
|
||||
# This is the second one of three init scripts, the third one
|
||||
# runs when the gui is up
|
||||
|
||||
#***************************************************************************
|
||||
#* (c) Juergen Riegel (juergen.riegel@web.de) 2002 *
|
||||
#* *
|
||||
#* This file is part of the FreeCAD CAx development system. *
|
||||
#* *
|
||||
#* This program is free software; you can redistribute it and/or modify *
|
||||
#* it under the terms of the GNU General Public License (GPL) *
|
||||
#* as published by the Free Software Foundation; either version 2 of *
|
||||
#* the License, or (at your option) any later version. *
|
||||
#* for detail see the LICENCE text file. *
|
||||
#* *
|
||||
#* FreeCAD is distributed in the hope that it will be useful, *
|
||||
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
#* GNU Library General Public License for more details. *
|
||||
#* *
|
||||
#* You should have received a copy of the GNU Library General Public *
|
||||
#* License along with FreeCAD; if not, write to the Free Software *
|
||||
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
#* USA *
|
||||
#* *
|
||||
#* Juergen Riegel 2002 *
|
||||
#***************************************************************************/
|
||||
|
||||
class PartDesignWorkbench ( Workbench ):
|
||||
"PartDesign workbench object"
|
||||
from WizardShaft import WizardShaft
|
||||
Icon = """
|
||||
/* XPM */
|
||||
static char * partdesign_xpm[] = {
|
||||
"16 16 9 1",
|
||||
" c None",
|
||||
". c #040006",
|
||||
"+ c #070F38",
|
||||
"@ c #002196",
|
||||
"# c #0030F3",
|
||||
"$ c #5A4D20",
|
||||
"% c #858EB2",
|
||||
"& c #DEB715",
|
||||
"* c #BFB99D",
|
||||
" & ........ ",
|
||||
"&&&$..@@@@@@+...",
|
||||
"&&&&$@#####@..@.",
|
||||
"&&&&&$......@#@.",
|
||||
"&&&&&&@@@+.###@.",
|
||||
"$&&&&&&@#@.###@.",
|
||||
".$&&&&&%#@.###@.",
|
||||
".@*&&&*%#@.###@.",
|
||||
".@#*&**%#@.###@.",
|
||||
".@#@%%%.@@.###@.",
|
||||
".@@@@@@@#@.###@.",
|
||||
".@#######@.###@.",
|
||||
".@#######@.##+. ",
|
||||
".+@@@####@.@.. ",
|
||||
" ......+++.. ",
|
||||
" ... "};
|
||||
"""
|
||||
MenuText = "Part Design"
|
||||
ToolTip = "Part Design workbench"
|
||||
|
||||
def Initialize(self):
|
||||
# load the module
|
||||
import PartDesignGui
|
||||
import PartDesign
|
||||
def GetClassName(self):
|
||||
return "PartDesignGui::Workbench"
|
||||
|
||||
Gui.addWorkbench(PartDesignWorkbench())
|
||||
|
152
src/Mod/PartDesign/WizardShaft/SegmentFunction.py
Normal file
152
src/Mod/PartDesign/WizardShaft/SegmentFunction.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
#/******************************************************************************
|
||||
# * Copyright (c)2012 Jan Rheinlaender <jrheinlaender@users.sourceforge.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This library is free software; you can redistribute it and/or *
|
||||
# * modify it under the terms of the GNU Library General Public *
|
||||
# * License as published by the Free Software Foundation; either *
|
||||
# * version 2 of the License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * This library is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this library; see the file COPYING.LIB. If not, *
|
||||
# * write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
# * Suite 330, Boston, MA 02111-1307, USA *
|
||||
# * *
|
||||
# ******************************************************************************/
|
||||
|
||||
class SegmentFunctionSegment:
|
||||
"One segment of a segment function"
|
||||
start = 0
|
||||
variable = "x"
|
||||
coefficient = 0
|
||||
exponent = 0
|
||||
|
||||
def __init__(self, st, var, coeff, exp):
|
||||
self.start = st
|
||||
self.variable = var
|
||||
self.coefficient = coeff
|
||||
self.exponent = exp
|
||||
|
||||
def hasStart(self, xval):
|
||||
"Return true if the start of this segment is xval"
|
||||
#FIXME: 1E-9 is arbitrary here. But since units are in meters, 1E-9 is a nanometer...
|
||||
return abs(self.start - xval) < 1E-9
|
||||
|
||||
def value(self, xval):
|
||||
if xval < self.start:
|
||||
return 0
|
||||
else:
|
||||
return self.coefficient * pow(xval - self.start, self.exponent)
|
||||
|
||||
def clone(self):
|
||||
return SegmentFunctionSegment(self.start, self.variable, self.coefficient, self.exponent)
|
||||
|
||||
def negate(self):
|
||||
self.coefficient *= -1
|
||||
|
||||
def integrate(self):
|
||||
self.exponent = self.exponent + 1
|
||||
self.coefficient = self.coefficient * 1 / self.exponent
|
||||
|
||||
def asString(self):
|
||||
return "%f * {%s - %f}^%i" % (self.coefficient, self.variable, self.start, self.exponent)
|
||||
|
||||
class SegmentFunction:
|
||||
"Function that is defined segment-wise"
|
||||
variable = "x"
|
||||
segments = []
|
||||
name = "f(x)"
|
||||
|
||||
def __init__(self, name = "f(x)"):
|
||||
self.variable = "x"
|
||||
self.segments = []
|
||||
self.name = name
|
||||
|
||||
def negate(self):
|
||||
for s in self.segments:
|
||||
s.negate()
|
||||
return self
|
||||
|
||||
def index(self, xval):
|
||||
"Find insert position for start value xval"
|
||||
lastStart = 0.0
|
||||
for i in range(len(self.segments)):
|
||||
newStart = self.segments[i].start
|
||||
if (xval >= lastStart) and (xval < newStart):
|
||||
return i
|
||||
lastStart = newStart
|
||||
return len(self.segments)
|
||||
|
||||
def buildFromDict(self, var, dict):
|
||||
self.variable = var
|
||||
for key in sorted(dict.iterkeys()):
|
||||
#if abs(dict[key]) > 1E-9:
|
||||
self.segments.append(SegmentFunctionSegment(key, var, dict[key], 0))
|
||||
|
||||
def addSegments(self, dict):
|
||||
for key in sorted(dict.iterkeys()):
|
||||
if abs(dict[key]) > 1E-9:
|
||||
self.segments.insert(self.index(key), SegmentFunctionSegment(key, self.variable, dict[key], 0))
|
||||
|
||||
def setMaxX(self, mx):
|
||||
self.maxX = mx
|
||||
|
||||
def value(self, xval):
|
||||
"Return the value of the function at the specified x value"
|
||||
result = 0
|
||||
for s in self.segments:
|
||||
result = result + s.value(xval)
|
||||
return result
|
||||
|
||||
def lowervalue(self, xval):
|
||||
"Return the value of the previous segment at the specified x value"
|
||||
result = 0
|
||||
for s in self.segments:
|
||||
result = result + s.value(xval - 1E-8)
|
||||
return result
|
||||
|
||||
def clone(self):
|
||||
result = SegmentFunction()
|
||||
result.variable = self.variable
|
||||
for s in self.segments:
|
||||
result.segments.append(s.clone())
|
||||
return result
|
||||
|
||||
def integrate(self):
|
||||
"Integrate all segments with respect to the variable"
|
||||
for s in self.segments:
|
||||
s.integrate()
|
||||
|
||||
def integrated(self):
|
||||
"Return a copy of self integrated with respect to the variable"
|
||||
result = self.clone()
|
||||
result.integrate()
|
||||
return result
|
||||
|
||||
def evaluate(self, maxX, pointsX):
|
||||
# Note: This usually creates a few more points than specified in pointsX
|
||||
offset = (maxX - self.segments[0].start) / (pointsX - 1)
|
||||
xvals = set([self.segments[0].start + s * offset for s in range(pointsX)])
|
||||
starts = set([self.segments[i].start for i in range(len(self.segments))])
|
||||
xvals = xvals.union(starts) # Make sure we have a point on each segment start
|
||||
result = []
|
||||
for xval in sorted(xvals):
|
||||
if xval in starts:
|
||||
result.append( (xval, self.lowervalue(xval)) ) # create double point at segment border
|
||||
result.append( (xval, self.value(xval)) )
|
||||
return result
|
||||
|
||||
def output(self):
|
||||
print self.name, " = ",
|
||||
for i in range(len(self.segments)):
|
||||
print self.segments[i].asString(),
|
||||
if i < len(self.segments) - 1:
|
||||
print " + ",
|
||||
print ""
|
||||
|
258
src/Mod/PartDesign/WizardShaft/Shaft.py
Normal file
258
src/Mod/PartDesign/WizardShaft/Shaft.py
Normal file
|
@ -0,0 +1,258 @@
|
|||
#/******************************************************************************
|
||||
# * Copyright (c)2012 Jan Rheinlaender <jrheinlaender@users.sourceforge.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This library is free software; you can redistribute it and/or *
|
||||
# * modify it under the terms of the GNU Library General Public *
|
||||
# * License as published by the Free Software Foundation; either *
|
||||
# * version 2 of the License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * This library is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this library; see the file COPYING.LIB. If not, *
|
||||
# * write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
# * Suite 330, Boston, MA 02111-1307, USA *
|
||||
# * *
|
||||
# ******************************************************************************/
|
||||
|
||||
import os, tempfile
|
||||
import FreeCADGui
|
||||
import WebGui
|
||||
from SegmentFunction import SegmentFunction
|
||||
from ShaftFeature import ShaftFeature
|
||||
from ShaftDiagram import Diagram
|
||||
|
||||
htmlHeader = """
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=utf-8">
|
||||
<TITLE></TITLE>
|
||||
<META NAME="GENERATOR" CONTENT="FreeCAD shaft design wizard">
|
||||
</HEAD>
|
||||
<BODY LANG="en-US" DIR="A4">
|
||||
<P>
|
||||
"""
|
||||
htmlFooter = """
|
||||
</P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
"""
|
||||
|
||||
class ShaftSegment:
|
||||
length = 0.0
|
||||
diameter = 0.0
|
||||
loadType = "None"
|
||||
loadSize = 0.0
|
||||
loadLocation = 0.0
|
||||
|
||||
def __init__(self, l, d):
|
||||
self.length = l
|
||||
self.diameter = d
|
||||
|
||||
class Shaft:
|
||||
"The axis of the shaft is always assumed to correspond to the X-axis"
|
||||
# List of shaft segments (each segment has a different diameter)
|
||||
segments = []
|
||||
# The sketch
|
||||
sketch = 0
|
||||
#featureWindow = None
|
||||
# The diagrams
|
||||
diagrams = {} # map of function name against Diagram object
|
||||
# Directory for diagram files
|
||||
tmpdir = ""
|
||||
# HTML browser
|
||||
haveBrowser = False
|
||||
#htmlWindow = None
|
||||
# Calculation of shaft
|
||||
Qy = 0 # force in direction of y axis
|
||||
Qz = 0 # force in direction of z axis
|
||||
Mbz = 0 # bending moment around z axis
|
||||
Mby = 0 # bending moment around y axis
|
||||
Mtz = 0 # torsion moment around z axis
|
||||
|
||||
def __init__(self, doc):
|
||||
# Create a temporary directory
|
||||
self.tmpdir = tempfile.mkdtemp()
|
||||
self.sketch = ShaftFeature(doc)
|
||||
|
||||
def getLengthTo(self, index):
|
||||
"Get the total length of all segments up to the given one"
|
||||
result = 0.0
|
||||
for i in range(index):
|
||||
result += self.segments[i].length
|
||||
return result
|
||||
|
||||
def addSegment(self, l, d):
|
||||
#print "Adding segment: ", l, " : ", d
|
||||
self.segments.append(ShaftSegment(l,d))
|
||||
self.sketch.addSegment(l, d)
|
||||
# We don't call equilibrium() here because the new segment has no loads defined yet
|
||||
|
||||
def updateSegment(self, index, length = None, diameter = None):
|
||||
oldLength = self.segments[index].length
|
||||
#print "Old length of ", index, ": ", oldLength, ", new Length: ", length, " diameter: ", diameter
|
||||
if length is not None:
|
||||
self.segments[index].length = length
|
||||
if diameter is not None:
|
||||
self.segments[index].diameter = diameter
|
||||
self.sketch.updateSegment(index, oldLength, self.segments[index].length, self.segments[index].diameter)
|
||||
self.equilibrium()
|
||||
self.updateDiagrams()
|
||||
|
||||
def updateLoad(self, index, loadType = None, loadSize = None, loadLocation = None):
|
||||
if (loadType is not None):
|
||||
self.segments[index].loadType = loadType
|
||||
if (loadSize is not None):
|
||||
self.segments[index].loadSize = loadSize
|
||||
if (loadLocation is not None):
|
||||
if (loadLocation >= 0) and (loadLocation <= self.segments[index].length):
|
||||
self.segments[index].loadLocation = loadLocation
|
||||
else:
|
||||
# TODO: Show warning
|
||||
print "Load location must be inside segment"
|
||||
|
||||
#self.feature.updateForces() graphical representation of the forces
|
||||
self.equilibrium()
|
||||
self.updateDiagrams()
|
||||
|
||||
def updateDiagrams(self):
|
||||
if (self.Qy == 0) or (self.Mbz == 0):
|
||||
return
|
||||
if self.Qy.name in self.diagrams:
|
||||
# Update diagram
|
||||
self.diagrams[self.Qy.name].update(self.Qy, self.getLengthTo(len(self.segments)) / 1000.0)
|
||||
else:
|
||||
# Create diagram
|
||||
self.diagrams[self.Qy.name] = Diagram(self.tmpdir)
|
||||
self.diagrams[self.Qy.name].create("Shear force", self.Qy, self.getLengthTo(len(self.segments)) / 1000.0, "x", "mm", 1000.0, "Q_y", "N", 1.0, 10)
|
||||
if self.Mbz.name in self.diagrams:
|
||||
# Update diagram
|
||||
self.diagrams[self.Mbz.name].update(self.Mbz, self.getLengthTo(len(self.segments)) / 1000.0)
|
||||
else:
|
||||
# Create diagram
|
||||
self.diagrams[self.Mbz.name] = Diagram(self.tmpdir)
|
||||
self.diagrams[self.Mbz.name].create("Bending moment", self.Mbz, self.getLengthTo(len(self.segments)) / 1000.0, "x", "mm", 1000.0, "M_{b,z}", "Nm", 1.0, 10)
|
||||
|
||||
if self.haveBrowser is False:
|
||||
# Create HTML file with the diagrams
|
||||
htmlFile = os.path.join(self.tmpdir, "diagrams.html")
|
||||
file = open(htmlFile, "w")
|
||||
file.write(htmlHeader)
|
||||
for fname in self.diagrams.iterkeys():
|
||||
plotFile = os.path.join(self.tmpdir, (fname + ".png"))
|
||||
file.write("<IMG SRC=\"%s\" NAME=\"%s\" ALIGN=MIDDLE WIDTH=450 HEIGHT=320 BORDER=0>\n" % (plotFile, fname))
|
||||
file.write(htmlFooter)
|
||||
file.close()
|
||||
WebGui.openBrowser(htmlFile)
|
||||
self.haveBrowser = True
|
||||
# Once the diagram is created, it will take care of refreshing the HTML view
|
||||
|
||||
def equilibrium(self):
|
||||
# Build equilibrium equations
|
||||
forces = {0.0:0.0} # dictionary of (location : outer force)
|
||||
moments = {0.0:0.0} # dictionary of (location : outer moment)
|
||||
variableNames = [""] # names of all variables
|
||||
locations = {} # dictionary of (variableName : location)
|
||||
coefficientsFy = [0] # force equilibrium equation
|
||||
coefficientsMbz = [0] # moment equilibrium equation
|
||||
|
||||
for i in range(len(self.segments)):
|
||||
lType = self.segments[i].loadType
|
||||
load = -1 # -1 means unknown (just for debug printing)
|
||||
location = -1
|
||||
|
||||
if lType == "Fixed":
|
||||
# Fixed segment
|
||||
if i == 0:
|
||||
location = 0
|
||||
variableNames.append("Fy%u" % i)
|
||||
coefficientsFy.append(1)
|
||||
coefficientsMbz.append(0)
|
||||
variableNames.append("Mz%u" % i)
|
||||
coefficientsFy.append(0)
|
||||
coefficientsMbz.append(1) # Force does not contribute because location is zero
|
||||
elif i == len(self.segments) - 1:
|
||||
location = self.getLengthTo(len(self.segments)) / 1000
|
||||
variableNames.append("Fy%u" % i)
|
||||
coefficientsFy.append(1)
|
||||
coefficientsMbz.append(location)
|
||||
variableNames.append("Mz%u" % i)
|
||||
coefficientsFy.append(0)
|
||||
coefficientsMbz.append(1)
|
||||
else:
|
||||
# TODO: Better error message
|
||||
print "Fixed constraint must be at beginning or end of shaft"
|
||||
return
|
||||
|
||||
locations["Fy%u" % i] = location
|
||||
locations["Mz%u" % i] = location
|
||||
elif lType == "Static":
|
||||
# Static load (currently force only)
|
||||
load = self.segments[i].loadSize
|
||||
location = (self.getLengthTo(i) + self.segments[i].loadLocation) / 1000 # convert to meters
|
||||
coefficientsFy[0] = coefficientsFy[0] - load
|
||||
forces[location] = load
|
||||
coefficientsMbz[0] = coefficientsMbz[0] - load * location
|
||||
moments[location] = 0
|
||||
#elif lType == "None":
|
||||
# # No loads on segment
|
||||
|
||||
print "Segment: %u, type: %s, load: %f, location: %f" % (i, lType, load, location)
|
||||
|
||||
self.printEquilibrium(variableNames, coefficientsFy)
|
||||
self.printEquilibrium(variableNames, coefficientsMbz)
|
||||
|
||||
# Build matrix and vector for linear algebra solving algorithm
|
||||
import numpy as np
|
||||
if (len(coefficientsFy) < 3) or (len(coefficientsMbz) < 3):
|
||||
return
|
||||
A = np.array([coefficientsFy[1:], coefficientsMbz[1:]])
|
||||
b = np.array([coefficientsFy[0], coefficientsMbz[0]])
|
||||
solution = np.linalg.solve(A, b)
|
||||
|
||||
# Complete dictionary of forces and moments
|
||||
if variableNames[1][0] == "F":
|
||||
forces[locations[variableNames[1]]] = solution[0]
|
||||
else:
|
||||
moments[locations[variableNames[1]]] = solution[0]
|
||||
|
||||
if variableNames[2][0] == "F":
|
||||
forces[locations[variableNames[2]]] = solution[1]
|
||||
else:
|
||||
moments[locations[variableNames[2]]] = solution[1]
|
||||
|
||||
print forces
|
||||
print moments
|
||||
self.Qy = SegmentFunction("Qy")
|
||||
self.Qy.buildFromDict("x", forces)
|
||||
self.Qy.output()
|
||||
self.Mbz = self.Qy.integrated().negate()
|
||||
self.Mbz.addSegments(moments) # takes care of boundary conditions
|
||||
self.Mbz.name = "Mbz"
|
||||
self.Mbz.output()
|
||||
|
||||
def printEquilibrium(self, var, coeff):
|
||||
# Auxiliary method for debugging purposes
|
||||
for i in range(len(var)):
|
||||
if i == 0:
|
||||
print "%f = " % coeff[i],
|
||||
else:
|
||||
print "%f * %s" % (coeff[i], var[i]),
|
||||
if (i < len(var) - 1) and (i != 0):
|
||||
print " + ",
|
||||
print ""
|
||||
|
||||
def __del__(self):
|
||||
"Remove the temporary directory"
|
||||
for fname in self.diagrams.iterkeys():
|
||||
os.remove(os.path.join(self.tmpdir, (fname + ".dat")))
|
||||
os.remove(os.path.join(self.tmpdir, (fname + ".pyxplot")))
|
||||
os.remove(os.path.join(self.tmpdir, (fname + ".png")))
|
||||
os.remove(os.path.join(self.tmpdir, "diagrams.html"))
|
||||
os.rmdir(self.tmpdir)
|
192
src/Mod/PartDesign/WizardShaft/ShaftDiagram.py
Normal file
192
src/Mod/PartDesign/WizardShaft/ShaftDiagram.py
Normal file
|
@ -0,0 +1,192 @@
|
|||
#/******************************************************************************
|
||||
# * Copyright (c)2012 Jan Rheinlaender <jrheinlaender@users.sourceforge.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This library is free software; you can redistribute it and/or *
|
||||
# * modify it under the terms of the GNU Library General Public *
|
||||
# * License as published by the Free Software Foundation; either *
|
||||
# * version 2 of the License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * This library is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this library; see the file COPYING.LIB. If not, *
|
||||
# * write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
# * Suite 330, Boston, MA 02111-1307, USA *
|
||||
# * *
|
||||
# ******************************************************************************/
|
||||
|
||||
import os, subprocess
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import FreeCAD, FreeCADGui
|
||||
|
||||
header = " ### FREECAD SHAFT WIZARD ###\n #\n"
|
||||
|
||||
class Diagram:
|
||||
function = 0 # This is assumed to be always a SegmentFunction
|
||||
fname = "y(x)"
|
||||
xlength = 0.0
|
||||
xname = "x"
|
||||
xunit = ""
|
||||
xscale = 1.0
|
||||
yname = "y"
|
||||
yunit = ""
|
||||
yscale = 1.0
|
||||
numxpoints = 10
|
||||
points = []
|
||||
# process object of pyxplot running in the background
|
||||
pyxplot = None
|
||||
# Filesystem watcher
|
||||
graphicsFile = ""
|
||||
timer = 0
|
||||
watcher = 0
|
||||
updatePending = False
|
||||
|
||||
def __init__(self, tmpdir):
|
||||
self.tmpdir = tmpdir
|
||||
# Set up watcher and timer
|
||||
self.watcher = QtCore.QFileSystemWatcher()
|
||||
self.watcher.fileChanged.connect(self.updateFinished)
|
||||
self.timer = QtCore.QTimer()
|
||||
self.timer.setInterval(100)
|
||||
self.timer.timeout.connect(self.pollFile)
|
||||
|
||||
def create(self, title, function, xlength, xname, xunit, xscale, yname, yunit, yscale, numxpoints):
|
||||
# Initialize
|
||||
self.title = title
|
||||
self.function = function
|
||||
self.xlength = xlength
|
||||
self.xname = xname
|
||||
self.xunit = xunit
|
||||
self.xscale = xscale
|
||||
self.yname = yname
|
||||
self.yunit = yunit
|
||||
self.yscale = yscale
|
||||
self.numxpoints = numxpoints
|
||||
self.graphicsFile = os.path.join(self.tmpdir, self.function.name + '.png')
|
||||
|
||||
# Calculate points
|
||||
self.points = self.function.evaluate(self.xlength, self.numxpoints)
|
||||
# Write files
|
||||
self.write()
|
||||
|
||||
def update(self, function = None, xlength = None):
|
||||
if function is not None:
|
||||
self.function = function
|
||||
if xlength is not None:
|
||||
self.xlength = xlength
|
||||
# Calculate points
|
||||
self.points = self.function.evaluate(self.xlength, self.numxpoints)
|
||||
# Write files
|
||||
self.write()
|
||||
|
||||
def write(self):
|
||||
"Write diagram files"
|
||||
# Check if pyxplot is still running in the background
|
||||
if (self.pyxplot is not None) and (self.pyxplot.poll() is None):
|
||||
# Process hasn't terminated yet, set flag to update again
|
||||
# as soon as the graphicsFile has been written by the current pyxplot process
|
||||
self.updatePending = True
|
||||
return
|
||||
|
||||
# Get max and min values
|
||||
(xmin, xmax) = self.minmaxX()
|
||||
(ymin, ymax) = self.minmaxY()
|
||||
|
||||
# Create data file
|
||||
dataFile = os.path.join(self.tmpdir, (self.function.name + ".dat"))
|
||||
file = open(dataFile, "w")
|
||||
file.write(header)
|
||||
file.write(" # File automatically exported by FreeCAD Shaft Wizard\n")
|
||||
file.write(" # This file contains xy data, filled with following columns:\n")
|
||||
file.write(" # 1: %s [%s]\n" % (self.xname, self.xunit))
|
||||
file.write(" # 2: %s [%s]\n" % (self.yname, self.yunit))
|
||||
file.write(" #\n")
|
||||
for (x, y) in self.points:
|
||||
file.write("%f %f\n" % (x * self.xscale, y * self.yscale))
|
||||
file.close()
|
||||
|
||||
# Create pyxplot file
|
||||
commandFile = os.path.join(self.tmpdir, (self.function.name + ".pyxplot"))
|
||||
file = open(commandFile, "w")
|
||||
file.write(header)
|
||||
file.write(" # File automatically exported by FreeCAD Shaft Wizard\n")
|
||||
file.write(" # This file contains a script to plot xy data.\n")
|
||||
file.write(" # To use it execute:\n")
|
||||
file.write(" #\n")
|
||||
file.write(" # pyxplot %s\n" % (commandFile))
|
||||
file.write(" #\n")
|
||||
file.write(" #################################################################\n")
|
||||
# Write general options
|
||||
file.write("set numeric display latex\n")
|
||||
file.write("set terminal png\n")
|
||||
file.write("set output '%s'\n" % (self.graphicsFile))
|
||||
file.write("set nokey\n")
|
||||
file.write("set grid\n")
|
||||
file.write("# X axis\n")
|
||||
file.write("set xlabel '$%s$ [%s]'\n" % (self.xname, self.xunit))
|
||||
file.write("set xrange [%f:%f]\n" % ((xmin * self.xscale * 1.05, xmax * self.xscale * 1.05)))
|
||||
file.write("set xtic\n")
|
||||
file.write("# Y axis\n")
|
||||
file.write("set ylabel '$%s$ [%s]'\n" % (self.yname, self.yunit))
|
||||
file.write("set yrange [%f:%f]\n" % ((ymin * self.yscale * 1.05, ymax * self.yscale * 1.05)))
|
||||
file.write("set ytic\n")
|
||||
file.write("# Line styles\n")
|
||||
file.write("set style 1 line linetype 1 linewidth 2 colour rgb (0):(0):(0)\n")
|
||||
file.write("# Title\n")
|
||||
file.write("set title '%s'" % self.title)
|
||||
# Write plot call
|
||||
file.write("# Plot\n")
|
||||
file.write("plot '%s' using 1:2 axes x1y1 with lines style 1\n" % (dataFile))
|
||||
# Close file
|
||||
file.close()
|
||||
|
||||
# Run pyxplot on the files, but don't wait for execution to finish
|
||||
# Instead, set timer to poll for the process to finish
|
||||
try:
|
||||
self.pyxplot = subprocess.Popen(["pyxplot", commandFile])
|
||||
# Poll the process to add a watcher as soon as it has created the graphics file
|
||||
if len(self.watcher.files()) == 0:
|
||||
self.timer.start()
|
||||
except OSError:
|
||||
FreeCAD.Console.PrintError("Can't execute pyxplot. Maybe it is not installed?\n")
|
||||
|
||||
self.updatePending = False
|
||||
|
||||
def pollFile(self):
|
||||
# Check if process has finished
|
||||
if self.pyxplot.poll() is None:
|
||||
return
|
||||
# Check if the graphics file has appeared and then set a watcher on it
|
||||
if not os.path.isfile(self.graphicsFile):
|
||||
return
|
||||
# We don't need the timer any more now
|
||||
self.timer.stop()
|
||||
FreeCADGui.SendMsgToActiveView('Refresh')
|
||||
self.watcher.addPath(self.graphicsFile)
|
||||
|
||||
def updateFinished(self, filePath):
|
||||
# filePath is not important because we are only watching a single file
|
||||
# FIXME: Will give a warning in the console if the active window is not the HTML page
|
||||
FreeCADGui.SendMsgToActiveView('Refresh')
|
||||
if self.updatePending is True:
|
||||
self.write()
|
||||
|
||||
def minmaxX(self):
|
||||
xpoints = []
|
||||
for (x, y) in self.points:
|
||||
xpoints.append(x)
|
||||
return (min(xpoints), max(xpoints))
|
||||
|
||||
def minmaxY(self):
|
||||
ypoints = []
|
||||
for (x, y) in self.points:
|
||||
ypoints.append(y)
|
||||
return (min(ypoints), max(ypoints))
|
||||
|
||||
|
||||
|
133
src/Mod/PartDesign/WizardShaft/ShaftFeature.py
Normal file
133
src/Mod/PartDesign/WizardShaft/ShaftFeature.py
Normal file
|
@ -0,0 +1,133 @@
|
|||
#/******************************************************************************
|
||||
# * Copyright (c)2012 Jan Rheinlaender <jrheinlaender@users.sourceforge.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This library is free software; you can redistribute it and/or *
|
||||
# * modify it under the terms of the GNU Library General Public *
|
||||
# * License as published by the Free Software Foundation; either *
|
||||
# * version 2 of the License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * This library is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this library; see the file COPYING.LIB. If not, *
|
||||
# * write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
# * Suite 330, Boston, MA 02111-1307, USA *
|
||||
# * *
|
||||
# ******************************************************************************/
|
||||
|
||||
import FreeCAD, FreeCADGui
|
||||
import Part, Sketcher
|
||||
|
||||
class ShaftFeature:
|
||||
"Creates and updates the feature of the shaft"
|
||||
Doc = 0
|
||||
App = FreeCAD
|
||||
Gui = FreeCADGui
|
||||
sketch = 0
|
||||
feature = 0
|
||||
segments = 0 # number of segments
|
||||
totalLength = 0 # total length of all segments
|
||||
lastRadius = 0 # radius of last segment (required for adding segments)
|
||||
|
||||
def __init__(self, doc):
|
||||
"Create new feature"
|
||||
self.Doc = doc
|
||||
|
||||
# TODO: Discover existing sketch and get data from it
|
||||
self.sketch = self.Doc.addObject("Sketcher::SketchObject","SketchShaft")
|
||||
self.sketch.Placement = self.App.Placement(self.App.Vector(0,0,0),self.App.Rotation(0,0,0,1))
|
||||
|
||||
def addSegment(self, length, diameter):
|
||||
"Add a segment at the end of the shaft"
|
||||
# Find constraint indices of vertical line constraint, horizontal line constraint
|
||||
# FIXME: Should have a unique id instead of indices that might change with user editing
|
||||
# 0-3 belong to the centerline
|
||||
# 4-6 to the first vertical section
|
||||
# 7-9 to the first horizontal section
|
||||
# 10-12 to the second vertical section
|
||||
# etc. etc.
|
||||
constrRadius = 4 + self.segments * 6
|
||||
constrLength = 7 + self.segments * 6
|
||||
# Find line index of vertical segment, horizontal segment, last shaft segment
|
||||
# FIXME: Should have a unique id instead of indices that might change with user editing
|
||||
segRadius = 1 + self.segments * 2
|
||||
segLength = 2 + self.segments * 2
|
||||
prevSegLength = 0 + self.segments * 2
|
||||
prevSegEnd = 3 + (self.segments - 1) * 2
|
||||
segEnd = prevSegEnd + 2
|
||||
|
||||
radius = diameter / 2
|
||||
oldLength = self.totalLength
|
||||
self.totalLength += length
|
||||
self.segments += 1
|
||||
|
||||
if oldLength == 0:
|
||||
# First segment of shaft
|
||||
# Create centerline
|
||||
self.sketch.addGeometry(Part.Line(self.App.Vector(0,0,0), self.App.Vector(self.totalLength,0,0)))
|
||||
self.sketch.addConstraint(Sketcher.Constraint('DistanceX',0, self.totalLength)) # Constraint1
|
||||
self.sketch.addConstraint(Sketcher.Constraint('PointOnObject',0,1,-1)) # Constraint2
|
||||
self.sketch.addConstraint(Sketcher.Constraint('PointOnObject',0,1,-2)) # Constraint3
|
||||
self.sketch.addConstraint(Sketcher.Constraint('Horizontal', 0)) # Constraint4
|
||||
# Create first segment
|
||||
self.sketch.addGeometry(Part.Line(self.App.Vector(0,0,0), self.App.Vector(0,radius,0)))
|
||||
self.sketch.addConstraint(Sketcher.Constraint('DistanceY',1,radius)) # Constraint5
|
||||
self.sketch.addConstraint(Sketcher.Constraint('Coincident',0,1,1,1)) # Constraint6
|
||||
self.sketch.addConstraint(Sketcher.Constraint('Vertical',1)) # Constraint7
|
||||
self.sketch.addGeometry(Part.Line(self.App.Vector(0,radius,0), self.App.Vector(length,radius,0)))
|
||||
self.sketch.addConstraint(Sketcher.Constraint('DistanceX',2,length)) # Constraint8
|
||||
self.sketch.addConstraint(Sketcher.Constraint('Coincident',2,1,1,2)) # Constraint9
|
||||
self.sketch.addConstraint(Sketcher.Constraint('Horizontal',2)) # Constraint10
|
||||
else:
|
||||
# remove line that closes the shaft
|
||||
self.sketch.delGeometry(prevSegEnd)
|
||||
# TODO: Delete the two constraints? Or will they disappear automatically?
|
||||
# Adjust length of centerline
|
||||
self.sketch.setDatum(0,self.totalLength)
|
||||
# Add segment at the end
|
||||
self.sketch.addGeometry(Part.Line(self.App.Vector(oldLength,self.lastRadius,0), self.App.Vector(oldLength,radius,0)))
|
||||
self.sketch.addConstraint(Sketcher.Constraint('DistanceY', 0, 1, segRadius, 2, radius))
|
||||
self.sketch.addConstraint(Sketcher.Constraint('Coincident',segRadius,1,prevSegLength,2))
|
||||
self.sketch.addConstraint(Sketcher.Constraint('Vertical',segRadius))
|
||||
self.sketch.addGeometry(Part.Line(self.App.Vector(oldLength,radius,0), self.App.Vector(oldLength+length,radius,0)))
|
||||
self.sketch.addConstraint(Sketcher.Constraint('DistanceX',segLength,length))
|
||||
self.sketch.addConstraint(Sketcher.Constraint('Coincident',segLength,1,segRadius,2))
|
||||
self.sketch.addConstraint(Sketcher.Constraint('Horizontal',segLength))
|
||||
|
||||
# close the sketch
|
||||
self.sketch.addGeometry(Part.Line(self.App.Vector(oldLength+length,radius,0), self.App.Vector(oldLength+length,0,0)))
|
||||
self.sketch.addConstraint(Sketcher.Constraint('Coincident',0,2,segEnd,2))
|
||||
self.sketch.addConstraint(Sketcher.Constraint('Coincident',segEnd,1,segLength,2))
|
||||
lastRadius = radius
|
||||
|
||||
if oldLength == 0:
|
||||
# create feature
|
||||
self.feature = self.Doc.addObject("PartDesign::Revolution","RevolutionShaft")
|
||||
self.feature.Sketch = self.sketch
|
||||
self.feature.ReferenceAxis = (self.sketch,['H_Axis'])
|
||||
self.feature.Angle = 360.0
|
||||
self.Doc.recompute()
|
||||
self.Gui.hide("SketchShaft")
|
||||
else:
|
||||
self.Doc.recompute()
|
||||
# FIXME: Will give a warning in the console if the active window is not the feature
|
||||
self.Gui.SendMsgToActiveView("ViewFit")
|
||||
|
||||
def updateSegment(self, segment, oldLength, length, diameter):
|
||||
constrRadius = 4 + segment * 6
|
||||
constrLength = 7 + segment * 6
|
||||
# update total length
|
||||
self.totalLength = self.totalLength - oldLength + length
|
||||
# Adjust length of centerline
|
||||
self.sketch.setDatum(0,self.totalLength)
|
||||
# Adjust segment length
|
||||
self.sketch.setDatum(constrLength, length)
|
||||
self.sketch.setDatum(constrRadius, diameter/2)
|
||||
# Update feature
|
||||
self.Doc.recompute()
|
||||
self.Gui.SendMsgToActiveView("ViewFit")
|
118
src/Mod/PartDesign/WizardShaft/WizardShaft.py
Normal file
118
src/Mod/PartDesign/WizardShaft/WizardShaft.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
#/******************************************************************************
|
||||
# * Copyright (c)2012 Jan Rheinlaender <jrheinlaender@users.sourceforge.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This library is free software; you can redistribute it and/or *
|
||||
# * modify it under the terms of the GNU Library General Public *
|
||||
# * License as published by the Free Software Foundation; either *
|
||||
# * version 2 of the License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * This library is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this library; see the file COPYING.LIB. If not, *
|
||||
# * write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
# * Suite 330, Boston, MA 02111-1307, USA *
|
||||
# * *
|
||||
# ******************************************************************************/
|
||||
|
||||
import FreeCAD, FreeCADGui
|
||||
import os
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from WizardShaftTable import WizardShaftTable
|
||||
from Shaft import Shaft
|
||||
|
||||
class WizardShaft:
|
||||
"Shaft Wizard"
|
||||
# GUI
|
||||
App = FreeCAD
|
||||
Gui = FreeCADGui
|
||||
doc = Gui.ActiveDocument
|
||||
table = 0
|
||||
# Shaft
|
||||
shaft = 0
|
||||
# Feature
|
||||
sketch = 0
|
||||
featureWindow = 0
|
||||
|
||||
def __init__(self):
|
||||
#FreeCADGui.activeWorkbench()
|
||||
#
|
||||
"Initialize the user interface"
|
||||
### Create a dock window
|
||||
wizardWidget = QtGui.QDockWidget("Shaft Wizard")
|
||||
wizardWidget.setObjectName("Shaft Wizard")
|
||||
mw = QtGui.qApp.activeWindow()
|
||||
mw.addDockWidget(QtCore.Qt.TopDockWidgetArea,wizardWidget)
|
||||
cw = mw.centralWidget() # This is a qmdiarea widget
|
||||
|
||||
# Get active document or create a new one
|
||||
# Important because when setting the default data in WizardShaftTable() the
|
||||
# updateSketch() slot will be activated and it relies on finding a valid document
|
||||
if self.doc == None:
|
||||
self.Gui.activateWorkbench("PartDesignWorkbench")
|
||||
self.doc = self.App.newDocument()
|
||||
# Grab the newly created feature window
|
||||
featureWindow = cw.subWindowList()[-1]
|
||||
else:
|
||||
featureWindow = cw.activeSubWindow()
|
||||
|
||||
# Create Shaft object
|
||||
self.shaft = Shaft(self.doc)
|
||||
#self.shaft.featureWindow = featureWindow
|
||||
|
||||
# Assign a table widget to the dock window
|
||||
self.table = WizardShaftTable(self, self.shaft)
|
||||
wizardWidget.setWidget(self.table.widget)
|
||||
# Grab the newly created HTML window
|
||||
cw = mw.centralWidget()
|
||||
htmlWindow = cw.subWindowList()[-1]
|
||||
#self.shaft.htmlWindow = htmlWindow
|
||||
|
||||
def updateEdge(self, column, start):
|
||||
print "Not implemented yet - waiting for robust references..."
|
||||
return
|
||||
if self.sketchClosed is not True:
|
||||
return
|
||||
# Create a chamfer or fillet at the start or end edge of the segment
|
||||
if start is True:
|
||||
row = rowStartEdgeType
|
||||
idx = 0
|
||||
else:
|
||||
row = rowEndEdgeType
|
||||
idx = 1
|
||||
|
||||
edgeType = self.tableWidget.item(row, column).text().toAscii()[0].upper()
|
||||
if not ((edgeType == "C") or (edgeType == "F")):
|
||||
return # neither chamfer nor fillet defined
|
||||
|
||||
if edgeType == "C":
|
||||
objName = self.doc.addObject("PartDesign::Chamfer","ChamferShaft%u" % (column * 2 + idx))
|
||||
else:
|
||||
objName = self.doc.addObject("PartDesign::Fillet","FilletShaft%u" % (column * 2 + idx))
|
||||
if objName == "":
|
||||
return
|
||||
|
||||
edgeName = "Edge%u" % self.getEdgeIndex(column, idx, edgeType)
|
||||
self.doc.getObject(objName).Base = (self.doc.getObject("RevolutionShaft"),"[%s]" % edgeName)
|
||||
# etc. etc.
|
||||
|
||||
def getEdgeIndex(self, column, startIdx):
|
||||
# FIXME: This is impossible without robust references anchored in the sketch!!!
|
||||
return
|
||||
|
||||
class WizardShaftGui:
|
||||
def Activated(self):
|
||||
WizardShaft()
|
||||
|
||||
def GetResources(self):
|
||||
IconPath = FreeCAD.ConfigGet("AppHomePath") + "Mod/PartDesign/Gui/Resources/icons/WizardShaftIcon.png"
|
||||
MenuText = 'Start the shaft design wizard'
|
||||
ToolTip = 'Start the shaft design wizard'
|
||||
return {'Pixmap' : IconPath, 'MenuText': MenuText, 'ToolTip': ToolTip}
|
||||
|
||||
FreeCADGui.addCommand('PartDesign_WizardShaft', WizardShaftGui())
|
332
src/Mod/PartDesign/WizardShaft/WizardShaftTable.py
Normal file
332
src/Mod/PartDesign/WizardShaft/WizardShaftTable.py
Normal file
|
@ -0,0 +1,332 @@
|
|||
#/******************************************************************************
|
||||
# * Copyright (c)2012 Jan Rheinlaender <jrheinlaender@users.sourceforge.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This library is free software; you can redistribute it and/or *
|
||||
# * modify it under the terms of the GNU Library General Public *
|
||||
# * License as published by the Free Software Foundation; either *
|
||||
# * version 2 of the License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * This library is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this library; see the file COPYING.LIB. If not, *
|
||||
# * write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
# * Suite 330, Boston, MA 02111-1307, USA *
|
||||
# * *
|
||||
# ******************************************************************************/
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
class WizardShaftTable:
|
||||
"The table widget that contains all the data of the shaft"
|
||||
# Dictionary to access different parameters without using a numeric row index that might change
|
||||
# as the code evolves
|
||||
rowDict = {
|
||||
"Length" : 0,
|
||||
"Diameter" : 1,
|
||||
"LoadType" : 2,
|
||||
"LoadSize" : 3,
|
||||
"LoadLocation" : 4,
|
||||
"StartEdgeType" : 5,
|
||||
"StartEdgeSize" : 6,
|
||||
"EndEdgeType" : 7,
|
||||
"EndEdgeSize" : 8
|
||||
}
|
||||
rowDictReverse = {}
|
||||
headers = ["Length [mm]",
|
||||
"Diameter [mm]",
|
||||
"Load type",
|
||||
"Load [N]",
|
||||
"Location [mm]",
|
||||
"Start edge type",
|
||||
"Start edge size",
|
||||
"End edge type",
|
||||
"End edge size"
|
||||
]
|
||||
widget = 0
|
||||
wizard = 0
|
||||
shaft = 0
|
||||
|
||||
def __init__(self, w, s):
|
||||
for key in self.rowDict.iterkeys():
|
||||
self.rowDictReverse[self.rowDict[key]] = key
|
||||
# Set parent wizard (for connecting slots)
|
||||
self.wizard = w
|
||||
self.shaft = s
|
||||
# Create table widget
|
||||
self.widget = QtGui.QTableWidget(len(self.rowDict), 0)
|
||||
self.widget.resize(QtCore.QSize(300,100))
|
||||
#self.widget.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
# Label rows and columns
|
||||
self.widget.setVerticalHeaderLabels(self.headers)
|
||||
self.widget.setHorizontalHeaderLabels(["Section 1", "Section 2"])
|
||||
#self.widget.columnMoved.connect(column, oldIndex, newIndex)
|
||||
|
||||
# Create context menu
|
||||
action = QtGui.QAction("Add column", self.widget)
|
||||
action.triggered.connect(self.slotInsertColumn)
|
||||
self.widget.addAction(action)
|
||||
self.widget.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
||||
|
||||
# Set some default data
|
||||
# Section 1
|
||||
self.addColumn()
|
||||
self.setLength(0, 40.0)
|
||||
self.setDiameter(0, 50.0)
|
||||
self.setLoadType(0, "Static")
|
||||
self.setLoadSize(0, 1000.0)
|
||||
self.setLoadLocation(0, 25.0)
|
||||
# Section 2
|
||||
self.addColumn()
|
||||
self.setLength(1, 80.0)
|
||||
self.setDiameter(1, 60.0)
|
||||
self.setLoadType(1, "Fixed")
|
||||
|
||||
def slotInsertColumn(self, point):
|
||||
# FIXME: Allow inserting columns, not just adding at the end
|
||||
# Note: need to re-name all the following column headers then
|
||||
# if (column == self.tableWidget.columnCount()):
|
||||
self.addColumn()
|
||||
# else:
|
||||
# self.insertColumn(index)
|
||||
|
||||
def addColumn(self):
|
||||
"Add a new column, fill it with widgets, and connect the signals"
|
||||
index = self.widget.columnCount()
|
||||
# Make an intelligent guess at the length/dia of the next segment
|
||||
if index > 0:
|
||||
length = self.shaft.segments[index-1].length
|
||||
diameter = self.shaft.segments[index-1].diameter
|
||||
if index > 2:
|
||||
diameter -= 5.0
|
||||
else:
|
||||
diameter += 5.0
|
||||
else:
|
||||
length = 20.0
|
||||
diameter = 10.0
|
||||
self.shaft.addSegment(length, diameter)
|
||||
|
||||
self.widget.insertColumn(index)
|
||||
self.widget.setHorizontalHeaderItem(index + 1, QtGui.QTableWidgetItem("Section %s" % (index + 1)))
|
||||
|
||||
# Length
|
||||
widget = QtGui.QDoubleSpinBox(self.widget)
|
||||
widget.setMinimum(0)
|
||||
widget.setMaximum(1E9)
|
||||
self.widget.setCellWidget(self.rowDict["Length"],index, widget)
|
||||
widget.setValue(length)
|
||||
widget.valueChanged.connect(self.slotValueChanged)
|
||||
widget.editingFinished.connect(self.slotEditingFinished)
|
||||
# Diameter
|
||||
widget = QtGui.QDoubleSpinBox(self.widget)
|
||||
widget.setMinimum(0)
|
||||
widget.setMaximum(1E9)
|
||||
self.widget.setCellWidget(self.rowDict["Diameter"],index, widget)
|
||||
widget.setValue(diameter)
|
||||
widget.valueChanged.connect(self.slotValueChanged)
|
||||
widget.editingFinished.connect(self.slotEditingFinished)
|
||||
# Load type
|
||||
widget = QtGui.QListWidget(self.widget)
|
||||
widget.insertItem(0, QtGui.QListWidgetItem("None", widget))
|
||||
widget.insertItem(1, QtGui.QListWidgetItem("Fixed", widget))
|
||||
widget.insertItem(2, QtGui.QListWidgetItem("Static", widget))
|
||||
widget.insertItem(3, QtGui.QListWidgetItem("Bearing", widget))
|
||||
widget.insertItem(4, QtGui.QListWidgetItem("Pulley", widget))
|
||||
self.widget.setCellWidget(self.rowDict["LoadType"],index, widget)
|
||||
widget.setCurrentRow(0)
|
||||
widget.currentItemChanged.connect(self.slotLoadType)
|
||||
# Load size
|
||||
widget = QtGui.QDoubleSpinBox(self.widget)
|
||||
widget.setMinimum(-1E9)
|
||||
widget.setMaximum(1E9)
|
||||
self.widget.setCellWidget(self.rowDict["LoadSize"],index, widget)
|
||||
widget.setValue(0)
|
||||
widget.valueChanged.connect(self.slotValueChanged)
|
||||
widget.editingFinished.connect(self.slotEditingFinished)
|
||||
# Load location
|
||||
widget = QtGui.QDoubleSpinBox(self.widget)
|
||||
widget.setMinimum(0)
|
||||
widget.setMaximum(1E9)
|
||||
self.widget.setCellWidget(self.rowDict["LoadLocation"],index, widget)
|
||||
widget.setValue(0)
|
||||
widget.valueChanged.connect(self.slotValueChanged)
|
||||
widget.editingFinished.connect(self.slotEditingFinished)
|
||||
# Start edge type
|
||||
widget = QtGui.QListWidget(self.widget)
|
||||
widget.insertItem(0, QtGui.QListWidgetItem("None", widget))
|
||||
widget.insertItem(1, QtGui.QListWidgetItem("Chamfer", widget))
|
||||
widget.insertItem(2, QtGui.QListWidgetItem("Fillet", widget))
|
||||
self.widget.setCellWidget(self.rowDict["StartEdgeType"],index, widget)
|
||||
widget.setCurrentRow(0)
|
||||
widget.currentItemChanged.connect(self.slotStartEdgeType)
|
||||
# Start edge size
|
||||
widget = QtGui.QDoubleSpinBox(self.widget)
|
||||
widget.setMinimum(0)
|
||||
widget.setMaximum(1E9)
|
||||
self.widget.setCellWidget(self.rowDict["StartEdgeSize"],index, widget)
|
||||
widget.setValue(1)
|
||||
widget.valueChanged.connect(self.slotValueChanged)
|
||||
widget.editingFinished.connect(self.slotEditingFinished)
|
||||
# End edge type
|
||||
widget = QtGui.QListWidget(self.widget)
|
||||
widget.insertItem(0, QtGui.QListWidgetItem("None", widget))
|
||||
widget.insertItem(1, QtGui.QListWidgetItem("Chamfer", widget))
|
||||
widget.insertItem(2, QtGui.QListWidgetItem("Fillet", widget))
|
||||
self.widget.setCellWidget(self.rowDict["EndEdgeType"],index, widget)
|
||||
widget.setCurrentRow(0)
|
||||
widget.currentItemChanged.connect(self.slotEndEdgeType)
|
||||
# End edge size
|
||||
widget = QtGui.QDoubleSpinBox(self.widget)
|
||||
widget.setMinimum(0)
|
||||
widget.setMaximum(1E9)
|
||||
self.widget.setCellWidget(self.rowDict["EndEdgeSize"],index, widget)
|
||||
widget.setValue(1)
|
||||
widget.valueChanged.connect(self.slotValueChanged)
|
||||
widget.editingFinished.connect(self.slotEditingFinished)
|
||||
|
||||
def slotValueChanged(self, value):
|
||||
(self.editedRow, self.editedColumn) = self.getFocusedCell()
|
||||
self.editedValue = value
|
||||
|
||||
def slotEditingFinished(self):
|
||||
rowName = self.rowDictReverse[self.editedRow]
|
||||
if rowName is None:
|
||||
return
|
||||
if rowName == "Length":
|
||||
self.shaft.updateSegment(self.editedColumn, length = self.getDoubleValue(rowName, self.editedColumn))
|
||||
elif rowName == "Diameter":
|
||||
self.shaft.updateSegment(self.editedColumn, diameter = self.getDoubleValue(rowName, self.editedColumn))
|
||||
elif rowName == "LoadType":
|
||||
self.shaft.updateLoad(self.editedColumn, loadType = self.getListValue(rowName, self.editedColumn))
|
||||
elif rowName == "LoadSize":
|
||||
self.shaft.updateLoad(self.editedColumn, loadSize = self.getDoubleValue(rowName, self.editedColumn))
|
||||
elif rowName == "LoadLocation":
|
||||
self.shaft.updateLoad(self.editedColumn, loadLocation = self.getDoubleValue(rowName, self.editedColumn))
|
||||
elif rowName == "StartEdgeType":
|
||||
pass
|
||||
elif rowName == "StartEdgeSize":
|
||||
pass
|
||||
elif rowName == "EndEdgeType":
|
||||
pass
|
||||
elif rowName == "EndEdgeSize":
|
||||
pass
|
||||
|
||||
def setLength(self, column, l):
|
||||
self.setDoubleValue("Length", column, l)
|
||||
self.shaft.updateSegment(column, length = l)
|
||||
|
||||
def getLength(self, column):
|
||||
return self.getDoubleValue("Length", column)
|
||||
|
||||
def setDiameter(self, column, d):
|
||||
self.setDoubleValue("Diameter", column, d)
|
||||
self.shaft.updateSegment(column, diameter = d)
|
||||
|
||||
def getDiameter(self, column):
|
||||
return self.getDoubleValue("Diameter", column)
|
||||
|
||||
def slotLoadType(self, new, old):
|
||||
if new.text() != "Fixed":
|
||||
if (self.getLoadSize is None) or (self.getLoadLocation is None):
|
||||
return
|
||||
self.shaft.updateLoad(self.getFocusedColumn(), loadType = new.text())
|
||||
|
||||
def setLoadType(self, column, t):
|
||||
self.setListValue("LoadType", column, t)
|
||||
self.shaft.updateLoad(column, loadType = t)
|
||||
|
||||
def getLoadType(self, column):
|
||||
return self.getListValue("LoadType", column)
|
||||
|
||||
def setLoadSize(self, column, s):
|
||||
self.setDoubleValue("LoadSize", column, s)
|
||||
self.shaft.updateLoad(column, loadSize = s)
|
||||
|
||||
def getLoadSize(self, column):
|
||||
return self.getDoubleValue("LoadSize", column)
|
||||
|
||||
def setLoadLocation(self, column, l):
|
||||
self.setDoubleValue("LoadLocation", column, l)
|
||||
self.shaft.updateLoad(column, loadLocation = l)
|
||||
|
||||
def getLoadLocation(self, column):
|
||||
return self.getDoubleValue("LoadLocation", column)
|
||||
|
||||
def slotStartEdgeType(self, old, new):
|
||||
pass
|
||||
|
||||
def setStartEdgeType(self, column, t):
|
||||
self.setListValue("StartEdgeType", column, t)
|
||||
|
||||
def getStartEdgeType(self, column):
|
||||
return self.getListValue("StartEdgeType", column)
|
||||
|
||||
def setStartEdgeSize(self, column, s):
|
||||
self.setDoubleValue("StartEdgeSize", column, s)
|
||||
|
||||
def getStartEdgeSize(self, column):
|
||||
return self.getDoubleValue("StartEdgeSize", column)
|
||||
|
||||
def slotEndEdgeType(self, old, new):
|
||||
pass
|
||||
|
||||
def setEndEdgeType(self, column, t):
|
||||
self.setListValue("EndEdgeType", column, t)
|
||||
|
||||
def getEndEdgeType(self, column):
|
||||
return self.getListValue("EndEdgeType", column)
|
||||
|
||||
def setEndEdgeSize(self, column, s):
|
||||
self.setDoubleValue("EndEdgeSize", column, s)
|
||||
|
||||
def getEndEdgeSize(self, column):
|
||||
return self.getDoubleValue("EndEdgeSize", column)
|
||||
|
||||
def setDoubleValue(self, row, column, v):
|
||||
widget = self.widget.cellWidget(self.rowDict[row], column)
|
||||
# Avoid triggering a signal, because the slot will work on the focused cell, not the current one
|
||||
widget.blockSignals(True)
|
||||
widget.setValue(v)
|
||||
widget.blockSignals(False)
|
||||
|
||||
def getDoubleValue(self, row, column):
|
||||
widget = self.widget.cellWidget(self.rowDict[row], column)
|
||||
if widget is not None:
|
||||
return widget.value()
|
||||
else:
|
||||
return None
|
||||
|
||||
def setListValue(self, row, column, v):
|
||||
widget = self.widget.cellWidget(self.rowDict[row], column)
|
||||
widget.blockSignals(True)
|
||||
widget.setCurrentItem(widget.findItems(v, QtCore.Qt.MatchExactly)[0])
|
||||
widget.blockSignals(False)
|
||||
|
||||
def getListValue(self, row, column):
|
||||
widget = self.widget.cellWidget(self.rowDict[row], column)
|
||||
if widget is not None:
|
||||
return widget.currentItem().text().toAscii()[0].upper()
|
||||
else:
|
||||
return None
|
||||
|
||||
def getFocusedColumn(self):
|
||||
# Make the focused cell also the current one in the table
|
||||
widget = QtGui.QApplication.focusWidget()
|
||||
if widget is not None:
|
||||
index = self.widget.indexAt(widget.pos())
|
||||
self.widget.setCurrentCell(index.row(), index.column())
|
||||
return self.widget.currentColumn()
|
||||
|
||||
def getFocusedCell(self):
|
||||
# Make the focused cell also the current one in the table
|
||||
widget = QtGui.QApplication.focusWidget()
|
||||
if widget is not None:
|
||||
index = self.widget.indexAt(widget.pos())
|
||||
self.widget.setCurrentCell(index.row(), index.column())
|
||||
return (self.widget.currentRow(), self.widget.currentColumn())
|
27
src/Mod/PartDesign/WizardShaft/__init__.py
Normal file
27
src/Mod/PartDesign/WizardShaft/__init__.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
"""
|
||||
Shaft Wizard
|
||||
"""
|
||||
|
||||
#/******************************************************************************
|
||||
# * Copyright (c)2012 Jan Rheinlaender <jrheinlaender@users.sourceforge.net> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This library is free software; you can redistribute it and/or *
|
||||
# * modify it under the terms of the GNU Library General Public *
|
||||
# * License as published by the Free Software Foundation; either *
|
||||
# * version 2 of the License, or (at your option) any later version. *
|
||||
# * *
|
||||
# * This library is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this library; see the file COPYING.LIB. If not, *
|
||||
# * write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||
# * Suite 330, Boston, MA 02111-1307, USA *
|
||||
# * *
|
||||
# ******************************************************************************/
|
||||
|
||||
# Empty file to treat the folder as a package
|
Loading…
Reference in New Issue
Block a user