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 # 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): 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, di): #print "Adding segment: ", l, " : ", d self.segments.append(ShaftSegment(l,d,di)) self.sketch.addSegment(l, d, di) # We don't call equilibrium() here because the new segment has no loads defined yet def updateSegment(self, index, length = None, diameter = None, innerdiameter = 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 if innerdiameter is not None: self.segments[index].innerdiameter = innerdiameter self.sketch.updateSegment(index, oldLength, self.segments[index].length, self.segments[index].diameter, self.segments[index].innerdiameter) 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 FreeCAD.Console.PrintMessage("Load location must be inside segment\n") #self.feature.updateForces() graphical representation of the forces self.equilibrium() self.updateDiagrams() def updateEdge(self, column, start): App.Console.PrintMessage("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 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.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.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) 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 FreeCAD.Console.PrintMessage("Fixed constraint must be at beginning or end of shaft\n") 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 FreeCAD.Console.PrintMessage("Segment: %u, type: %s, load: %f, location: %f\n" % (i, lType, load, location)) self.printEquilibrium(variableNames, coefficientsFy) self.printEquilibrium(variableNames, coefficientsMbz) # Build matrix and vector for linear algebra solving algorithm try: import numpy as np except ImportError: FreeCAD.Console.PrintMessage("numpy is not installed on your system\n") raise ImportError("numpy not installed") 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] FreeCAD.Console.PrintMessage(forces) FreeCAD.Console.PrintMessage(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: FreeCAD.Console.PrintMessage("%f = " % coeff[i]) else: FreeCAD.Console.PrintMessage("%f * %s" % (coeff[i], var[i])) if (i < len(var) - 1) and (i != 0): FreeCAD.Console.PrintMessage(" + ") FreeCAD.Console.PrintMessage("\n")
def equilibrium(self): # Build equilibrium equations try: import numpy as np except ImportError: FreeCAD.Console.PrintMessage( "numpy is not installed on your system\n") raise ImportError("numpy not installed") # Initialization of structures. All three axes are handled separately so everything is 3-fold # dictionaries of (location : outer force/moment) with reverse sign, which means that the segment functions for the section force and section moment # created from them will have signs as by the convention in # http://www.umwelt-campus.de/ucb/fileadmin/users/90_t.preussler/dokumente/Skripte/TEMECH/TMI/Ebene_Balkenstatik.pdf (page 10) # (see also example on page 19) forces = [{0.0: 0.0}, {0.0: 0.0}, {0.0: 0.0}] moments = [{0.0: 0.0}, {0.0: 0.0}, {0.0: 0.0}] # Boundary conditions for shaft bending line tangents = [[], [], []] # Tangents to shaft bending line translations = [[], [], []] # Shaft displacement # Variable names, e.g. Fx, Mz. Because the system must be exactly determined, not more than two independent variables for each # force/moment per axis are possible (if there are more no solution is calculated) variableNames = [[""], [""], [""]] # # dictionary of (variableName : location) giving the x-coordinate at which the force/moment represented by the variable acts on the shaft locations = {} # Coefficients of the equilibrium equations in the form a = b * F1 + c * F2 and d = e * M1 + f * M2 # LHS (variables a1, a2, a3, d3) initialized to zero coefficientsF = [[0], [0], [0]] coefficientsM = [[0], [0], [0]] for i in range(len(self.segments)): cType = self.segments[i].constraintType constraint = self.segments[i].constraint if cType == "Fixed": # Fixed segment if i == 0: # At beginning of shaft location = 0 elif i == len(self.segments) - 1: # At end of shaft location = self.getLengthTo(len( self.segments)) / 1000.0 # convert to meters else: # TODO: Better error message FreeCAD.Console.PrintMessage( "Fixed constraint must be at beginning or end of shaft\n" ) return for ax in range(3): # Create a new reaction force variableNames[ax].append("%s%u" % (self.Fstr[ax], i)) coefficientsF[ax].append(1) # Register location of reaction force locations["%s%u" % (self.Fstr[ax], i)] = location # Boundary conditions for the translations tangents[ax].append((location, 0.0)) translations[ax].append((location, 0.0)) coefficientsM[0].append( 0) # Reaction force contributes no moment around x axis coefficientsM[1].append( location ) # Reaction force contributes a positive moment around z axis coefficientsM[2].append( -location ) # Reaction force contributes a negative moment around y axis for ax in range(3): # Create a new reaction moment variableNames[ax].append("%s%u" % (self.Mstr[ax], i)) coefficientsF[ax].append(0) coefficientsM[ax].append(1) locations["%s%u" % (self.Mstr[ax], i)] = location elif cType == "Force": # Static force (currently force on midpoint of segment only) force = constraint.DirectionVector.multiply(constraint.Force) # TODO: Extract value of the location from geometry location = (self.getLengthTo(i) + self.segments[i].length / 2.0) / 1000.0 # The force itself for ax in range(3): if abs(force[ax]) > 0.0: coefficientsF[ax][0] = coefficientsF[ax][0] - force[ ax] # neg. because this coefficient is on the LHS of the equilibrium equation self.addTo( forces[ax], location, -force[ax] ) # neg. to fulfill the convention mentioned above # Moments created by the force (by definition no moment is created by the force in x-direction) if abs(force[1]) > 0.0: coefficientsM[1][0] = coefficientsM[1][ 0] - force[1] * location # moment around z-axis self.addTo(moments[1], location, 0) if abs(force[2]) > 0.0: coefficientsM[2][0] = coefficientsM[2][ 0] + force[2] * location # moment around y-axis self.addTo(moments[2], location, 0) # No outer moment acts here! elif cType == "Bearing": location = constraint.BasePoint.x / 1000.0 # TODO: This assumes that the shaft feature starts with the first segment at (0,0,0) and its axis corresponds to the x-axis # Bearing reaction forces. TODO: the bearing is assumed to not induce any reaction moments start = (0 if constraint.AxialFree == False else 1) for ax in range(start, 3): variableNames[ax].append("%s%u" % (self.Fstr[ax], i)) coefficientsF[ax].append(1) locations["%s%u" % (self.Fstr[ax], i)] = location # Boundary condition translations[ax].append((location, 0.0)) if constraint.AxialFree == False: coefficientsM[0].append( 0 ) # Reaction force contributes no moment around x axis coefficientsM[1].append( location ) # Reaction force contributes a positive moment around z axis coefficientsM[2].append( -location ) # Reaction force contributes a negative moment around y axis elif cType == "Gear": force = constraint.DirectionVector.multiply(constraint.Force) location = constraint.BasePoint.x / 1000.0 lever = [ 0, constraint.Diameter / 2.0 / 1000.0 * math.sin(constraint.ForceAngle / 180.0 * math.pi), constraint.Diameter / 2.0 / 1000.0 * math.cos(constraint.ForceAngle / 180.0 * math.pi) ] # Effect of the gear force for ax in range(3): if abs(force[ax]) > 0.0: # Effect of the force coefficientsF[ax][0] = coefficientsF[ax][0] - force[ax] self.addTo(forces[ax], location, -force[ax]) # Moments created by the force (by definition no moment is created by the force in x-direction) if abs(force[1]) > 0.0: coefficientsM[1][0] = coefficientsM[1][ 0] - force[1] * location # moment around z-axis self.addTo(moments[1], location, 0) if abs(force[2]) > 0.0: coefficientsM[2][0] = coefficientsM[2][ 0] + force[2] * location # moment around y-axis self.addTo(moments[2], location, 0) # No outer moment acts here! # Moments created by the force and lever if abs(force[0]) > 0.0: momenty = force[0] * lever[2] momentz = force[0] * lever[1] coefficientsM[1][0] = coefficientsM[1][ 0] + momentz # moment around z-axis self.addTo(moments[1], location, momentz) coefficientsM[2][0] = coefficientsM[2][ 0] - momenty # moment around y-axis self.addTo(moments[2], location, -momenty) if abs(force[1]) > 0.0: moment = force[1] * lever[2] coefficientsM[0][0] = coefficientsM[0][0] + moment self.addTo(moments[0], location, moment) if abs(force[2]) > 0.0: moment = force[2] * lever[1] coefficientsM[0][0] = coefficientsM[0][0] - moment self.addTo(moments[0], location, -moment) elif cType == "Pulley": forceAngle1 = (constraint.ForceAngle + constraint.BeltAngle + 90.0) / 180.0 * math.pi forceAngle2 = (constraint.ForceAngle - constraint.BeltAngle + 90.0) / 180.0 * math.pi #FreeCAD.Console.PrintMessage("BeltForce1: %f, BeltForce2: %f\n" % (constraint.BeltForce1, constraint.BeltForce2)) #FreeCAD.Console.PrintMessage("Angle1: %f, Angle2: %f\n" % (forceAngle1, forceAngle2)) force = [ 0, -constraint.BeltForce1 * math.sin(forceAngle1) - constraint.BeltForce2 * math.sin(forceAngle2), constraint.BeltForce1 * math.cos(forceAngle1) + constraint.BeltForce2 * math.cos(forceAngle2) ] location = constraint.BasePoint.x / 1000.0 # Effect of the pulley forces for ax in range(3): if abs(force[ax]) > 0.0: # Effect of the force coefficientsF[ax][0] = coefficientsF[ax][0] - force[ax] self.addTo(forces[ax], location, -force[ax]) # Moments created by the force (by definition no moment is created by the force in x-direction) if abs(force[1]) > 0.0: coefficientsM[1][0] = coefficientsM[1][ 0] - force[1] * location # moment around z-axis self.addTo(moments[1], location, 0) if abs(force[2]) > 0.0: coefficientsM[2][0] = coefficientsM[2][ 0] + force[2] * location # moment around y-axis self.addTo(moments[2], location, 0) # No outer moment acts here! # Torque moment = constraint.Force * (1 if constraint.IsDriven is True else -1) coefficientsM[0][0] = coefficientsM[0][0] + moment self.addTo(moments[0], location, moment) areas = [None, None, None] areamoments = [None, None, None] bendingmoments = [None, None, None] torquemoments = [None, None, None] for ax in range(3): FreeCAD.Console.PrintMessage("Axis: %u\n" % ax) self.printEquilibrium(variableNames[ax], coefficientsF[ax]) self.printEquilibrium(variableNames[ax], coefficientsM[ax]) if len(coefficientsF[ax]) <= 1: # Note: coefficientsF and coefficientsM always have the same length FreeCAD.Console.PrintMessage( "Matrix is singular, no solution possible\n") self.parent.updateButtons(ax, False) continue # Handle special cases. Note that the code above should ensure that coefficientsF and coefficientsM always have same length solution = [None, None] if len(coefficientsF[ax]) == 2: if coefficientsF[ax][1] != 0.0 and coefficientsF[ax][0] != 0.0: solution[0] = coefficientsF[ax][0] / coefficientsF[ax][1] if coefficientsM[ax][1] != 0.0 and coefficientsM[ax][0] != 0.0: solution[1] = coefficientsM[ax][0] / coefficientsM[ax][1] if abs(solution[0] - solution[1]) < 1E9: FreeCAD.Console.PrintMessage( "System is statically undetermined. No solution possible.\n" ) self.parent.updateButtons(ax, False) continue else: # Build matrix and vector for linear algebra solving algorithm # TODO: This could easily be done manually... there are only 2 variables and 6 coefficients A = np.array([coefficientsF[ax][1:], coefficientsM[ax][1:]]) b = np.array([coefficientsF[ax][0], coefficientsM[ax][0]]) try: solution = np.linalg.solve(A, b) # A * solution = b except np.linalg.linalg.LinAlgError, e: FreeCAD.Console.PrintMessage(e.message) FreeCAD.Console.PrintMessage(". No solution possible.\n") self.parent.updateButtons(ax, False) continue # Complete dictionary of forces and moments with the two reaction forces that were calculated for i in range(2): if solution[i] is None: continue FreeCAD.Console.PrintMessage( "Reaction force/moment: %s = %f\n" % (variableNames[ax][i + 1], solution[i])) if variableNames[ax][i + 1][0] == "M": moments[ax][locations[variableNames[ax][i + 1]]] = -solution[i] else: forces[ax][locations[variableNames[ax][i + 1]]] = -solution[i] FreeCAD.Console.PrintMessage(forces[ax]) FreeCAD.Console.PrintMessage("\n") FreeCAD.Console.PrintMessage(moments[ax]) FreeCAD.Console.PrintMessage("\n") # Forces self.F[ax] = SegmentFunction(self.Fstr[ax]) self.F[ax].buildFromDict("x", forces[ax]) self.parent.updateButton(1, ax, not self.F[ax].isZero()) self.F[ax].output() # Moments if ax == 0: self.M[0] = SegmentFunction(self.Mstr[0]) self.M[0].buildFromDict("x", moments[0]) elif ax == 1: self.M[1] = self.F[1].integrated().negate() self.M[1].name = self.Mstr[1] self.M[1].addSegments( moments[1]) # takes care of boundary conditions elif ax == 2: self.M[2] = self.F[2].integrated() self.M[2].name = self.Mstr[2] self.M[2].addSegments( moments[2]) # takes care of boundary conditions self.parent.updateButton(2, ax, not self.M[ax].isZero()) self.M[ax].output() # Areas and area moments location = 0.0 areas[ax] = IntervalFunction() # A [m²] areamoments[ax] = IntervalFunction() # I [m⁴] bendingmoments[ax] = IntervalFunction() # W_b [m³] torquemoments[ax] = IntervalFunction() # W_t [m³] for i in range(len(self.segments)): od = self.segments[i].diameter / 1000.0 id = self.segments[i].innerdiameter / 1000.0 length = self.segments[i].length / 1000.0 areas[ax].addInterval( location, length, math.pi / 4.0 * (math.pow(od, 2.0) - math.pow(id, 2.0))) areamoment = math.pi / 64.0 * (math.pow(od, 4.0) - math.pow(id, 4.0)) areamoments[ax].addInterval(location, length, areamoment) bendingmoments[ax].addInterval(location, length, areamoment / (od / 2.0)) torquemoments[ax].addInterval(location, length, 2 * (areamoment / (od / 2.0))) location += length # Bending line if ax > 0: if len(tangents[ax]) + len(translations[ax]) == 2: # TODO: Get Young's module from material type instead of using 210000 N/mm² = 2.1E12 N/m² self.w[ax] = TranslationFunction(self.M[ax].negated(), 2.1E12, areamoments[ax], tangents[ax], translations[ax]) self.w[ax].name = self.wstr[ax] self.parent.updateButton(3, ax, not self.w[ax].isZero()) else: self.parent.updateButton(3, ax, False) # Normal/shear stresses and torque/bending stresses self.sigmaN[ax] = StressFunction(self.F[ax], areas[ax]) self.sigmaN[ax].name = self.sigmaNstr[ax] self.parent.updateButton(4, ax, not self.sigmaN[ax].isZero()) if ax == 0: self.sigmaB[ax] = StressFunction(self.M[ax], torquemoments[ax]) else: self.sigmaB[ax] = StressFunction(self.M[ax], bendingmoments[ax]) self.sigmaB[ax].name = self.sigmaBstr[ax] self.parent.updateButton(5, ax, not self.sigmaB[ax].isZero())
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 FreeCAD.Console.PrintMessage("Fixed constraint must be at beginning or end of shaft\n") 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 FreeCAD.Console.PrintMessage("Segment: %u, type: %s, load: %f, location: %f\n" % (i, lType, load, location)) self.printEquilibrium(variableNames, coefficientsFy) self.printEquilibrium(variableNames, coefficientsMbz) # Build matrix and vector for linear algebra solving algorithm try: import numpy as np except ImportError: FreeCAD.Console.PrintMessage("numpy is not installed on your system\n") raise ImportError("numpy not installed") 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] FreeCAD.Console.PrintMessage(forces) FreeCAD.Console.PrintMessage(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()