Skip to content
LIBRARY
Cable.md

Cable

Cable / rope of total length l and mass m, modeled as n rigid segments connected by 3-DOF Spherical joints, matching the Multibody.jl Rope.

When end_joints = true (default), the topology uses n + 1 joints: frame_a → S₁ → link₁ → S₂ → … → Sₙ → linkₙ → Sₙ₊₁ → frame_b

When end_joints = false, only the n − 1 internal joints are kept and both ends attach rigidly to the adjacent frame: frame_a → link₁ → S₁ → link₂ → … → Sₙ₋₁ → linkₙ → frame_b

When iscut = true, the last segment is replaced by a SphericalSpherical rod that imposes a single distance constraint and carries only axial force. Use this whenever the cable closes a kinematic loop (both ends attached to frames that are already rooted, e.g. a mooring line between the world and a free-floating body): the rod terminates the chain without over-constraining the orientation, and the two spherical-like rod ends replace the joints that would otherwise sit on either side of the last link. Requires n >= 2. The resulting topologies are frame_a → S₁ → link₁ → … → Sₙ₋₁ → linkₙ₋₁ → rod → frame_b (end_joints = true) frame_a → link₁ → S₁ → … → Sₙ₋₂ → linkₙ₋₁ → rod → frame_b (end_joints = false)

When drag = true, each link receives a linear drag force proportional to the velocity of its center of mass, with coefficient d_drag per link. With external_field = true the link velocities are evaluated relative to a uniform external velocity field connected at the field port: the cable exposes its midpoint position at field.r and the connected side supplies the field velocity at field.v (e.g. a water current or wave field; the connection is made outside the cable). The loop-closing rod of iscut cables carries no drag.

Each Spherical joint uses quaternion state (Euler-angle state hits a structural-simplify cycle when chained — SciML/ModelingToolkit.jl#4608) and transmits no torque. The cable starts straight (along dir); seed an initial bend by setting a joint's non-unit quaternion Q_hat as an initial condition, e.g. a 0.9 rad bend about x on the first joint: cable.joints[1].Q_hat = [cos(0.45), sin(0.45), 0, 0].

This component extends from PartialTwoFrames This component extends from Renderable

Usage

MultibodyComponents.Cable(render=true, color=[0.5, 0.4, 0.4, 1], specular_coefficient=1.5, d_drag=0, guess_axis=[0, 0, 1], guess_turn=0, guess_half_first=-guess_turn / 4, guess_half_rest=guess_turn / (2 * ifelse(n > 1, n - 1, 1)), q_guess_first=[cos(guess_half_first), sin(guess_half_first) * guess_axis[1], sin(guess_half_first) * guess_axis[2], sin(guess_half_first) * guess_axis[3]], q_guess_rest=[cos(guess_half_rest), sin(guess_half_rest) * guess_axis[1], sin(guess_half_rest) * guess_axis[2], sin(guess_half_rest) * guess_axis[3]], l=1, m=1, dir=[0, -1, 0], radius=0.05, r_segment=(l / n) * (dir / norm_(dir)), m_segment=m / n)

Parameters:

NameDescriptionUnitsDefault value
initialize_orientationSpecify identity initial conditions for the joint orientations. Set to false when the cable closes a kinematic loop, where a straight cable is not a consistent initial condition, to let initialization solve for the joint orientations.true
orientation_stateOrientation...uaternion()
nNumber of rigid segments5
end_jointsInclude Spherical joints at both ends (frame_a and frame_b)true
iscutTerminate the frame_b end with a SphericalSpherical rod as the last segment; use when the cable closes a kinematic loop (requires n >= 2)false
dragApply a linear drag force to each link, proportional to the link velocityfalse
external_fieldEvaluate the link velocities relative to the uniform external velocity field connected at field (requires drag = true); when false the world-frame link velocity is usedfalse
njNumber of jointsifelse(iscu... 1, n - 1))
nlNumber of rigid linksifelse(iscut, n - 1, n)
rendertrue
color[0.5, 0.4, 0.4, 1]
specular_coefficient1.5
d_dragLinear drag coefficient per link (only used when drag = true)0
guess_axisRotation axis (unit vector) for the joint-quaternion initial guesses; provide an off-identity guess when the cable closes a kinematic loop (the torn loop equations are singular at identity configurations)[0, 0, 1]
guess_turnTotal turn-angle guess of the cable about guess_axis: the guessed shape is a circular arc turning guess_turn from the first to the last segment, with the first joint tilted by -guess_turn/2 so the arc's mean direction stays along dir. Provide a nonzero value when the cable closes a kinematic loop (the torn loop equations are singular at straight configurations)rad0
lTotal unstretched length of the ropem1
mTotal mass of the rope (each segment gets m/n)kg1
dirHanging direction; unit-normalized internally[0, -1, 0]
radiusRendering radius of each segment0.05

