#/****************************************************************************** # * Copyright (c)2012 Jan Rheinlaender * # * * # * 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)