scene.mechanics dibuja la notación de la mecánica y el dibujo técnico: cotas, ángulos, barras, muelles, vectores de fuerza, apoyos y engranajes. Casi todo es reactivo: los extremos aceptan objetos, AnchorPoint y PointRef (ver Geometría), y la geometría se regenera en el mismo fotograma cuando se mueven. Las magnitudes aceptan números o valores reactivos (Parameter, Variable, Computed).

from gaanim import Anchor, BLACK, Direction, WHITE, Scene

scene = Scene(frame=(16, 9), background=WHITE)
wall = scene.mechanics.fixed_support((-4, 0), direction=Direction.RIGHT, size=1.2, ground_length=2.4)
block = scene.geometry.rect(2, 1.6).fill("#cbd5e1").stroke(BLACK, 0.04).move_to(0, 0)
spring = scene.mechanics.spring_between((-4, 0), block.anchor_point(Anchor.LEFT), coils=7, amplitude=0.3).no_fill().stroke(BLACK, 0.05)
gap = scene.mechanics.dimension_between((-4, -0.8), block.anchor_point(Anchor.BOTTOM_LEFT), 0.6, side="below", show_value=True, unit="m", color=BLACK)
scene.play([spring.animate.fade_in(), gap.animate.fade_in()], duration=0.3)
scene.play([block.animate.shift_by(2, 0).duration(1.2)])
scene.render()
animation preview

Los textos de las anotaciones (etiqueta, valor y unidad) miden 0.48 unidades por defecto, legibles en 1080p; font_size lo cambia. color pinta la geometría y la anotación completa, incluido el número cuando cambia o al hacer seek.

Cotas y ángulos

Medidas técnicas estáticas o que siguen a sus extremos.