Connectors

  • frame_a - Frame3D is the fundamental 3D connector used for 6DOF motion. Most components have one or several Frame

connectors that can be connected together (Frame3D)

  • frame_b - Frame3D is the fundamental 3D connector used for 6DOF motion. Most components have one or several Frame

connectors that can be connected together (Frame3D)

  • field - Port for evaluating an external velocity field.

The component owning the port exposes the position r at which the field is to be evaluated; the connected side supplies the field velocity v at that position. Both are resolved in the world frame. (VelocityFieldPort)

Behavior

julia
using MultibodyComponents #hide
using ModelingToolkit #hide
@variables render #hide
@variables color #hide
@variables specular_coefficient #hide
@variables d_drag #hide
@variables guess_axis #hide
@variables guess_turn #hide
@variables l #hide
@variables m #hide
@variables dir #hide
@variables radius #hide
@named sys = MultibodyComponents.Cable(render=render, color=color, specular_coefficient=specular_coefficient, d_drag=d_drag, guess_axis=guess_axis, guess_turn=guess_turn, l=l, m=m, dir=dir, radius=radius) #hide
let eqs = full_equations(sys); Base.length(eqs) > 25 ? nothing : eqs end #hide
<< @example-block not executed in draft mode >>

Source

dyad
"""
Cable / rope of total length `l` and mass `m`, modeled as `n` rigid
segments connected by 3-DOF `Spherical` joints, matching the
Multibody.jl `Rope`.

When `end_joints = true` (default), the topology uses `n + 1` joints:
`frame_a → S₁ → link₁ → S₂ → … → Sₙ → linkₙ → Sₙ₊₁ → frame_b`

When `end_joints = false`, only the `n − 1` internal joints are kept and
both ends attach rigidly to the adjacent frame:
`frame_a → link₁ → S₁ → link₂ → … → Sₙ₋₁ → linkₙ → frame_b`

When `iscut = true`, the last segment is replaced by a `SphericalSpherical`
rod that imposes a single distance constraint and carries only axial force.
Use this whenever the cable closes a kinematic loop (both ends attached to
frames that are already rooted, e.g. a mooring line between the world and a
free-floating body): the rod terminates the chain without over-constraining
the orientation, and the two spherical-like rod ends replace the joints that
would otherwise sit on either side of the last link. Requires `n >= 2`. The
resulting topologies are
`frame_a → S₁ → link₁ → … → Sₙ₋₁ → linkₙ₋₁ → rod → frame_b` (`end_joints = true`)
`frame_a → link₁ → S₁ → … → Sₙ₋₂ → linkₙ₋₁ → rod → frame_b` (`end_joints = false`)

When `drag = true`, each link receives a linear drag force proportional to
the velocity of its center of mass, with coefficient `d_drag` per link. With
`external_field = true` the link velocities are evaluated relative to a
uniform external velocity field connected at the `field` port: the cable
exposes its midpoint position at `field.r` and the connected side supplies
the field velocity at `field.v` (e.g. a water current or wave field; the
connection is made outside the cable). The loop-closing rod of `iscut`
cables carries no drag.

Each `Spherical` joint uses quaternion state (Euler-angle state hits a
structural-simplify cycle when chained — SciML/ModelingToolkit.jl#4608) and
transmits no torque. The cable starts straight (along `dir`); seed an initial
bend by setting a joint's non-unit quaternion `Q_hat` as an initial condition,
e.g. a 0.9 rad bend about x on the first joint:
`cable.joints[1].Q_hat = [cos(0.45), sin(0.45), 0, 0]`.
"""
component Cable
  extends PartialTwoFrames()
  extends Renderable(color = [0.5, 0.4, 0.4, 1])
  joints = [MultibodyComponents.Spherical(orientation_state = orientation_state, render = false, initialize_orientation = initialize_orientation) for i in 1:nj]
  links = [MultibodyComponents.BodyShape(r = r_segment, m = m_segment, color = color, radius = radius) for i in 1:nl]
  rod = MultibodyComponents.SphericalSpherical(r_0 = r_segment, has_mass = true, m = m_segment, radius = radius, color = color, render = render) if iscut
  drag_forces = [MultibodyComponents.WorldForce(resolve_in_frame = ResolveInFrame.World(), render = false) for i in 1:nl] if drag
  "Uniform external velocity field in which the link velocities are evaluated (only used when external_field = true)"
  field = MultibodyComponents.VelocityFieldPort() if external_field
  "Specify identity initial conditions for the joint orientations. Set to false when the cable closes a kinematic loop, where a straight cable is not a consistent initial condition, to let initialization solve for the joint orientations."
  structural parameter initialize_orientation::Boolean = true
  structural parameter orientation_state::MultibodyComponents.OrientationState = OrientationState.Quaternion()
  "Number of rigid segments"
  structural parameter n::Integer = 5
  "Include Spherical joints at both ends (frame_a and frame_b)"
  structural parameter end_joints::Boolean = true
  "Terminate the frame_b end with a SphericalSpherical rod as the last segment; use when the cable closes a kinematic loop (requires n >= 2)"
  structural parameter iscut::Boolean = false
  "Apply a linear drag force to each link, proportional to the link velocity"
  structural parameter drag::Boolean = false
  "Evaluate the link velocities relative to the uniform external velocity field connected at `field` (requires drag = true); when false the world-frame link velocity is used"
  structural parameter external_field::Boolean = false
  "Linear drag coefficient per link (only used when drag = true)"
  parameter d_drag::Real = 0
  "Rotation axis (unit vector) for the joint-quaternion initial guesses; provide an off-identity guess when the cable closes a kinematic loop (the torn loop equations are singular at identity configurations)"
  parameter guess_axis::Real[3] = [0, 0, 1]
  "Total turn-angle guess of the cable about guess_axis: the guessed shape is a circular arc turning guess_turn from the first to the last segment, with the first joint tilted by -guess_turn/2 so the arc's mean direction stays along dir. Provide a nonzero value when the cable closes a kinematic loop (the torn loop equations are singular at straight configurations)"
  parameter guess_turn::Angle = 0
  final parameter guess_half_first::Real = -guess_turn / 4
  final parameter guess_half_rest::Real = guess_turn / (2 * ifelse(n > 1, n - 1, 1))
  final parameter q_guess_first::Real[4] = [cos(guess_half_first), sin(guess_half_first) * guess_axis[1], sin(guess_half_first) * guess_axis[2], sin(guess_half_first) * guess_axis[3]]
  final parameter q_guess_rest::Real[4] = [cos(guess_half_rest), sin(guess_half_rest) * guess_axis[1], sin(guess_half_rest) * guess_axis[2], sin(guess_half_rest) * guess_axis[3]]
  "Number of joints"
  final structural parameter nj::Integer = ifelse(iscut, ifelse(end_joints, n - 1, n - 2), ifelse(end_joints, n + 1, n - 1))
  "Number of rigid links"
  final structural parameter nl::Integer = ifelse(iscut, n - 1, n)
  "Total unstretched length of the rope"
  parameter l::Length = 1
  "Total mass of the rope (each segment gets m/n)"
  parameter m::Mass = 1
  "Hanging direction; unit-normalized internally"
  parameter dir::Real[3] = [0, -1, 0]
  "Rendering radius of each segment"
  parameter radius::Real = 0.05
  final parameter r_segment::Length[3] = (l / n) * (dir / norm_(dir))
  final parameter m_segment::Mass = m / n
