Skip to content

Commit 155d80e

Browse files
Merge pull request #52 from ModiaSim/gh_extendBushingFunctionality
Gh extend bushing functionality
2 parents 3a132c9 + 87cb42f commit 155d80e

File tree

7 files changed

+226
-27
lines changed

7 files changed

+226
-27
lines changed

src/Composition/ForceElements/Bushing.jl

Lines changed: 161 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,171 @@
11

22
"""
33
force = Bushing(; obj1, obj2,
4-
nominalForce = Modia3D.ZeroVector3D,
5-
stiffness = Modia3D.ZeroVector3D,
6-
damping = Modia3D.ZeroVector3D )
4+
nominalForce = Modia3D.ZeroVector3D,
5+
springForceLaw = Modia3D.ZeroVector3D,
6+
damperForceLaw = Modia3D.ZeroVector3D,
7+
nominalTorque = Modia3D.ZeroVector3D,
8+
rotSpringForceLaw = Modia3D.ZeroVector3D,
9+
rotDamperForceLaw = Modia3D.ZeroVector3D,
10+
largeAngles = false )
711
812
Return a `force` acting as bushing between `obj1::`[`Object3D`](@ref) and
9-
`obj2::`[`Object3D`](@ref). Vectors `stiffness`, `damping` and
10-
`nominalForce` define the stiffness, damping and nominal force values in
11-
x, y and z direction of `obj1`. The orientation of `obj2` does not
12-
influence the resulting forces.
13+
`obj2::`[`Object3D`](@ref). The force directions are defined by `obj1`,
14+
i.e. the orientation of `obj2` does not influence the resulting forces.
15+
16+
# Arguments
17+
18+
- `nominalForce` defines the nominal force vector, i.e. the force that
19+
acts when spring and damper forces are zero. Positive values act in
20+
positive axis directions at `obj1` and in opposite directions at `obj2`.
21+
- `springForceLaw` defines the force law of the spring in x-, y- and
22+
z-direction:
23+
- A `Float64` value represents a linear stiffness coefficient.
24+
- An univariate `Function` is used to compute the spring force
25+
dependent of its deflection.
26+
- `damperForceLaw` defines the force law of the damper in x-, y- and
27+
z-direction:
28+
- A `Float64` value represents a linear damping coefficient.
29+
- An univariate `Function` is used to compute the damper force
30+
dependent of its deflection velocity.
31+
- `nominalTorque` defines nominal torques about alpha, beta and gamma
32+
directions.
33+
- `rotSpringForceLaw` defines the force law of the rotational spring
34+
about alpha-, beta- and gamma-direction:
35+
- A `Float64` value represents a linear damping coefficient.
36+
- An univariate `Function` is used to compute the spring force
37+
dependent of its deflection.
38+
- `rotDamperForceLaw` defines the force law of the rotational damper
39+
about alpha-, beta- and gamma-direction:
40+
- A `Float64` value represents a linear damping coefficient.
41+
- An univariate `Function` is used to compute the damper force
42+
dependent of its deflection velocity.
43+
- `largeAngles` can be used to enable large angle mode.
44+
- When disabled, small deformation angles (< 10°) are assumed. This
45+
option deals equally with rotations [alpha, beta gamma] about the
46+
axes [x, y, z] of `obj1`, but causes approximation errors for
47+
larger angles.
48+
- When enabled, the deformation angles and torque directions are
49+
calculated as [Cardan (Tait–Bryan) angles](https://en.wikipedia.org/wiki/Euler_angles#Chained_rotations_equivalence)
50+
(rotation sequence x-y-z from `obj1` to `obj2`). This option
51+
supports angles up to nearly 90°, but introduces local rotation
52+
directions [alpha, beta gamma] which differ from the axes [x, y, z]
53+
of `obj1` and increases computation effort.
1354
"""
1455
mutable struct Bushing <: Modia3D.AbstractForceElement
1556

1657
obj1::Object3D
1758
obj2::Object3D
1859

1960
nominalForce::SVector{3,Float64}
20-
stiffness::SVector{3,Float64}
21-
damping::SVector{3,Float64}
61+
springForceFunction::SVector{3,Function}
62+
damperForceFunction::SVector{3,Function}
63+
nominalTorque::SVector{3,Float64}
64+
rotSpringForceFunction::SVector{3,Function}
65+
rotDamperForceFunction::SVector{3,Function}
66+
largeAngles::Bool
2267

