From 59355909f2de9236af8168a26c70bcf6caa3b285 Mon Sep 17 00:00:00 2001 From: Elvis Claros Castro Date: Sat, 26 Sep 2026 20:21:47 -0300 Subject: Import video pipeline as it was --- blender/diferencial.py | 304 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 blender/diferencial.py (limited to 'blender/diferencial.py') 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() -- cgit v1.2.3