relations
  if iscut
    if end_joints
      connect(frame_a, joints[1].frame_a)
      for i in 1:n - 2
        connect(joints[i].frame_b, links[i].frame_a)
        connect(links[i].frame_b, joints[i + 1].frame_a)
      end
      connect(joints[n - 1].frame_b, links[n - 1].frame_a)
      connect(links[n - 1].frame_b, rod.frame_a)
      connect(rod.frame_b, frame_b)
    else
      connect(frame_a, links[1].frame_a)
      for i in 1:n - 2
        connect(links[i].frame_b, joints[i].frame_a)
        connect(joints[i].frame_b, links[i + 1].frame_a)
      end
      connect(links[n - 1].frame_b, rod.frame_a)
      connect(rod.frame_b, frame_b)
    end
  else
    if end_joints
      connect(frame_a, joints[1].frame_a)
      for i in 1:n
        connect(joints[i].frame_b, links[i].frame_a)
        connect(links[i].frame_b, joints[i + 1].frame_a)
      end
      connect(joints[nj].frame_b, frame_b)
    else
      connect(frame_a, links[1].frame_a)
      for i in 1:nj
        connect(links[i].frame_b, joints[i].frame_a)
        connect(joints[i].frame_b, links[i + 1].frame_a)
      end
      connect(links[n].frame_b, frame_b)
    end
  end
  for i in 1:nj
    guess joints[i].Q_hat = i == 1 ? q_guess_first : q_guess_rest
  end
  if external_field
    field.r = (frame_a.r_0 + frame_b.r_0) / 2
  end
  if drag
    if external_field
      for i in 1:nl
        connect(drag_forces[i].frame_b, links[i].frame_cm)
        drag_forces[i].force_x = -d_drag * (der(links[i].frame_cm.r_0[1]) - field.v[1])
        drag_forces[i].force_y = -d_drag * (der(links[i].frame_cm.r_0[2]) - field.v[2])
        drag_forces[i].force_z = -d_drag * (der(links[i].frame_cm.r_0[3]) - field.v[3])
      end
    else
      for i in 1:nl
        connect(drag_forces[i].frame_b, links[i].frame_cm)
        drag_forces[i].force_x = -d_drag * der(links[i].frame_cm.r_0[1])
        drag_forces[i].force_y = -d_drag * der(links[i].frame_cm.r_0[2])
        drag_forces[i].force_z = -d_drag * der(links[i].frame_cm.r_0[3])
      end
    end
  end