2368
function Bushing(; obj1::Object3D,
2469
obj2::Object3D,
2570
nominalForce::AbstractVector = Modia3D.ZeroVector3D,
26-
stiffness::AbstractVector = Modia3D.ZeroVector3D,
27-
damping::AbstractVector = Modia3D.ZeroVector3D)
71+
springForceLaw::AbstractVector = Modia3D.ZeroVector3D,
72+
damperForceLaw::AbstractVector = Modia3D.ZeroVector3D,
73+
nominalTorque::AbstractVector = Modia3D.ZeroVector3D,
74+
rotSpringForceLaw::AbstractVector = Modia3D.ZeroVector3D,
75+
rotDamperForceLaw::AbstractVector = Modia3D.ZeroVector3D,
76+
largeAngles::Bool = false )
77+
for dir in 1:3
78+
@assert(typeof(springForceLaw[dir]) == Float64 || isa(springForceLaw[dir], Function))
79+
@assert(typeof(damperForceLaw[dir]) == Float64 || isa(damperForceLaw[dir], Function))
80+
@assert(typeof(rotSpringForceLaw[dir]) == Float64 || isa(rotSpringForceLaw[dir], Function))
81+
@assert(typeof(rotDamperForceLaw[dir]) == Float64 || isa(rotDamperForceLaw[dir], Function))
82+
end
83+
84+
nomForce = Modia3D.convertAndStripUnit(SVector{3,Float64}, u"N" , nominalForce)
85+
nomTorque = Modia3D.convertAndStripUnit(SVector{3,Float64}, u"N*m", nominalTorque)
86+
springForceFunction = Vector{Function}(undef, 3)
87+
damperForceFunction = Vector{Function}(undef, 3)
88+
rotSpringForceFunction = Vector{Function}(undef, 3)
89+
rotDamperForceFunction = Vector{Function}(undef, 3)
90+
irand = rand(Int)
91+
for dir in 1:3
92+
if (typeof(springForceLaw[dir]) == Float64)
93+
stiffness = Modia3D.convertAndStripUnit(Float64, u"N/m", springForceLaw[dir])
94+
fsymb = Symbol("fc", dir, "_", irand) # todo: replace irand by force.path
95+
springForceFunction[dir] = eval(:($fsymb(pos) = $stiffness * pos))
96+
else
97+
springForceFunction[dir] = springForceLaw[dir]
98+
end
99+
if (typeof(damperForceLaw[dir]) == Float64)
100+
damping = Modia3D.convertAndStripUnit(Float64, u"N*s/m", damperForceLaw[dir])
101+
fsymb = Symbol("fd", dir, "_", irand) # todo: replace irand by force.path
102+
damperForceFunction[dir] = eval(:($fsymb(vel) = $damping * vel))
103+
else
104+
damperForceFunction[dir] = damperForceLaw[dir]
105+
end
106+
if (typeof(rotSpringForceLaw[dir]) == Float64)
107+
stiffness = Modia3D.convertAndStripUnit(Float64, u"N*m/rad", rotSpringForceLaw[dir])
108+
fsymb = Symbol("mc", dir, "_", irand) # todo: replace irand by force.path
109+
rotSpringForceFunction[dir] = eval(:($fsymb(ang) = $stiffness * ang))
110+
else
111+
rotSpringForceFunction[dir] = rotSpringForceLaw[dir]
112+
end
113+
if (typeof(rotDamperForceLaw[dir]) == Float64)
114+
damping = Modia3D.convertAndStripUnit(Float64, u"N*m*s/rad", rotDamperForceLaw[dir])
115+
fsymb = Symbol("md", dir, "_", irand) # todo: replace irand by force.path
116+
rotDamperForceFunction[dir] = eval(:($fsymb(angd) = $damping * angd))
117+
else
118+
rotDamperForceFunction[dir] = rotDamperForceLaw[dir]
119+
end
120+
end
28121

