aboutsummaryrefslogtreecommitdiffstats
path: root/blender/diferencial.py
diff options
context:
space:
mode:
authorElvis Claros Castro <elvis@claros.ar>2026-09-26 20:21:47 -0300
committerElvis Claros Castro <elvis@claros.ar>2026-09-26 20:21:47 -0300
commit59355909f2de9236af8168a26c70bcf6caa3b285 (patch)
tree686186e2086f81aa22ad25e78eb29fca3cbadc9a /blender/diferencial.py
download100cia-videos-59355909f2de9236af8168a26c70bcf6caa3b285.tar.gz
100cia-videos-59355909f2de9236af8168a26c70bcf6caa3b285.zip
Import video pipeline as it was
Diffstat (limited to 'blender/diferencial.py')
-rw-r--r--blender/diferencial.py304
1 files changed, 304 insertions, 0 deletions
diff --git a/blender/diferencial.py b/blender/diferencial.py
new file mode 100644
index 0000000..015444a
--- /dev/null
+++ b/blender/diferencial.py
@@ -0,0 +1,304 @@
+# -*- coding: utf-8 -*-
+"""DIFERENCIAL - por que las dos ruedas pueden girar a distinta velocidad.
+
+Todas las piezas se generan por codigo y se mueven con la cinematica exacta del
+mecanismo: w_izq + w_der = 2 * w_jaula, y el satelite gira sobre su eje a
+(w_der - w_izq)/2 respecto de la jaula. El pinon y la corona respetan su
+relacion: 14 y 43 dientes, con semiangulos de cono que suman 90 grados.
+"""
+import math, os, sys
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+import bpy
+from mathutils import Vector
+from base import *
+
+NOMBRE = "diferencial"
+EJE_X = 1.32 # donde van las ruedas
+R_RUEDA = 0.52
+OMEGA = 1.45 # rad/s de la jaula
+RADIO_GIRO = 3.9
+TROCHA = 1.70
+INCL = -0.72 # bien diagonal: en 9:16 es lo que aprovecha el alto
+
+N_COR, N_PIN = 43, 14 # relacion 3,07:1, como un diferencial de calle
+G_COR, G_PIN = 72.0, 18.0 # los semiangulos suman 90: es la condicion de engrane
+N_LAT = 16
+X_COR = -0.50 # apice comun de corona y pinon: lejos de los satelites
+D_I, D_O = 0.70, 1.00 # la cara dentada, igual en los dos
+P_FASE = math.pi / 2 # para que el diente caiga en el hueco
+P_ESP = -0.62 # mano contraria a la corona
+P_SIG = 1.0
+
+# el conjunto mide 3,68 de punta a punta; inclinado 41 grados eso son 2,77 de
+# ancho y 2,41 de alto, que es lo que fija estos numeros
+ENCUADRE = [(0, 6.2), (2, 5.0), (3, 4.2), (4, 5.6), (5, 4.6),
+ (6, 5.0), (7, 5.6), (8, 5.6), (9, 5.8), (10, 5.2)]
+DIR_CAM = Vector((0.30, -0.93, 0.21)).normalized()
+
+
+def alto_en(T, f):
+ tabla, altos, v = dict(ENCUADRE), [], ENCUADRE[0][1]
+ for i in range(len(T.beats)):
+ v = tabla.get(i, v)
+ altos.append(v)
+ i = 0
+ for k in range(len(T.beats)):
+ if f >= T.rango(k)[0]:
+ i = k
+ ini, fin = T.rango(i)
+ p = min(1.0, (f - ini) / max(1.0, (fin - ini) * 0.55))
+ return mezcla(altos[i - 1] if i else altos[0], altos[i], p)
+
+
+def construir(T):
+ sc = escena()
+ mundo_estudio(0.65) # lo que refleja el acero
+ cam = camara((0, -6, 0), (0, 0, 0), lente=50)
+ cam.data.sensor_fit = 'VERTICAL'
+ cam.data.sensor_height = 36.0
+
+ luz("key", 'AREA', (-3.4, -4.6, 4.4), 1400, "blanco", tam=6.0, mira=(0, 0, 0))
+ luz("fill", 'AREA', (4.2, -3.2, -1.6), 420, "#BFD4FF", tam=5.0, mira=(0, 0, 0))
+ luz("rim", 'AREA', (-0.8, 4.6, 2.8), 900, "blanco", tam=4.0, mira=(0, 0, 0))
+
+ M = {
+ "acero": metal("acero", "#B7BEC9", rug=0.22),
+ "acero2": metal("acero2", "#9BA3B0", rug=0.30),
+ "fundido": metal("fundido", "#5E6774", rug=0.62, met=0.85),
+ "eje_i": metal("eje_i", "#C2C8D2", rug=0.24),
+ "eje_d": metal("eje_d", "#C2C8D2", rug=0.24),
+ "goma": material("goma", "#1E222C", rug=0.52),
+ "alu": metal("alu", "#CBD3DE", rug=0.15),
+ "freno": metal("freno", "#79818E", rug=0.45, met=0.9),
+ }
+
+ # --- vista de arriba: el auto doblando --------------------------------
+ piso = bpy.data.objects.new("suelo_g", None)
+ bpy.context.collection.objects.link(piso)
+ arcos, ruedas_a = [], []
+ for k, (rr, c) in enumerate(((RADIO_GIRO - TROCHA / 2, "rosa"),
+ (RADIO_GIRO + TROCHA / 2, "verde"))):
+ pts = []
+ for i in range(120):
+ th = -0.62 + 1.24 * i / 119.0
+ pts.append((-RADIO_GIRO + rr * math.cos(th), rr * math.sin(th), 0.0))
+ cu = curva_poly(f"arco{k}", [pts], grosor=0.045, radios=[[0.0] * 120],
+ mat=material(f"m_arco{k}", c, emis=2.4, rug=0.5))
+ cu.parent = piso
+ arcos.append((cu, pts))
+ r = rueda_completa(f"ra{k}", M, R=R_RUEDA, r=0.185, ancho=0.28)
+ r.parent = piso
+ ruedas_a.append(r)
+ eje_a = cilindro("eje_a", 0.075, TROCHA, M["acero2"])
+ eje_a.parent = piso
+ patina = curva_poly("patina", [[(0, 0, 0)] * 60], grosor=0.11,
+ radios=[[0.0] * 60], mat=material("m_pat", "blanco", emis=3.4))
+ patina.parent = piso
+
+ # --- el diferencial ----------------------------------------------------
+ dif = bpy.data.objects.new("dif", None)
+ bpy.context.collection.objects.link(dif)
+ dif.scale = (0, 0, 0)
+
+ jaula = bpy.data.objects.new("jaula", None)
+ bpy.context.collection.objects.link(jaula)
+ jaula.parent = dif
+ # caja de la jaula: dos bridas anulares y dos paredes curvas sobre el eje del
+ # perno. Los lados +-Y quedan abiertos: por ahi se ven los engranajes, que es
+ # como se ve una caja de diferencial de verdad
+ # todo lo de la jaula queda por dentro de r=0.52: el pinon nunca baja de
+ # r=0.59, y asi la jaula puede girar sin pasarle por encima
+ for sx in (0.58, -0.58):
+ br = anillo_plano(f"brida{sx}", 0.13, 0.52, 0.08, M["fundido"])
+ br.rotation_euler = (0, math.pi / 2, 0)
+ br.location = (sx, 0, 0)
+ br.parent = jaula
+ for k, centro in enumerate((math.pi / 2, -math.pi / 2)):
+ pared = sector_anillo(f"pared{k}", 0.43, 0.52, 1.16,
+ centro - 0.62, centro + 0.62, M["fundido"])
+ pared.rotation_euler = (0, math.pi / 2, 0)
+ pared.parent = jaula
+ perno = cilindro("perno", 0.062, 1.00, M["acero2"])
+ perno.parent = jaula
+
+ corona = engranaje_conico("corona", (1, 0, 0), N=N_COR, gamma=G_COR,
+ d_i=D_I, d_o=D_O, alto=0.042, mat=M["acero"],
+ espiral=0.62, espesor=0.14, hueco=0.58,
+ por_diente=24)
+ corona.location = (X_COR, 0, 0)
+ corona.parent = jaula
+ tornillos("bulones", 10, 0.78, -0.11, largo=0.10, r_t=0.035,
+ mat=M["acero2"], padre=jaula)
+
+ lat = {}
+ for nom, eje, sx in (("izq", (-1, 0, 0), -1), ("der", (1, 0, 0), 1)):
+ g = bpy.data.objects.new(f"lat_{nom}", None)
+ bpy.context.collection.objects.link(g)
+ g.parent = dif
+ eng = engranaje_conico(f"eng_{nom}", eje, N=N_LAT, gamma=45.0,
+ d_i=0.26, d_o=0.50, alto=0.042, mat=M["acero"],
+ espiral=0.30, espesor=0.12, hueco=0.09)
+ eng.parent = g
+ # semieje escalonado + brida
+ for (largo, radio, x0) in ((0.48, 0.110, 0.50), (EJE_X - 0.82, 0.080, 0.95)):
+ se = cilindro(f"se_{nom}_{radio}", radio, largo, M[f"eje_{nom[0]}"])
+ se.rotation_euler = (0, math.pi / 2, 0)
+ se.location = (sx * x0, 0, 0)
+ se.parent = g
+ brida = anillo_plano(f"bw_{nom}", 0.09, 0.22, 0.07, M["acero2"])
+ brida.rotation_euler = (0, math.pi / 2, 0)
+ brida.location = (sx * (EJE_X - 0.20), 0, 0)
+ brida.parent = g
+ rue = rueda_completa(f"rue_{nom}", M, R=R_RUEDA, r=0.185, ancho=0.28)
+ rue.location = (sx * EJE_X, 0, 0)
+ rue.parent = g
+ lat[nom] = g
+
+ sat = []
+ for k, eje in enumerate(((0, 0, 1), (0, 0, -1))):
+ g = bpy.data.objects.new(f"sat{k}", None)
+ bpy.context.collection.objects.link(g)
+ g.parent = jaula
+ eng = engranaje_conico(f"eng_s{k}", eje, N=N_LAT, gamma=45.0,
+ d_i=0.26, d_o=0.50, alto=0.042, mat=M["acero2"],
+ espiral=-0.30, espesor=0.12, hueco=0.09, fase=math.pi)
+ eng.parent = g
+ if k == 0:
+ p = esfera(f"testigo{k}", 0.050, material("m_test", "ambar", emis=3.0))
+ p.location = (0.30, 0.0, 0.42)
+ p.parent = g
+ sat.append(g)
+
+ entrada = bpy.data.objects.new("entrada", None)
+ bpy.context.collection.objects.link(entrada)
+ entrada.parent = dif
+ # misma cara que la corona, espiral de mano contraria y fase para que el
+ # diente caiga en el hueco
+ pinon = engranaje_conico("pinon", (0, -1, 0), N=N_PIN, gamma=G_PIN,
+ d_i=D_I, d_o=D_O, alto=0.042, mat=M["acero"],
+ espiral=P_ESP, espesor=0.12, hueco=0.07,
+ fase=P_FASE, por_diente=24)
+ pinon.location = (X_COR, 0, 0)
+ pinon.parent = entrada
+ cardan = cilindro("cardan", 0.095, 1.30, M["acero2"])
+ cardan.rotation_euler = (math.pi / 2, 0, 0)
+ cardan.location = (X_COR, -1.50, 0)
+ cardan.parent = entrada
+ horquilla = anillo_plano("horquilla", 0.10, 0.24, 0.10, M["acero2"])
+ horquilla.location = (X_COR, -1.20, 0)
+ horquilla.rotation_euler = (math.pi / 2, 0, 0)
+ horquilla.parent = entrada
+
+ barras = {}
+ for nom, c, z in (("izq", "rosa", -0.92), ("der", "verde", -0.60)):
+ riel = cilindro(f"riel_{nom}", 0.038, 1.50, material(f"mr_{nom}", "riel", rug=0.6))
+ riel.rotation_euler = (0, math.pi / 2, 0)
+ riel.location = (0.90, -0.6, z)
+ b = cilindro(f"bar_{nom}", 0.060, 1.0, material(f"mb_{nom}", c, emis=2.6))
+ b.rotation_euler = (0, math.pi / 2, 0)
+ barras[nom] = (riel, b, z)
+ riel.scale = b.scale = (0, 0, 0)
+ fecha = texto("1827", tam=0.44, color="ambar")
+ fecha.scale = (0, 0, 0)
+
+ def emision(mat, v, col=None):
+ n = mat.node_tree.nodes["Principled BSDF"]
+ n.inputs["Emission Strength"].default_value = v
+ if col is not None:
+ n.inputs["Emission Color"].default_value = (*srgb(col), 1)
+
+ return dict(cam=cam, M=M, emision=emision, piso=piso, arcos=arcos,
+ ruedas_a=ruedas_a, eje_a=eje_a, patina=patina, dif=dif, lat=lat,
+ jaula=jaula, sat=sat, entrada=entrada, pinon=pinon,
+ barras=barras, fecha=fecha)
+
+
+def main():
+ T = Tiempo(NOMBRE)
+ ob = construir(T)
+ M, barras = ob["M"], ob["barras"]
+
+ def actualizar(f):
+ t = T.t(f)
+ baja = suave(T.p(f, 2) / 0.62)
+ dist = 50.0 / 36.0 * alto_en(T, f)
+ apuntar(ob["cam"], Vector((0.0, -1.1, 8.6)).lerp(DIR_CAM * dist, baja),
+ Vector((0.0, 0.0, 0.0)).lerp(Vector((0.0, 0.0, -0.50)), baja))
+
+ # ---- vista de arriba
+ vis_g = 1.0 - suave(T.p(f, 2) / 0.45)
+ ob["piso"].scale = (vis_g, vis_g, vis_g)
+ if vis_g > 0.001:
+ av = suave(T.p(f, 0) * 0.62) if f < T.rango(1)[0] else \
+ mezcla(0.62, 1.0, T.p(f, 1))
+ th = -0.60 + 1.20 * av
+ centros = []
+ for k, (cu, pts) in enumerate(ob["arcos"]):
+ rr = RADIO_GIRO + (TROCHA / 2) * (1 if k else -1)
+ c = Vector((-RADIO_GIRO + rr * math.cos(th), rr * math.sin(th), R_RUEDA))
+ centros.append(c)
+ n = max(2, int(120 * av))
+ rehacer_curva(cu, [pts], [[1.0 if i <= n else 0.30 for i in range(120)]])
+ giro = (rr * (th + 0.60)) / R_RUEDA
+ if f >= T.rango(1)[0]:
+ rig = (RADIO_GIRO + TROCHA / 2) * (th + 0.60) / R_RUEDA
+ giro = mezcla(giro, rig, suave(T.p(f, 1) / 0.35))
+ r = ob["ruedas_a"][k]
+ r.location = c
+ r.rotation_euler = (giro, 0, th) # rueda sobre su eje y apunta al centro
+ medio = (centros[0] + centros[1]) / 2
+ ob["piso"].location = (-medio.x, -medio.y - 0.30, 0.0)
+ ob["eje_a"].location = medio
+ ob["eje_a"].rotation_euler = (0, math.pi / 2, th)
+ pv = suave(T.p(f, 1) / 0.4)
+ rr = RADIO_GIRO - TROCHA / 2
+ pts, rad = [], []
+ for i in range(60):
+ a2 = -0.60 + (th + 0.60) * i / 59.0
+ pts.append((-RADIO_GIRO + rr * math.cos(a2), rr * math.sin(a2), 0.04))
+ rad.append(pv * min(1.0, (i / 59.0) * 2.4))
+ rehacer_curva(ob["patina"], [pts], [rad])
+
+ # ---- el diferencial
+ ap = suave((T.p(f, 2) - 0.35) / 0.5)
+ ob["dif"].scale = (ap, ap, ap)
+ ob["dif"].rotation_euler = (0, INCL, 0)
+ arranca = T.rango(6)[0]
+ tt = max(0.0, t - T.t(arranca))
+ k_giro = mezcla(0.0, 0.42, T.p(f, 8) / 0.7)
+ ang_j = OMEGA * tt if f >= arranca else 0.0
+ ang_i = ang_j * (1.0 - k_giro)
+ ang_d = ang_j * (1.0 + k_giro)
+ ob["jaula"].rotation_euler = (ang_j, 0, 0)
+ ob["lat"]["izq"].rotation_euler = (ang_i, 0, 0)
+ ob["lat"]["der"].rotation_euler = (ang_d, 0, 0)
+ for k, g in enumerate(ob["sat"]):
+ g.rotation_euler = (0, 0, (ang_i - ang_d) / 2 * (1 if k == 0 else -1))
+ ob["pinon"].rotation_euler = (0, P_SIG * ang_j * N_COR / N_PIN, 0)
+
+ # ---- resaltados en los colores del canal, sobre el acero
+ def pico(i):
+ p = T.p(f, i)
+ return 0.85 * math.sin(math.pi * p) ** 2 if 0.0 < p < 1.0 else 0.0
+ ob["emision"](M["eje_i"], pico(4), "rosa")
+ ob["emision"](M["eje_d"], pico(4), "verde")
+ ob["emision"](M["acero2"], pico(5), "celeste")
+ ob["emision"](M["fundido"], pico(5), "ambar")
+ ob["emision"](M["acero"], pico(6), "ambar")
+
+ # ---- barras de velocidad
+ bv = suave(T.p(f, 9) / 0.4) * (1.0 - suave(T.p(f, 10) / 0.3))
+ for nom, (riel, bar, z) in barras.items():
+ riel.scale = (bv, bv, bv)
+ rel = (1.0 - k_giro) if nom == "izq" else (1.0 + k_giro)
+ L = 1.50 * rel / 1.5
+ bar.scale = (bv, bv, bv * L)
+ bar.location = (0.15 + L / 2, -0.6, z)
+ fv = suave((T.p(f, 10) - 0.25) / 0.4)
+ ob["fecha"].location = (0.90, 0.0, -0.80)
+ ob["fecha"].scale = (fv, fv, fv)
+
+ render_secuencia(NOMBRE, T, actualizar)
+
+
+main()