metadata {
  "Dyad": {
    "icons": {"default": "dyad://MultibodyComponents/Cable.svg"},
    "labels": [
      {
        "label": "$(instance)",
        "x": 500,
        "y": 150,
        "rot": 0,
        "attrs": {"font-size": "160"}
      }
    ]
  }
}
end
Flattened Source
dyad
"""
Cable / rope of total length `l` and mass `m`, modeled as `n` rigid
segments connected by 3-DOF `Spherical` joints, matching the
Multibody.jl `Rope`.

When `end_joints = true` (default), the topology uses `n + 1` joints:
`frame_a → S₁ → link₁ → S₂ → … → Sₙ → linkₙ → Sₙ₊₁ → frame_b`

When `end_joints = false`, only the `n − 1` internal joints are kept and
both ends attach rigidly to the adjacent frame:
`frame_a → link₁ → S₁ → link₂ → … → Sₙ₋₁ → linkₙ → frame_b`

When `iscut = true`, the last segment is replaced by a `SphericalSpherical`
rod that imposes a single distance constraint and carries only axial force.
Use this whenever the cable closes a kinematic loop (both ends attached to
frames that are already rooted, e.g. a mooring line between the world and a
free-floating body): the rod terminates the chain without over-constraining
the orientation, and the two spherical-like rod ends replace the joints that
would otherwise sit on either side of the last link. Requires `n >= 2`. The
resulting topologies are
`frame_a → S₁ → link₁ → … → Sₙ₋₁ → linkₙ₋₁ → rod → frame_b` (`end_joints = true`)
`frame_a → link₁ → S₁ → … → Sₙ₋₂ → linkₙ₋₁ → rod → frame_b` (`end_joints = false`)

When `drag = true`, each link receives a linear drag force proportional to
the velocity of its center of mass, with coefficient `d_drag` per link. With
`external_field = true` the link velocities are evaluated relative to a
uniform external velocity field connected at the `field` port: the cable
exposes its midpoint position at `field.r` and the connected side supplies
the field velocity at `field.v` (e.g. a water current or wave field; the
connection is made outside the cable). The loop-closing rod of `iscut`
cables carries no drag.

Each `Spherical` joint uses quaternion state (Euler-angle state hits a
structural-simplify cycle when chained — SciML/ModelingToolkit.jl#4608) and
transmits no torque. The cable starts straight (along `dir`); seed an initial
bend by setting a joint's non-unit quaternion `Q_hat` as an initial condition,
e.g. a 0.9 rad bend about x on the first joint:
`cable.joints[1].Q_hat = [cos(0.45), sin(0.45), 0, 0]`.
"""
component Cable
  frame_a = Frame3D() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": -50, "y1": 450, "x2": 50, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
  frame_b = Frame3D() {
    "Dyad": {
      "placement": {
        "diagram": {"iconName": "default", "x1": 950, "y1": 450, "x2": 1050, "y2": 550, "rot": 0}
      },
      "tags": []
    }
  }
  parameter render::Boolean = true
  parameter color::Real[4] = [0.5, 0.5, 0.5, 1.0]
  parameter specular_coefficient::Real = 1.5
  joints = [MultibodyComponents.Spherical(orientation_state = orientation_state, render = false, initialize_orientation = initialize_orientation) for i in 1:nj]
  links = [MultibodyComponents.BodyShape(r = r_segment, m = m_segment, color = color, radius = radius) for i in 1:nl]
  rod = MultibodyComponents.SphericalSpherical(r_0 = r_segment, has_mass = true, m = m_segment, radius = radius, color = color, render = render) if iscut
  drag_forces = [MultibodyComponents.WorldForce(resolve_in_frame = ResolveInFrame.World(), render = false) for i in 1:nl] if drag
  "Uniform external velocity field in which the link velocities are evaluated (only used when external_field = true)"
  field = MultibodyComponents.VelocityFieldPort() if external_field
  "Specify identity initial conditions for the joint orientations. Set to false when the cable closes a kinematic loop, where a straight cable is not a consistent initial condition, to let initialization solve for the joint orientations."
  structural parameter initialize_orientation::Boolean = true
  structural parameter orientation_state::MultibodyComponents.OrientationState = OrientationState.Quaternion()
  "Number of rigid segments"
  structural parameter n::Integer = 5
  "Include Spherical joints at both ends (frame_a and frame_b)"
  structural parameter end_joints::Boolean = true
  "Terminate the frame_b end with a SphericalSpherical rod as the last segment; use when the cable closes a kinematic loop (requires n >= 2)"
  structural parameter iscut::Boolean = false
  "Apply a linear drag force to each link, proportional to the link velocity"
  structural parameter drag::Boolean = false
  "Evaluate the link velocities relative to the uniform external velocity field connected at `field` (requires drag = true); when false the world-frame link velocity is used"
  structural parameter external_field::Boolean = false
  "Linear drag coefficient per link (only used when drag = true)"
  parameter d_drag::Real = 0
  "Rotation axis (unit vector) for the joint-quaternion initial guesses; provide an off-identity guess when the cable closes a kinematic loop (the torn loop equations are singular at identity configurations)"
  parameter guess_axis::Real[3] = [0, 0, 1]
  "Total turn-angle guess of the cable about guess_axis: the guessed shape is a circular arc turning guess_turn from the first to the last segment, with the first joint tilted by -guess_turn/2 so the arc's mean direction stays along dir. Provide a nonzero value when the cable closes a kinematic loop (the torn loop equations are singular at straight configurations)"
  parameter guess_turn::Angle = 0
  final parameter guess_half_first::Real = -guess_turn / 4
  final parameter guess_half_rest::Real = guess_turn / (2 * ifelse(n > 1, n - 1, 1))
  final parameter q_guess_first::Real[4] = [cos(guess_half_first), sin(guess_half_first) * guess_axis[1], sin(guess_half_first) * guess_axis[2], sin(guess_half_first) * guess_axis[3]]
  final parameter q_guess_rest::Real[4] = [cos(guess_half_rest), sin(guess_half_rest) * guess_axis[1], sin(guess_half_rest) * guess_axis[2], sin(guess_half_rest) * guess_axis[3]]
  "Number of joints"
  final structural parameter nj::Integer = ifelse(iscut, ifelse(end_joints, n - 1, n - 2), ifelse(end_joints, n + 1, n - 1))
  "Number of rigid links"
  final structural parameter nl::Integer = ifelse(iscut, n - 1, n)
  "Total unstretched length of the rope"
  parameter l::Length = 1
  "Total mass of the rope (each segment gets m/n)"
  parameter m::Mass = 1
  "Hanging direction; unit-normalized internally"
  parameter dir::Real[3] = [0, -1, 0]
  "Rendering radius of each segment"
  parameter radius::Real = 0.05
  final parameter r_segment::Length[3] = (l / n) * (dir / norm_(dir))
  final parameter m_segment::Mass = m / n