29-
nomForce = Modia3D.convertAndStripUnit(SVector{3,Float64}, u"N" , nominalForce)
30-
stiff = Modia3D.convertAndStripUnit(SVector{3,Float64}, u"N/m" , stiffness)
31-
damp = Modia3D.convertAndStripUnit(SVector{3,Float64}, u"N*s/m", damping)
122+
return new(obj1, obj2, nomForce, springForceFunction, damperForceFunction, nomTorque, rotSpringForceFunction, rotDamperForceFunction, largeAngles)
123+
end
124+
end
32125

33-
return new(obj1, obj2, nomForce, stiff, damp)
34126

127+
# Compute deformation angles from rotation matrix
128+
function anglesFromRotation(largeAngles::Bool, R12::SMatrix{3,3,Float64}, w12::SVector{3,Float64})
129+
if largeAngles
130+
sbe = clamp(R12[3,1], -1.0, 1.0)
131+
cbe = sqrt(1.0 - sbe*sbe)
132+
if (cbe > 1e-12)
133+
sal = -R12[3,2]/cbe
134+
cal = R12[3,3]/cbe
135+
al = atan(-R12[3,2], R12[3,3])
136+
be = asin(sbe)
137+
ga = atan(-R12[2,1], R12[1,1])
138+
ald = w12[1] + (sal*w12[2] - cal*w12[3])*sbe/cbe
139+
bed = cal*w12[2] + sal*w12[3]
140+
gad = (-sal*w12[2] + cal*w12[3])/cbe
141+
return (@SVector[al, be, ga], @SVector[ald, bed, gad], @SMatrix[sal sbe; cal cbe])
142+
else
143+
@error("Gimbal lock of Bushing transformation.")
144+
return (@SVector[0.0, 0.0, 0.0], @SVector[0.0, 0.0, 0.0], @SMatrix[0.0 0.0; 0.0 0.0])
145+
end
146+
else
147+
al = R12[2,3]
148+
be = R12[3,1]
149+
ga = R12[1,2]
150+
ald = w12[1]
151+
bed = w12[2]
152+
gad = w12[3]
153+
if (max(abs(al), abs(be), abs(ga)) > 0.174)
154+
@warn("Bushing angle exceeds 10 deg.")
155+
end
156+
return (@SVector[al, be, ga], @SVector[ald, bed, gad], @SMatrix[0.0 0.0; 0.0 0.0])
157+
end
158+
end
159+
160+
# Compute torque vector from force law moments
161+
function torqueFromMoments(largeAngles::Bool, moments::SVector{3,Float64}, sico::SMatrix{2,2,Float64,4})
162+
if largeAngles
163+
tx = moments[1] + sico[1,2]*moments[3]
164+
ty = sico[2,1]*moments[2] - sico[1,1]*sico[2,2]*moments[3]
165+
tz = sico[1,1]*moments[2] + sico[2,1]*sico[2,2]*moments[3]
166+
return @SVector[tx, ty, tz]
167+
else
168+
return moments
35169
end
36170
end
37171

@@ -43,12 +177,22 @@ function initializeForceElement(force::Bushing)
43177
end
44178

45179
function evaluateForceElement(force::Bushing)
180+
R12 = measFrameRotation(force.obj2; frameOrig=force.obj1)
46181
r12 = measFramePosition(force.obj2; frameOrig=force.obj1, frameCoord=force.obj1)
182+
w12 = measFrameRotVelocity(force.obj2; frameOrig=force.obj1, frameCoord=force.obj1)
47183
v12 = measFrameTransVelocity(force.obj2; frameOrig=force.obj1, frameCoord=force.obj1, frameObsrv=force.obj1)
184+
(ang, angd, sico) = anglesFromRotation(force.largeAngles, R12, w12)
48185

49-
f12 = force.stiffness .* r12 + force.damping .* v12 + force.nominalForce
186+
f12 = Vector{Float64}(undef, 3)
187+
mom = Vector{Float64}(undef, 3)
188+
for dir in 1:3
189+
f12[dir] = force.springForceFunction[dir](r12[dir]) + force.damperForceFunction[dir](v12[dir]) + force.nominalForce[dir]
190+
mom[dir] = force.rotSpringForceFunction[dir](ang[dir]) + force.rotDamperForceFunction[dir](angd[dir]) + force.nominalTorque[dir]
191+
end
192+
t12 = torqueFromMoments(force.largeAngles, SVector{3}(mom), sico)
50193