FÁBRICAMechanics.dimensiondimension(x1: float, y1: float, x2: float, y2: float, offset: float) -> Drawable
Cota estática entre (x1, y1) y (x2, y2), desplazada offset en perpendicular, con líneas de extensión de 0.02 unidades y las puntas de double_arrow. Para una cota que siga a objetos móviles, usa dimension_between.
Ejemplo
dim = scene.mechanics.dimension(-3, 0, 3, 0, 0.6).fill(WHITE)
FÁBRICAMechanics.dimension_betweendimension_between(from_: Endpoint, to: Endpoint, offset: float, *, label: Optional[str] = None, show_value: bool = False, value: Optional[_ReactiveScalar] = None, format: str = ".2f", unit: Optional[str] = None, scale: float = 1.0, label_gap: float = 0.10, label_orientation: Literal["upright", "aligned"] = "upright", font_size: Optional[float] = None, color: Optional[ColorLike] = None, line_width: float = 0.03, extension_style: Literal["solid", "dashed"] = "solid", dash_length: float = 0.12, gap_length: float = 0.08, side: Optional[Literal["left", "right", "above", "below"]] = None, font: Optional[str] = None, weight: Optional[int] = None, label_style: Optional[TextStyle] = None) -> Dimension
Cota reactiva con líneas de extensión, puntas sólidas y anotación sincronizadas con los extremos. Como cualquier anotación, se ve desde su declaración; con una entrada (fade_in, create) espera oculta hasta que esta empieza. value solo controla el número: cambiarlo nunca cambia la longitud. Tipos, métricas, estilos u orientaciones inválidos lanzan TypeError o ValueError; un resultado reactivo no finito muestra el marcador de valor inválido.
Parámetros
NombreTipoPredeterminadoDescripción
from_ / toEndpointobligatorioExtremos medidos.
offsetfloatobligatorioDesplazamiento perpendicular. Sin side, positivo es a la izquierda de from_ → to; con side, solo cuenta su magnitud.
sidestr | NoneNoneleft, right, above o below: mantiene la cota en ese lado de la escena aunque los extremos se crucen.
labelstr | NoneNoneTexto simbólico o matemática en línea.
show_valueboolFalseMuestra la distancia actual multiplicada por scale.
valuefloat | Parameter | Variable | Computed | NoneNoneLectura numérica propia; implica show_value y sustituye la distancia medida y scale.
format / unit / scalestr / str | None / float".2f" / None / 1Formato, unidad y factor de escena a unidades mostradas.
label_gap / label_orientationfloat / str0.1 / "upright"Separación entre la línea y el borde más cercano de la anotación, y orientación upright o aligned (nunca boca abajo).
line_widthfloat0.03Ancho de la línea; las puntas miden seis anchos, limitadas en cotas cortas.
extension_style / dash_length / gap_lengthstr / float / float"solid" / 0.12 / 0.08Líneas de extensión solid o dashed.
font / weight / label_stylestr / int / TextStyle | NoneNoneTipografía de la anotación; font, weight y font_size sustituyen a label_style.
Ejemplo
from gaanim import Anchor, BLACK, WHITE, Scene
scene = Scene(frame=(16, 9), background=WHITE)
frame = scene.geometry.rect(4.5, 2).move_to(0, 0)
physical_width = scene.viz.parameter(2.5)
dim = scene.mechanics.dimension_between(
  frame.anchor_point(Anchor.TOP_LEFT),
  frame.anchor_point(Anchor.TOP_RIGHT),
  0.6, label="$W_f$", value=physical_width, unit="m", color=BLACK,
  extension_style="dashed", line_width=0.03, dash_length=0.12, gap_length=0.08,
)
height = scene.mechanics.dimension_between(
  frame.anchor_point(Anchor.TOP_RIGHT),
  frame.anchor_point(Anchor.BOTTOM_RIGHT),
  0.6, side="right", show_value=True, unit="m", color=BLACK,
  font="DejaVu Sans Mono", weight=700,
)
scene.play([
  dim.animate.fade_in().duration(0.3),
  height.animate.fade_in().duration(0.3),
  physical_width.animate.set(4.0).duration(0.9),
])
scene.render()
animation preview
PROPIEDADDimension.line / extensions / label / number / unitline · extensions · label · number · unit
Tipo que devuelve dimension_between. Sus partes son Drawable que se estilizan por separado; label, number y unit valen None si no se pidieron.
Ejemplo
dim = scene.mechanics.dimension_between((-2, 0), (2, 0), 0.5, label="$L$", show_value=True)
dim.extensions.opacity(0.4)
dim.number.fill(GOLD)
FÁBRICAMechanics.angle_betweenangle_between(vertex: Endpoint, from_: AngleRay, to: AngleRay, *, radius: float = 0.64, label: Optional[str] = None, show_value: bool = False, format: str = ".1f", unit: Literal["deg", "rad"] = "deg", sweep: Literal["minor", "major", "cw", "ccw"] = "minor", arrowheads: Literal["none", "start", "end", "both"] = "both", label_gap: float = 0.12, label_orientation: Literal["upright", "aligned"] = "upright", show_extensions: bool = True, font_size: Optional[float] = None, color: Optional[ColorLike] = None) -> AngleDimension
Cota angular reactiva. Un rayo de longitud cero oculta la geometría afectada en lugar de emitir caminos inválidos.
Parámetros
NombreTipoPredeterminadoDescripción
vertexEndpointobligatorioVértice del ángulo.
from_ / toDirection | EndpointobligatorioRayos como direcciones fijas o extremos.
radiusfloat0.64Radio del arco.
sweepstr"minor"minor, major, cw o ccw.
arrowheadsstr"both"none, start, end o both.
unitstr"deg"deg o rad para el valor mostrado.
show_extensionsboolTrueDibuja los rayos.
Ejemplo
from gaanim import Direction, GOLD, Scene
scene = Scene(frame=(16, 9))
bob = scene.geometry.dot(0.125).move_to(1, -1.125)
theta = scene.mechanics.angle_between((0,0), Direction.DOWN, bob, label="$theta$", show_value=True, color=GOLD)
scene.play([theta.animate.fade_in(), bob.animate.shift_by(1.125, 0.44).duration(1.2)])
scene.render()
animation preview
PROPIEDADAngleDimension.arc / arrows / extensions / label / number / unitarc · arrows · extensions · label · number · unit
Tipo que devuelve angle_between, con partes que se estilizan por separado.

Barras y muelles

Elementos que unen dos extremos y cambian de longitud con ellos.

