FreeCAD/src/Mod/PartDesign/WizardShaft/SegmentFunction.py
2017-03-01 17:19:24 +01:00

391 lines
17 KiB
Python

#/******************************************************************************
# * 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 # just for debug printing to console...
import numpy as np
class SegmentFunctionSegment:
"One segment of a segment function"
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 isZero(self):
#FIXME: 1E-9 is arbitrary here. But since units are in meters, 1E-9 is a nanometer...
return abs(self.coefficient) < 1E-5
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
return self
def negated(self):
return SegmentFunctionSegment(self.start, self.variable, self.coefficient * -1.0, self.exponent)
def __mul__(self, value):
return SegmentFunctionSegment(self.start, self.variable, self.coefficient * value, self.exponent)
def integrate(self):
self.exponent = self.exponent + 1
self.coefficient = self.coefficient * 1 / self.exponent
return self
def asString(self):
return "%f * {%s - %f}^%i" % (self.coefficient, self.variable, self.start, self.exponent)
class SegmentFunction:
"Function that is defined segment-wise"
def __init__(self, name = "f(x)"):
self.variable = "x"
self.segments = []
self.name = name
def findSegment(self, xval):
"Find segment valid for the given xval"
for s in self.segments:
if s.start <= xval:
return s
return self.segments[len(self.segments)]
def isZero(self):
for s in self.segments:
if not s.isZero():
return False
return True
def negate(self):
for s in self.segments:
s.negate()
return self
def negated(self):
result = SegmentFunction()
result.variable = self.variable
for s in self.segments:
result.segments.append(s.negated())
return result
def __mul__(self, value):
result = SegmentFunction()
result.variable = self.variable
for s in self.segments:
result.segments.append(s * value)
return result
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 addSegment(self, st, coeff, exp = 0.0):
if abs(coeff) > 1E-9:
self.segments.insert(self.index(st), SegmentFunctionSegment(st, self.variable, coeff, exp))
def addSegments(self, dict):
for key in sorted(dict.iterkeys()):
self.addSegment(key, dict[key])
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()
return self
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
xresult = []
yresult = []
for xval in sorted(xvals):
if xval in starts:
# create double point at segment border
xresult.append(xval)
yresult.append(self.lowervalue(xval))
xresult.append(xval)
yresult.append(self.value(xval))
return (xresult, yresult)
def output(self):
FreeCAD.Console.PrintMessage(self.name + " = ")
for i in range(len(self.segments)):
FreeCAD.Console.PrintMessage(self.segments[i].asString())
if i < len(self.segments) - 1:
FreeCAD.Console.PrintMessage(" + ")
FreeCAD.Console.PrintMessage("\n")
class IntervalFunction:
"Function defined in intervals"
def __init__(self):
self.intervals = []
self.values = []
def addInterval(self, begin, length, value):
self.intervals.append((begin, length))
self.values.append(value)
def value(self, xval):
for i in range(len(self.intervals)):
if xval >= self.intervals[i][0] and xval < self.intervals[i][0] + self.intervals[i][1]:
return self.values[i]
return self.values[len(self.values)-1]
def lowervalue(self, xval):
return self.value(xval - 1E-8)
def index(self, xval):
lastStart = 0.0
for i in range(len(self.intervals)):
newStart = self.intervals[i][0]
if (xval >= lastStart) and (xval < newStart):
return i-1
lastStart = newStart
return len(self.intervals)-1
def interval(self, xval):
"Return interval (begin, length) for this xval"
return self.intervals[self.index(xval)]
def begin(self, xval):
return self.intervals[self.index(xval)][0]
def length(self, xval):
return self.intervals[self.index(xval)][1]
class StressFunction:
"Specialization for segment-wise display of stresses"
# The hairy thing about this is that the segments of the segfunc usually do not correspond with the intervals of the intfunc!
def __init__(self, f, i):
self.segfunc = f # The segment function for the force/moment
self.intfunc = i # The divisors, an interval function giving a specific value for each interval
name = "sigma"
def isZero(self):
return self.segfunc.isZero()
def evaluate(self, maxX, pointsX):
# Note: This usually creates a few more points than specified in pointsX
offset = (maxX - self.segfunc.segments[0].start) / (pointsX - 1)
xvals = set([self.segfunc.segments[0].start + s * offset for s in range(pointsX)])
starts = set([self.segfunc.segments[i].start for i in range(len(self.segfunc.segments))])
xvals = xvals.union(starts) # Make sure we have a point on each segment start
divs = set([self.intfunc.intervals[i][0] for i in range(len(self.intfunc.intervals))])
xvals = xvals.union(divs)
xresult = []
yresult = []
for xval in sorted(xvals):
if xval in starts:
# create double point at segment border
xresult.append(xval)
yresult.append(self.segfunc.lowervalue(xval) / self.intfunc.value(xval))
if (xval in divs):
# create double point at divisor border
xresult.append(xval)
yresult.append(self.segfunc.value(xval) / self.intfunc.lowervalue(xval))
xresult.append(xval)
yresult.append(self.segfunc.value(xval) / self.intfunc.value(xval))
return (xresult, yresult)
class TranslationFunction:
"Specialization for segment-wise display of translations"
def __init__(self, f, E, d, tangents, translations):
if f.isZero():
self.transfunc = None
return
# Note: Integration has to be segment-wise because the area moment is not constant in different segments. But this only becomes relevant
# when boundary conditions are being applied
# E I_i w_i'(x) = tangfunc + C_i0
self.tangfunc = f.integrated() # The segment function for the tangent to the bending line
self.tangfunc.name = "w'"
self.tangfunc.output()
# E I_i w_i(x) = transfunc + C_i0 x + C_i1
self.transfunc = self.tangfunc.integrated() # + C_i0 * x + C_i1 (integration constants for interval number i)
self.transfunc.name = "w"
self.transfunc.output()
self.module = E
self.intfunc = d
self.name = "w"
# Solve boundary conditions. There are two types:
# External boundary conditions, e.g. a given tangent direction or translation value at a given x-value
# Internal boundary conditions, i.e. at the segment borders the tangent direction and translation of the lines must be equal
# Note that the relevant boundaries are those of the intfunc (where the area moment of the shaft cross-section changes)
# Every interval of the transfunc has two integration constants C_i0 and C_i1 that need to be defined
# Matrix of coefficients
A = np.zeros(shape = (2 * len(self.intfunc.intervals), 2 * len(self.intfunc.intervals)))
# Vector of RHS values
b = np.zeros(shape = 2 * len(self.intfunc.intervals))
# Current row where coefficients of next equation will be added
row = 0
# First look at external boundary conditions
for bound in tangents:
xval = bound[0]
tang = bound[1]
i = self.intfunc.index(xval) # index of this segment
I_i = self.intfunc.value(xval) # Area moment of this segment
# w_i'(xval) = tang => (tangfunc(xval) + C_i0) / (E * I_i) = tang => C_i0 = tang * (E * I_i) - tangfunc(xval)
A[row][2 * i] = 1.0
b[row] = tang * E * I_i - self.tangfunc.value(xval)
row += 1
for bound in translations:
xval = bound[0]
trans = bound[1]
i = self.intfunc.index(xval) # index of this segment
I_i = self.intfunc.value(xval) # Area moment of this segment
# w_i(xval) = trans => (transfunc(xval) + C_i0 * xval + C_i1) / (E * I_i) = trans => xval / (E * I_i) * C_i0 + 1 / (E * I_i) * C_i1 = trans - transfunc(xval) / (E * I_i)
A[row][2 * i] = xval / (E * I_i)
A[row][2 * i + 1] = 1 / (E * I_i)
b[row] = trans - self.transfunc.value(xval) / (E * I_i)
row += 1
# Now look at internal boundary conditions (n intervals have n-1 common segment boundaries)
for i in range(len(self.intfunc.intervals) - 1):
x_start = self.intfunc.intervals[i][0]
x_end = x_start + self.intfunc.intervals[i][1]
I_i = self.intfunc.value(x_start) # Area moment of this segment
I_ip1 = self.intfunc.value(x_end)
# w_i'(x_end) = w_i+1'(xend) => (tangfunc(x_end) + C_i0) / (E * I_i) = (tangfunc(x_end) * C_i+1,0) / (E * I_i+1)
# => 1 / (E * I_i) C_i0 - 1 / (E * I_i+1) * C_i+1,0 = tangfunc(x_end) / (E * I_i+1) - tangfunc(x_end) / (E * I_i)
A[row][2 * i] = 1 / (E * I_i)
A[row][2 * (i+1)] = -1 / (E * I_ip1)
b[row] = self.tangfunc.value(x_end) / (E * I_ip1) - self.tangfunc.value(x_end) / (E * I_i)
row += 1
# w_i(x_end) = w_i+1(xend) => (transfunc(x_end) + C_i0 * x_end + C_i1) / (E * I_i) = (transfunc(x_end) * C_i+1,0) * x_end + C_i+1,1) / (E * I_i+1)
# => x_end / (E * I_i) C_i0 + 1 / (E * I_i) C_i1 - x_end / (E * I_i+1) * C_i+1,0 - 1 / (E * I_i+1) * C_i+1,1 = transfunc(x_end) / (E * I_i+1) - transfunc(x_end) / (E * I_i)
A[row][2 * i] = x_end / (E * I_i)
A[row][2 * i + 1] = 1 / (E * I_i)
A[row][2 * (i+1)] = -x_end / (E * I_ip1)
A[row][2 * (i+1) + 1] = -1 / (E * I_ip1)
b[row] = self.transfunc.value(x_end) / (E * I_ip1) - self.transfunc.value(x_end) / (E * I_i)
row += 1
#FreeCAD.Console.PrintMessage(A)
#FreeCAD.Console.PrintMessage(" * x = ")
#FreeCAD.Console.PrintMessage(b)
#FreeCAD.Console.PrintMessage("\n")
try:
self.boundaries = np.linalg.solve(A, b) # A * self.boundaries = b
except np.linalg.linalg.LinAlgError as e:
FreeCAD.Console.PrintMessage(e.message)
FreeCAD.Console.PrintMessage(". No solution possible.\n")
return
def isZero(self):
if self.transfunc is None:
return True
return self.transfunc.isZero()
def evaluate(self, maxX, pointsX):
# Note: This usually creates a few more points than specified in pointsX
offset = (maxX - self.transfunc.segments[0].start) / (pointsX - 1)
xvals = set([self.transfunc.segments[0].start + s * offset for s in range(pointsX)])
starts = set([self.transfunc.segments[i].start for i in range(len(self.transfunc.segments))])
xvals = xvals.union(starts) # Make sure we have a point on each segment start
divs = set([self.intfunc.intervals[i][0] for i in range(len(self.intfunc.intervals))])
xvals = xvals.union(divs)
E = self.module
xresult = []
yresult = []
for xval in sorted(xvals):
if xval in divs:
i = self.intfunc.index(xval)
(begin, length) = self.intfunc.interval(xval)
I_i = self.intfunc.value(xval)
C_i0 = self.boundaries[2 * i]
C_i1 = self.boundaries[2 * i + 1]
FreeCAD.Console.PrintMessage("Interval %u: %f to %f, I_i: %f, C_i0: %f, C_i1: %f\n" % (i, begin, length, I_i, C_i0, C_i1))
xresult.append(xval)
# w(xval) = (transfunc(xval) + C_i0 * xval + C_i1) / (E * I_i)
value = (self.transfunc.value(xval) + C_i0 * xval + C_i1) / (E * I_i)
yresult.append(value)
return (xresult, yresult)