51-
applyFrameForcePair!(force.obj2, force.obj1, f12; frameCoord=force.obj1)
194+
applyFrameForcePair!(force.obj2, force.obj1, SVector{3}(f12); frameCoord=force.obj1)
195+
applyFrameTorquePair!(force.obj2, force.obj1, t12; frameCoord=force.obj1)
52196
return nothing
53197
end
54198

src/Composition/ForceElements/SpringDamperPtP.jl

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ Return a `force` acting as point-to-point parallel spring-damper between
1616
- `nominalForce` defines the nominal force, i.e. the force that acts when
1717
spring and damper forces are zero. Positive values represent tension.
1818
- `springForceLaw` defines the force law of the spring:
19-
A `Float64` value represents a linear stiffness coefficient.
20-
An univariate `Function` is used to compute the spring force dependent
21-
of its deflection. Positive values represent tension.
19+
- A `Float64` value represents a linear stiffness coefficient.
20+
- An univariate `Function` is used to compute the spring force
21+
dependent of its deflection. Positive values represent tension.
2222
- `damperForceLaw` defines the force law of the damper:
23-
A `Float64` value represents a linear damping coefficient.
24-
An univariate `Function` is used to compute the damper force dependent
25-
of its deflection velocity. Positive values represent expansion.
23+
- A `Float64` value represents a linear damping coefficient.
24+
- An univariate `Function` is used to compute the damper force
25+
dependent of its deflection velocity. Positive values represent
26+
expansion.
2627
"""
2728
mutable struct SpringDamperPtP <: Modia3D.AbstractForceElement
2829

@@ -43,12 +44,15 @@ mutable struct SpringDamperPtP <: Modia3D.AbstractForceElement
4344

4445
nomLength = Modia3D.convertAndStripUnit(Float64, u"m", nominalLength)
4546
nomForce = Modia3D.convertAndStripUnit(Float64, u"N", nominalForce)
47+
irand = rand(Int)
4648
if (typeof(springForceLaw) == Float64)
4749
stiffness = Modia3D.convertAndStripUnit(Float64, u"N/m", springForceLaw)
48-
springForceLaw = eval(:(fc(pos) = $stiffness * pos))
50+
fsymb = Symbol("fc", "_", irand) # todo: replace irand by force.path
51+
springForceLaw = eval(:($fsymb(pos) = $stiffness * pos))
4952
end
5053
if (typeof(damperForceLaw) == Float64)
5154
damping = Modia3D.convertAndStripUnit(Float64, u"N*s/m", damperForceLaw)
55+
fsymb = Symbol("fd", "_", irand) # todo: replace irand by force.path
5256
damperForceLaw = eval(:(fd(vel) = $damping * vel))
5357
end
5458

src/Composition/frameMeasurements.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Return relative RotationMatrix `R` from frame `frameOrig` into frame `frameMeas`
55
66
If `frameOrig` is omitted `R` represents the absolute rotation of `frameMeas`.
77
"""
8-
function measFrameRotation(frameMeas::Object3D; frameOrig::Object3D)
8+
function measFrameRotation(frameMeas::Object3D; frameOrig::Union{Object3D, Nothing}=nothing)
99
R_MeasOrig = copy(frameMeas.R_abs) # R_MeasOrig := R_MeasWorld
1010
if !isnothing(frameOrig)
1111
R_MeasOrig = R_MeasOrig * frameOrig.R_abs' # R_MeasOrig := R_MeasOrig * R_OrigWorld^T

