![]() |
|
Frage Launchingexperimente in Blender/Phyton - Druckversion +- Blenderforum (https://blender-forum.de/forum) +-- Forum: Blender Hilfe (https://blender-forum.de/forum/forumdisplay.php?fid=4) +--- Forum: Support (https://blender-forum.de/forum/forumdisplay.php?fid=5) +--- Thema: Frage Launchingexperimente in Blender/Phyton (/showthread.php?tid=898) |
Launchingexperimente in Blender/Phyton - Maurice - 24.03.2025 Moin! Ich schreibe gerade meine Bachelorarbeit in Psychologie und versuche für mein Experiment Stimuli in Blender zu erstellen. Es geht um ein Launching experiment also naives Physikverständniss. Bei den Stimuli werden in verschiedenen Geschwindigkeiten Billiardkugeln aufeinander geschossen, und mit eyetracking beobachten wir dann, wie gut die Teilnehmer bei welcher Geschwindigkeit Folgen können. Hier mein Problem: Ich habe bereits einige Stimuli mit dem Rigid body-system erstellt. Für mein Vorexperiment würde ich allerdings auch gerne mit dem Phyton package Pooltool Blends erstellen, um den Realismus zu vergleichen. Leider schaffe ich es nicht, meinen Code in Blender zum Laufen zu bringen. Das Problem ist die Geschwindigkeit V0. Für mein Experiment muss ich V0 verändern können, leider passiert allerdings nichts, wenn ich V0 veränder. Vielen Dank das ihr euch mein Problem angehört habt, ich hoffe irgendjemand kann mir weiterhelfen. Das ist die Blender datei: Und hier ist mein Code: import bpy import math import numpy as np # --- Minimaldefinitionen für Konstanten und Hilfsfunktionen --- class const: stationary = 0 rolling = 1 spinning = 2 pocketed = 3 nontranslating = [stationary, pocketed] tol = 1e-6 english_fraction = 0.5 numba_cache = False # Platzhalter # Minimal umgesetzte Utility-Funktionen (stark vereinfacht): def unit_vector_fast(v): norm = np.linalg.norm(v) return v if norm < const.tol else v / norm def coordinate_rotation_fast(v, angle): # Dreht den Vektor (oder alle Zeilen eines 2D-Arrays) um 'angle' im xy-Plan. c, s = np.cos(angle), np.sin(angle) if v.ndim == 1: x, y, z = v return np.array([x * c - y * s, x * s + y * c, z]) else: R = np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]]) return np.dot(v, R.T) def angle_fast(v): # Gibt den Winkel (im Bogenmaß) des Vektors im xy-Plan zurück. return np.arctan2(v[1], v[0]) if np.linalg.norm(v[:2]) > const.tol else 0 def get_rel_velocity_fast(rvw, R): # Für diese Simulation nehmen wir an, dass die relative Geschwindigkeit # einfach der linearen Geschwindigkeit entspricht. return rvw[1] def cross_fast(a, b): return np.cross(a, b) def quadratic_fast(A, B, C): # Löst die quadratische Gleichung: A*t^2 + B*t + C = 0 disc = B**2 - 4 * A * C if disc < 0 or abs(A) < const.tol: return np.inf, np.inf t1 = (-B + math.sqrt(disc)) / (2 * A) t2 = (-B - math.sqrt(disc)) / (2 * A) return t1, t2 # --- Funktionen aus deinem Code (vereinfacht) --- def resolve_ball_ball_collision(rvw1, rvw2): """Berechnet eine ideale elastische Kollision zweier Kugeln gleicher Masse.""" r1, r2 = rvw1[0], rvw2[0] v1, v2 = rvw1[1], rvw2[1] v_rel = v1 - v2 v_mag = np.linalg.norm(v_rel) n = unit_vector_fast(r2 - r1) t = coordinate_rotation_fast(n, np.pi / 2) beta = abs(angle_fast(v_rel) - angle_fast(n)) # Konvertiere ggf. zu float64 (wichtig auf Windows) rvw1 = rvw1.astype(np.float64) rvw2 = rvw2.astype(np.float64) rvw1[1] = t * v_mag * np.sin(beta) + v2 rvw2[1] = n * v_mag * np.cos(beta) + v2 return rvw1, rvw2 def get_ball_ball_collision_coeffs(rvw1, rvw2, s1, s2, mu1, mu2, m1, m2, g1, g2, R): # Vereinfachter Ansatz: Differenzen der Positionen und Geschwindigkeiten c1x, c1y = rvw1[0, 0], rvw1[0, 1] c2x, c2y = rvw2[0, 0], rvw2[0, 1] if s1 in const.nontranslating: b1x, b1y = 0, 0 else: phi1 = angle_fast(rvw1[1]) v1 = np.linalg.norm(rvw1[1]) b1x, b1y = v1 * np.cos(phi1), v1 * np.sin(phi1) if s2 in const.nontranslating: b2x, b2y = 0, 0 else: phi2 = angle_fast(rvw2[1]) v2 = np.linalg.norm(rvw2[1]) b2x, b2y = v2 * np.cos(phi2), v2 * np.sin(phi2) Bx, By = b2x - b1x, b2y - b1y Cx, Cy = c2x - c1x, c2y - c1y # Quadratisches Modell: (B*t + C)^2 = (2R)^2 A = Bx**2 + By**2 B = 2 * (Bx * Cx + By * Cy) C = Cx**2 + Cy**2 - (2 * R) ** 2 return A, B, C, 0, 0 # d und e nicht benötigt def get_ball_ball_collision_time(rvw1, rvw2, s1, s2, mu1, mu2, m1, m2, g1, g2, R): A, B, C, _, _ = get_ball_ball_collision_coeffs( rvw1, rvw2, s1, s2, mu1, mu2, m1, m2, g1, g2, R ) if abs(A) < const.tol: return np.inf disc = B**2 - 4 * A * C if disc < 0: return np.inf t1 = (-B + math.sqrt(disc)) / (2 * A) t2 = (-B - math.sqrt(disc)) / (2 * A) roots = [t for t in [t1, t2] if t > const.tol] return min(roots) if roots else np.inf def evolve_state_motion(state, rvw, R, m, u_s, u_sp, u_r, g, t): """Einfache Zustandsentwicklung: Wir gehen hier von einer gleichförmigen Bewegung aus.""" new_r = rvw[0] + rvw[1] * t # Geschwindigkeit und Spin bleiben gleich. new_rvw = np.array([new_r, rvw[1], rvw[2]]) return new_rvw, state def cue_strike(m, M, R, V0, phi, theta, a, b): """ Simuliert einen Queue-Stoß. Bei theta=0 und a=b=0 erhält man einen Stoß, der den Ball in Richtung (F/m)*[sin(phi+pi/2), -cos(phi+pi/2)] schickt. """ # Skaliere english-Effekt (hier ohne Einfluss, da a=b=0) a *= R * const.english_fraction b *= R * const.english_fraction # Umrechnung in Bogenmaß phi = phi * np.pi / 180 theta = theta * np.pi / 180 I = 2 / 5 * m * R**2 # Berechne einen fiktiven Impuls F F = 2 * M * V0 / (1 + m / M) # Ballgeschwindigkeit im Ball (nur im y-Anteil, da theta=0) v_B = -F / m * np.array([0, np.cos(theta), 0]) # Rotation ins Tischkoordinatensystem rot_angle = phi + np.pi / 2 v_T = coordinate_rotation_fast(v_B, rot_angle) # Für den Spin (ohne English-Effekte bleibt er hier 0) w_T = np.zeros(3) return v_T, w_T """TODO: Apply states to initial, intermediate and final positions in blender => Init parameters are set in scene Collision @ frame 40 Number of frames: Time of collision * 24 (+ delay after stimuli has been presented ~4 frames ± 0.1 sec) Key-Frames: - initial state is given - pre-collision state has to be computed with `evolve_state_motion` (Frame: Collision-1) - final state computed via `resolve_collision` (Frame: Collision-cont) Remarks: - blue ball is hit by cue """ blue = bpy.data.objects["blueball"] red = bpy.data.objects["redball"] ground = bpy.data.objects["Ground"] cue = bpy.data.objects["cue"] pos_blue = np.array([-0.211807, 0, 0.0285]) pos_red = np.array([0, 0, 0.0285]) directional_vector = pos_red - pos_blue distance = np.linalg.norm(directional_vector) phi = np.degrees(np.arctan2(directional_vector[1], directional_vector[0])) theta = 0 # Elevationswinkel (Grad) a = 0 # Seiteneffekt (English) b = 0 # Vertikaler English g = 9.81 # Erdbeschleunigung (m/s²) u_s = 0.2 # Gleitreibungskoeffizient => friction ground u_sp = 0.01 # Spin-Reibungskoeffizient u_r = 0.02 # Rollreibungskoeffizient # Ball2 ist stationär: vel2 = np.array([0.0, 0.0, 0.0]) spin2 = np.array([0.0, 0.0, 0.0]) rvw2 = np.array([pos_red, vel2, spin2]) # cue_strength = 2 * distance v0 = distance m = red.rigid_body.mass # Kugelmasse (kg) M = cue.rigid_body.mass # Masse des Queues (kg) radius = 0.0285 # Kugelradius (m) # Ball1 wird vom Queue getroffen: v_T, w_T = cue_strike(m, M, radius, v0, phi, theta, a, b) vel1 = v_T spin1 = w_T rvw1 = np.array([pos_blue, vel1, spin1]) # Zustände (hier: Ball1 rollt, Ball2 ist stationär) s1 = const.rolling s2 = const.stationary # Berechne Kollisionszeitpunkt collision_time = get_ball_ball_collision_time( rvw1, rvw2, s1, s2, u_r, u_r, m, m, g, g, radius ) if collision_time == np.inf: print("\nKeine Kollision vorhergesagt.") else: print("\nVorhergesagte Kollisionszeit: {:.4f} s".format(collision_time)) print("Number of frames: ", round(FPS * collision_time * SCALING)) collision_frame = round( FPS * collision_time * SCALING ) # TODO: this factor needs to be removed MAX_FRAMES = 2 * collision_frame + 4 # allow short adjustment # Zustandsentwicklung bis zur Kollision (hier bewegt sich vorwiegend Ball1) rvw1_collision, _ = evolve_state_motion( s1, rvw1, radius, m, u_s, u_sp, u_r, g, collision_time ) # Kollisionsauflösung rvw1_post, rvw2_post = resolve_ball_ball_collision( rvw1_collision, rvw2 ) # r=position, w=rotation, v=velocity # Init # cube.hide_set(False) # TODO: set visibility # cube.hide_viewport = True # cube.hide_render = True blue.lock_rotation[0] = False blue.lock_rotation[1] = True blue.lock_rotation[2] = True red.lock_rotation[0] = False red.lock_rotation[1] = True red.lock_rotation[2] = True bpy.app.handlers.frame_change_pre.clear() # INFO: Init ball location blue.location = [-0.211807, 0, 0.0285] blue.keyframe_insert("location", frame=0) # blue.rigid_body.kinematic = False # blue.keyframe_insert("rigid_body.kinematic", frame=0) red.location = [0, 0, 0.0285] red.keyframe_insert("location", frame=0) # red.rigid_body.kinematic = False # red.keyframe_insert("rigid_body.kinematic", frame=0) # INFO: SETUP cue.location = blue.location cue.location[0] -= radius * 2 + v0 cue.rigid_body.kinematic = True cue.keyframe_insert("rigid_body.kinematic", frame=0) cue.keyframe_insert("location", frame=0) # INFO: COLLISION CUE & BALL cue.location = blue.location # cue.location[0] -= radius cue.keyframe_insert("location", frame=1) # cue.rigid_body.kinematic = False # cue.keyframe_insert("rigid_body.kinematic", frame=1) # Pre-collision blue.location = rvw1_collision[0] blue.keyframe_insert("location", frame=collision_frame) # Resolved blue.location = rvw1_post[0] blue.keyframe_insert("location", frame=MAX_FRAMES) bpy.context.scene.rigidbody_world.enabled = True bpy.context.scene.frame_start = 1 bpy.context.scene.frame_end = MAX_FRAMES bpy.ops.screen.animation_play() |