relations
  if iscut
    if end_joints
      connect(frame_a, joints[1].frame_a)
      for i in 1:n - 2
        connect(joints[i].frame_b, links[i].frame_a)
        connect(links[i].frame_b, joints[i + 1].frame_a)
      end
      connect(joints[n - 1].frame_b, links[n - 1].frame_a)
      connect(links[n - 1].frame_b, rod.frame_a)
      connect(rod.frame_b, frame_b)
    else
      connect(frame_a, links[1].frame_a)
      for i in 1:n - 2
        connect(links[i].frame_b, joints[i].frame_a)
        connect(joints[i].frame_b, links[i + 1].frame_a)
      end
      connect(links[n - 1].frame_b, rod.frame_a)
      connect(rod.frame_b, frame_b)
    end
  else
    if end_joints
      connect(frame_a, joints[1].frame_a)
      for i in 1:n
        connect(joints[i].frame_b, links[i].frame_a)
        connect(links[i].frame_b, joints[i + 1].frame_a)
      end
      connect(joints[nj].frame_b, frame_b)
    else
      connect(frame_a, links[1].frame_a)
      for i in 1:nj
        connect(links[i].frame_b, joints[i].frame_a)
        connect(joints[i].frame_b, links[i + 1].frame_a)
      end
      connect(links[n].frame_b, frame_b)
    end
  end
  for i in 1:nj
    guess joints[i].Q_hat = i == 1 ? q_guess_first : q_guess_rest
  end
  if external_field
    field.r = (frame_a.r_0 + frame_b.r_0) / 2
  end
  if drag
    if external_field
      for i in 1:nl
        connect(drag_forces[i].frame_b, links[i].frame_cm)
        drag_forces[i].force_x = -d_drag * (der(links[i].frame_cm.r_0[1]) - field.v[1])
        drag_forces[i].force_y = -d_drag * (der(links[i].frame_cm.r_0[2]) - field.v[2])
        drag_forces[i].force_z = -d_drag * (der(links[i].frame_cm.r_0[3]) - field.v[3])
      end
    else
      for i in 1:nl
        connect(drag_forces[i].frame_b, links[i].frame_cm)
        drag_forces[i].force_x = -d_drag * der(links[i].frame_cm.r_0[1])
        drag_forces[i].force_y = -d_drag * der(links[i].frame_cm.r_0[2])
        drag_forces[i].force_z = -d_drag * der(links[i].frame_cm.r_0[3])
      end
    end
  end
metadata {
  "Dyad": {
    "icons": {"default": "dyad://MultibodyComponents/Cable.svg"},
    "labels": [
      {
        "label": "$(instance)",
        "x": 500,
        "y": 150,
        "rot": 0,
        "attrs": {"font-size": "160"}
      }
    ]
  }
}
end


Test Cases

No test cases defined.