test/ForceElements/BoxBushing.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
module BoxBushingSimulation
2+
3+
using ModiaLang
4+
5+
import Modia3D
6+
using Modia3D.ModiaInterface
7+
8+
const largeAngles = true
9+
if largeAngles
10+
startAngles = [0.8, 0.4, 0.2]
11+
else
12+
startAngles = [0.12, 0.06, 0.03]
13+
end
14+
fc(p) = 50.0 * p
15+
fd(v) = 2.0 * v
16+
mc(a) = 20.0 * a
17+
md(w) = 0.2 * w
18+
19+
BoxBushing = Model(
20+
Length = 0.1,
21+
Mass = 1.0,
22+
IMoment = 0.1,
23+
visualMaterial = VisualMaterial(color="IndianRed1", transparency=0.5),
24+
world = Object3D(feature=Scene(gravityField=UniformGravityField(g=9.81, n=[0, 0, -1]), nominalLength=:Length)),
25+
worldFrame = Object3D(parent=:world,
26+
feature=Visual(shape=CoordinateSystem(length=:Length))),
27+
box = Object3D(feature=Solid(shape=Box(lengthX=:Length, lengthY=:Length, lengthZ=:Length),
28+
massProperties=MassProperties(; mass=:Mass, Ixx=:IMoment, Iyy=:IMoment, Izz=:IMoment),
29+
visualMaterial=:(visualMaterial))),
30+
joint = FreeMotion(obj1=:world, obj2=:box, r=Var(init=[0.2, 0.1, 0.05]), rot=Var(init=startAngles)),
31+
force = Bushing(obj1=:world, obj2=:box,
32+
springForceLaw=[fc, 100.0, 200.0], damperForceLaw=[1.0, fd, 4.0],
33+
rotSpringForceLaw=[5.0, 10.0, mc], rotDamperForceLaw=[0.1, md, 0.4], largeAngles=largeAngles)
34+
)
35+
36+
boxBushing = @instantiateModel(buildModia3D(BoxBushing), aliasReduction=false, unitless=true)
37+
38+
stopTime = 5.0
39+
dtmax = 0.1
40+
if largeAngles
41+
requiredFinalStates = [-0.013214812736859366, 0.0005523898753356359, -0.04904666002334838, 0.0757946142513073, 0.003341416782112683, -4.954082753506823e-5, -0.056708142772310295, 0.0009597147602342456, 4.340509280339362e-5, 0.3809257838547459, 0.001097814904879805, -0.00018842580793933223]
42+
else
43+
requiredFinalStates = [-0.01321449035293881, 0.0005522522622707078, -0.04904666423238891, 0.07579225811468479, 0.0033409499586830368, -4.943166272627969e-5, -0.008049782190986742, 0.00034916853581158153, 1.460039207707777e-6, 0.04510769348383226, 0.0018233644599616554, 9.80056841368889e-6]
44+
end
45+
simulate!(boxBushing, stopTime=stopTime, dtmax=dtmax, log=true, requiredFinalStates=requiredFinalStates)
46+
47+
@usingModiaPlot
48+
plot(boxBushing, ["joint.r", "joint.v", "joint.rot", "joint.w"], figure=1)
49+
50+
end

test/ForceElements/BoxNonLinearSpringDamperPtP.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ using ModiaLang
55
import Modia3D
66
using Modia3D.ModiaInterface
77

8-
interpolatedForceLaws = false
8+
const interpolatedForceLaws = false
99
l0 = 0.1
1010
f0 = 5.0
1111
fc(x) = sign(x) * 100.0 * abs(x)^1.2

test/ForceElements/HarmonicOscillator.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Oscillator = Model(
1919
massProperties=MassProperties(; mass=:Mass, Ixx=0.1, Iyy=0.1, Izz=0.1),
2020
visualMaterial=:(visualMaterial))),
2121
joint = Prismatic(obj1=:world, obj2=:oscillator, axis=3, s=Var(init=0.0), v=Var(init=0.0)),
22-
force = Bushing(obj1=:world, obj2=:oscillator, nominalForce=:[0.0, 0.0, nomForce], stiffness=:[0.0, 0.0, Stiffness], damping=:[0.0, 0.0, Damping])
22+
force = Bushing(obj1=:world, obj2=:oscillator, nominalForce=:[0.0, 0.0, nomForce], springForceLaw=:[0.0, 0.0, Stiffness], damperForceLaw=:[0.0, 0.0, Damping])
2323
)
2424

2525
oscillator = @instantiateModel(buildModia3D(Oscillator), aliasReduction=false, unitless=true)

test/includeTests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ end
2020

2121
Test.@testset "Force Elements" begin
2222
include(joinpath("ForceElements", "HarmonicOscillator.jl"))
23+
include(joinpath("ForceElements", "BoxBushing.jl"))
2324
include(joinpath("ForceElements", "BoxSpringDamperPtP.jl"))
2425
include(joinpath("ForceElements", "BoxNonLinearSpringDamperPtP.jl"))
2526
end

0 commit comments

Comments
 (0)