FÁBRICAMechanics.bar_betweenbar_between(from_: Endpoint, to: Endpoint, *, width: float = 0.08) -> Drawable
Barra de extremos redondeados con longitud y ángulo reactivos; width en unidades de escena. Dibuja las articulaciones aparte si las necesitas.
Ejemplo
from gaanim import Anchor, BLACK, Scene
scene = Scene(frame=(16, 9))
body = scene.geometry.rect(1.875, 0.875).move_to(0.5, -0.25)
bar = scene.mechanics.bar_between((-1.875, 1.25), body.anchor_point(Anchor.TOP_LEFT), width=0.11).stroke(BLACK, 0.11)
scene.play([bar.animate.fade_in(), body.animate.shift_by(1, 0).duration(1.0)])
scene.render()
animation preview
FÁBRICAMechanics.spring_betweenspring_between(from_: Endpoint, to: Endpoint, coils: int = 8, amplitude: float = 0.12, crossing: float = 0.0, start_straight: float = 0.12, end_straight: float = 0.12) -> Drawable
Muelle helicoidal oculto que conserva su radio mientras su paso se deforma con la distancia. Revélalo con una animación de entrada. Tramos negativos o no finitos lanzan ValueError.
Parámetros
NombreTipoPredeterminadoDescripción
from_ / toEndpointobligatorioExtremos.
coilsint8Número de vueltas.
amplitudefloat0.12Radio de la hélice, perpendicular al eje.
crossingfloat0De 0 a 1: pliega cada vuelta para crear cruces en forma de «e».
start_straight / end_straightfloat0.12 / 0.12Tramos rectos no negativos en cada extremo; se acortan si los extremos están muy cerca.
Ejemplo
from gaanim import GOLD, WHITE, Scene
scene = Scene(frame=(16, 9), background="#0f172a")
mass = scene.geometry.dot(0.125).fill(GOLD).move_to(0.875, 0)
spring = scene.mechanics.spring_between(( -0.875, 0), mass, coils=6, amplitude=0.175, crossing=1.0, start_straight=0.225, end_straight=0.225).no_fill().stroke(WHITE, 0.04)
scene.play([spring.animate.fade_in().duration(0.3), mass.animate.shift_by(0.5, 0).duration(1.0)])
scene.render()
animation preview

Fuerzas y momentos

Vectores con punta sólida y lectura opcional, en unidades físicas.

