Download src/Mod/PartDesign/WizardShaft/SegmentFunction.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 17.2 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/PartDesign/WizardShaft/SegmentFunction.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/PartDesign/WizardShaft/SegmentFunction.py
-
curl -L -o SegmentFunction.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/PartDesign/WizardShaft/SegmentFunction.py
17.2 kB
| # /****************************************************************************** | |
| # * Copyright (c) 2012 Jan Rheinländer <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): | |
| # 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): | |
| 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 = [] | |
| # Coverity has reported a problem that I_i, C_i0 or C_i1: | |
| # Bad use of null-like value (CIDs are 192609, 192611, 192616) | |
| 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) | |