FÁBRICAMechanics.vector_betweenvector_between(from_: Endpoint, to: Endpoint, *, label: Optional[str] = None, show_value: bool = False, format: str = ".1f", unit: Optional[str] = None, scale: float = 1.0, label_gap: float = 0.14, font_size: Optional[float] = None, color: Optional[ColorLike] = None) -> ForceVector
Vector reactivo entre dos extremos con asta, punta sólida y lectura opcional de su magnitud (show_value, format, unit, scale).
Ejemplo
force = scene.mechanics.vector_between((-1.25, 0), (0.875, 0.56), label="$F$", color=GOLD)
FÁBRICAMechanics.force_atforce_at(origin: Endpoint, magnitude: _ReactiveScalar, *, direction: _ReactiveScalar = 0.0, visual_scale: float = 1.0, label: Optional[str] = None, show_value: bool = False, format: str = ".1f", unit: str = "N", label_gap: float = 0.14, font_size: Optional[float] = None, color: Optional[ColorLike] = None) -> ForceVector
Fuerza a partir de magnitud y dirección. La lectura opcional informa la magnitud física, no la longitud dibujada.
Parámetros
NombreTipoPredeterminadoDescripción
originEndpointobligatorioPunto de aplicación.
magnitudefloat | fuente reactivaobligatorioMagnitud física.
directionfloat | fuente reactiva0.0Dirección en radianes.
visual_scalefloat1.0Unidades de escena por unidad física; positiva.
unitstr"N"Unidad de la lectura.
Ejemplo
from gaanim import GREEN, Scene
scene = Scene(frame=(16, 9))
body = scene.geometry.circle(0.3)
magnitude = scene.viz.parameter(30)
force = scene.mechanics.force_at(
  body.anchor_point(), magnitude, direction=0.5, visual_scale=0.025,
  label="$F$", show_value=True, unit="N", color=GREEN,
)
scene.play([body.animate.fade_in(), force.animate.fade_in(), magnitude.animate.set(80).duration(1.5)])
scene.render()
animation preview
FÁBRICAMechanics.force_from_componentsforce_from_components(origin: Endpoint, fx: _ReactiveScalar, fy: _ReactiveScalar, *, visual_scale: float = 1.0, label: Optional[str] = None, show_value: bool = False, format: str = ".1f", unit: str = "N", label_gap: float = 0.14, font_size: Optional[float] = None, color: Optional[ColorLike] = None) -> ForceVector
Fuerza a partir de sus componentes físicas X e Y respecto a un origen móvil. Combínala con Parameter.add_updater_fn para simulaciones: las simulaciones de paso fijo se reconstruyen antes, así que una fuerza derivada del cuerpo simulado ve el estado del mismo fotograma.
Ejemplo
body = scene.geometry.circle(0.3)
fx = scene.viz.parameter(20)
weight = scene.mechanics.force_from_components(body, fx, -40, visual_scale=0.02, label="$W$")
PROPIEDADForceVector.shaft / head / label / number / unitshaft · head · label · number · unit
Tipo que devuelven vector_between, force_at y force_from_components, con partes que se estilizan por separado.
FÁBRICAMechanics.moment_aboutmoment_about(center: Endpoint, radius: float, *, direction: Literal["cw", "ccw"] = "ccw", label: Optional[str] = None, color: Optional[ColorLike] = None) -> Drawable
Flecha curva de momento que sigue a un centro reactivo; direction es "cw" o "ccw".
Ejemplo
moment = scene.mechanics.moment_about((1.5, -0.5), radius=0.525, label="$M$")
FÁBRICAMechanics.coordinate_frame_atcoordinate_frame_at(origin: Endpoint, x_direction: Direction, *, length: float = 0.70, labels: Optional[tuple[str, str]] = None, color: Optional[ColorLike] = None) -> Drawable
Sistema de ejes 2D ortogonal y reactivo en un extremo, con el eje X en x_direction y etiquetas opcionales.
Ejemplo
from gaanim import Direction, GOLD, Scene
scene = Scene(frame=(16, 9))
force = scene.mechanics.vector_between((-1.25, 0), (0.875, 0.56), label="$F$", color=GOLD)
moment = scene.mechanics.moment_about((1.5, -0.5), radius=0.525, label="$M$")
frame = scene.mechanics.coordinate_frame_at((0, -1), Direction.RIGHT, labels=("$e_1$", "$e_2$"))
scene.play([force.animate.fade_in(), moment.animate.fade_in(), frame.animate.fade_in()])
scene.render()
animation preview
FÁBRICAMechanics.contact_on_curvecontact_on_curve(curve: Drawable, tracker: _ReactiveScalar, *, tangent_length: float = 0.8, normal_length: float = 0.8) -> Drawable
Agrupa un punto de contacto, su tangente y su normal sobre una curva muestreada, conducidos por un Parameter o Variable de 0 a 1.
Ejemplo
t = scene.viz.parameter(0.1)
track = scene.geometry.polyline([(x / 10, 0.5 * math.sin(x / 10)) for x in range(-40, 41)]).no_fill()
contact = scene.mechanics.contact_on_curve(track, t, tangent_length=1.0, normal_length=0.6)
scene.play([contact.animate.fade_in(), t.animate.set(0.9).duration(1.5)])

Apoyos y articulaciones

Símbolos de apoyo con base rayada y juntas, que siguen a su punto de conexión.

FÁBRICAMechanics.support_atsupport_at(point: Endpoint, *, kind: Literal["fixed", "pin", "roller", "simple", "guided", "prismatic", "cable", "spring"] = "pin", direction: Optional[Direction] = None, size: float = 0.48, ground_length: float = 0.70, color: Optional[ColorLike] = None) -> Support
Apoyo vectorial que respeta el tema. Los atajos fixed_support, pin_support, roller_support y guided_support fijan kind.
Parámetros
NombreTipoPredeterminadoDescripción
pointEndpointobligatorioPunto de conexión.
kindstr"pin"fixed, pin, roller, simple, guided, prismatic, cable o spring.
directionDirection | NoneNoneDe la base hacia la conexión: UP pone el suelo debajo y DOWN crea un apoyo de techo.
size / ground_lengthfloat0.48 / 0.70Medidas en unidades de escena.
Ejemplo
anchor = scene.mechanics.support_at((0, 2), kind="cable", direction=Direction.DOWN)
FÁBRICAMechanics.pin_supportpin_support(point: Endpoint, *, direction: Optional[Direction] = None, size: float = 0.48, ground_length: float = 0.70, color: Optional[ColorLike] = None) -> Support
Apoyo triangular articulado con junta circular.
Ejemplo
from gaanim import Direction, Scene
scene = Scene(frame=(16, 9))
pin = scene.mechanics.pin_support((-1.25, 0), direction=Direction.UP)
roller = scene.mechanics.roller_support((1.25, 0), direction=Direction.UP)
scene.play([pin.animate.fade_in(), roller.animate.fade_in()])
scene.render()
animation preview
FÁBRICAMechanics.roller_supportroller_support(point: Endpoint, *, direction: Optional[Direction] = None, size: float = 0.48, ground_length: float = 0.70, color: Optional[ColorLike] = None) -> Support
Apoyo triangular sobre dos rodillos alineados.
FÁBRICAMechanics.fixed_supportfixed_support(point: Endpoint, *, direction: Optional[Direction] = None, size: float = 0.48, ground_length: float = 0.70, color: Optional[ColorLike] = None) -> Support
Empotramiento (o apoyo de techo) con placa y rayado.
Ejemplo
wall = scene.mechanics.fixed_support((-4, 0), direction=Direction.RIGHT)
FÁBRICAMechanics.guided_supportguided_support(point: Endpoint, *, direction: Optional[Direction] = None, size: float = 0.48, ground_length: float = 0.70, color: Optional[ColorLike] = None) -> Support
Carro guiado alineado con direction.
PROPIEDADSupport.joint / body / ground / rollers / guides / hatchingjoint · body · ground · rollers · guides · hatching
Tipo que devuelven los apoyos, con partes vectoriales que se estilizan por separado.
Ejemplo
base = scene.mechanics.pin_support((0, 0))
base.hatching.opacity(0.5)
FÁBRICAMechanics.joint_atjoint_at(point: Endpoint, *, kind: Literal["revolute", "prismatic"] = "revolute", axis: Optional[Direction] = None, size: float = 0.36, color: Optional[ColorLike] = None) -> Drawable
Símbolo independiente de junta de revolución (kind="revolute") o prismática ("prismatic", alineada con axis) que sigue a su punto.
Ejemplo
hinge = scene.mechanics.joint_at((0, 0))
slider = scene.mechanics.joint_at((2, 0), kind="prismatic", axis=Direction.RIGHT)

Transmisiones

Engranajes, cremalleras y levas editoriales. Son relaciones visuales, no un solver cinemático ni de contacto; los dientes son esquemáticos.

FÁBRICAMechanics.geargear(radius: float, teeth: int, *, bore_radius: float = 0.08, color: Optional[ColorLike] = None) -> Drawable
Silueta de engranaje de radio radius con teeth dientes y agujero central. Acóplalos con Drawable.bind_rotation_from(source, ratio=..., phase=...).
Ejemplo
from gaanim import Direction, Scene
scene = Scene(frame=(16, 9))
driver = scene.mechanics.gear(0.69, 20).move_to(-0.69, 0.25)
driven = scene.mechanics.gear(0.41, 12).move_to(0.41, 0.25).bind_rotation_from(driver, ratio=-5/3)
rack = scene.mechanics.rack(2.75, 18).move_to(0, -0.81).bind_translation_from_rotation(
  driver, axis=Direction.RIGHT, scale=0.69,
)
scene.play([driver.animate.fade_in(), driven.animate.fade_in(), rack.animate.fade_in(), driver.animate.rotate_by(2.0)])
scene.render()
animation preview
FÁBRICAMechanics.rackrack(length: float, teeth: int, *, color: Optional[ColorLike] = None) -> Drawable
Cremallera recta de length con teeth dientes equiespaciados. Muévela con Drawable.bind_translation_from_rotation.
FÁBRICAMechanics.cam_profilecam_profile(samples: Sequence[tuple[float, float]], *, bore_radius: float = 0.08, color: Optional[ColorLike] = None) -> Drawable
Leva radial cerrada a partir de muestras (ángulo_en_radianes, radio).
Ejemplo
samples = [(2 * math.pi * i / 64, 1 + 0.3 * math.cos(2 * math.pi * i / 64)) for i in range(64)]
cam = scene.mechanics.cam_profile(samples)
scene.play([cam.animate.rotate_by(math.tau).duration(2)])