updated versions

This commit is contained in:
2025-07-09 16:39:51 +03:00
parent 507f526433
commit 35d3bb8442
21 changed files with 23620 additions and 136873 deletions

View File

@@ -0,0 +1,332 @@
import json
import numpy as np
import os
from datetime import datetime
import math
# Blender dependencies with fallback handling
try:
import bpy
import bmesh
from mathutils import Vector, Euler
BLENDER_AVAILABLE = True
except ImportError:
BLENDER_AVAILABLE = False
print("Warning: Blender (bpy) not available. This module requires Blender to be installed with Python API access.")
class BlenderGPSAnimator:
"""
Advanced GPS track animation using Blender for high-quality 3D rendering
"""
def __init__(self, output_folder):
self.output_folder = output_folder
if BLENDER_AVAILABLE:
self.setup_blender_scene()
else:
raise ImportError("Blender (bpy) is not available. Please install Blender with Python API access.")
def check_dependencies(self):
"""Check if Blender dependencies are available"""
if not BLENDER_AVAILABLE:
raise ImportError("Blender (bpy) is not available. Please install Blender with Python API access.")
return True
def setup_blender_scene(self):
"""Setup Blender scene for GPS animation"""
# Clear existing mesh objects
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
# Add camera
bpy.ops.object.camera_add(location=(0, 0, 10))
self.camera = bpy.context.object
# Add sun light
bpy.ops.object.light_add(type='SUN', location=(0, 0, 20))
light = bpy.context.object
light.data.energy = 5
# Setup world environment
world = bpy.context.scene.world
world.use_nodes = True
env_texture = world.node_tree.nodes.new('ShaderNodeTexEnvironment')
world.node_tree.links.new(env_texture.outputs[0], world.node_tree.nodes['Background'].inputs[0])
# Set render settings
scene = bpy.context.scene
scene.render.engine = 'CYCLES'
scene.render.resolution_x = 1920
scene.render.resolution_y = 1080
scene.render.fps = 30
scene.cycles.samples = 64
def load_gps_data(self, positions_file):
"""Load GPS data from JSON file"""
with open(positions_file, 'r') as f:
positions = json.load(f)
# Convert to numpy array for easier processing
coords = []
times = []
speeds = []
for pos in positions:
coords.append([pos['longitude'], pos['latitude'], pos.get('altitude', 0)])
times.append(pos['fixTime'])
speeds.append(pos.get('speed', 0) * 1.852) # Convert to km/h
return np.array(coords), times, speeds
def create_terrain_mesh(self, coords):
"""Create a simple terrain mesh based on GPS bounds"""
# Calculate bounds
min_lon, min_lat = coords[:, :2].min(axis=0)
max_lon, max_lat = coords[:, :2].max(axis=0)
# Expand bounds slightly
padding = 0.001
min_lon -= padding
min_lat -= padding
max_lon += padding
max_lat += padding
# Create terrain mesh
bpy.ops.mesh.primitive_plane_add(size=2, location=(0, 0, 0))
terrain = bpy.context.object
terrain.name = "Terrain"
# Scale terrain to match GPS bounds
lon_range = max_lon - min_lon
lat_range = max_lat - min_lat
scale_factor = max(lon_range, lat_range) * 100000 # Convert to reasonable scale
terrain.scale = (scale_factor, scale_factor, 1)
# Apply material
mat = bpy.data.materials.new(name="TerrainMaterial")
mat.use_nodes = True
mat.node_tree.nodes.clear()
# Add principled BSDF
bsdf = mat.node_tree.nodes.new(type='ShaderNodeBsdfPrincipled')
bsdf.inputs['Base Color'].default_value = (0.2, 0.5, 0.2, 1.0) # Green
bsdf.inputs['Roughness'].default_value = 0.8
material_output = mat.node_tree.nodes.new(type='ShaderNodeOutputMaterial')
mat.node_tree.links.new(bsdf.outputs['BSDF'], material_output.inputs['Surface'])
terrain.data.materials.append(mat)
return terrain
def create_gps_track_mesh(self, coords):
"""Create a 3D mesh for the GPS track"""
# Normalize coordinates to Blender scale
coords_normalized = self.normalize_coordinates(coords)
# Create curve from GPS points
curve_data = bpy.data.curves.new('GPSTrack', type='CURVE')
curve_data.dimensions = '3D'
curve_data.bevel_depth = 0.02
curve_data.bevel_resolution = 4
# Create spline
spline = curve_data.splines.new('BEZIER')
spline.bezier_points.add(len(coords_normalized) - 1)
for i, coord in enumerate(coords_normalized):
point = spline.bezier_points[i]
point.co = coord
point.handle_left_type = 'AUTO'
point.handle_right_type = 'AUTO'
# Create object from curve
track_obj = bpy.data.objects.new('GPSTrack', curve_data)
bpy.context.collection.objects.link(track_obj)
# Apply material
mat = bpy.data.materials.new(name="TrackMaterial")
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
bsdf.inputs['Base Color'].default_value = (1.0, 0.0, 0.0, 1.0) # Red
bsdf.inputs['Emission'].default_value = (1.0, 0.2, 0.2, 1.0)
bsdf.inputs['Emission Strength'].default_value = 2.0
track_obj.data.materials.append(mat)
return track_obj
def create_vehicle_model(self):
"""Create a simple vehicle model"""
# Create a simple car shape using cubes
bpy.ops.mesh.primitive_cube_add(size=0.1, location=(0, 0, 0.05))
vehicle = bpy.context.object
vehicle.name = "Vehicle"
vehicle.scale = (2, 1, 0.5)
# Apply material
mat = bpy.data.materials.new(name="VehicleMaterial")
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
bsdf.inputs['Base Color'].default_value = (0.0, 0.0, 1.0, 1.0) # Blue
bsdf.inputs['Metallic'].default_value = 0.5
bsdf.inputs['Roughness'].default_value = 0.2
vehicle.data.materials.append(mat)
return vehicle
def normalize_coordinates(self, coords):
"""Normalize GPS coordinates to Blender scale"""
# Center coordinates
center = coords.mean(axis=0)
coords_centered = coords - center
# Scale to reasonable size for Blender
scale_factor = 100
coords_scaled = coords_centered * scale_factor
# Convert to Vector objects
return [Vector((x, y, z)) for x, y, z in coords_scaled]
def animate_vehicle(self, vehicle, coords, times, speeds):
"""Create animation keyframes for vehicle movement"""
coords_normalized = self.normalize_coordinates(coords)
scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = len(coords_normalized) * 2 # 2 frames per GPS point
for i, (coord, speed) in enumerate(zip(coords_normalized, speeds)):
frame = i * 2 + 1
# Set location
vehicle.location = coord
vehicle.keyframe_insert(data_path="location", frame=frame)
# Calculate rotation based on direction
if i < len(coords_normalized) - 1:
next_coord = coords_normalized[i + 1]
direction = next_coord - coord
if direction.length > 0:
direction.normalize()
# Calculate rotation angle
angle = math.atan2(direction.y, direction.x)
vehicle.rotation_euler = Euler((0, 0, angle), 'XYZ')
vehicle.keyframe_insert(data_path="rotation_euler", frame=frame)
# Set interpolation mode
if vehicle.animation_data:
for fcurve in vehicle.animation_data.action.fcurves:
for keyframe in fcurve.keyframe_points:
keyframe.interpolation = 'BEZIER'
def animate_camera(self, coords):
"""Create smooth camera animation following the vehicle"""
coords_normalized = self.normalize_coordinates(coords)
# Create camera path
for i, coord in enumerate(coords_normalized):
frame = i * 2 + 1
# Position camera above and behind the vehicle
offset = Vector((0, -2, 3))
cam_location = coord + offset
self.camera.location = cam_location
self.camera.keyframe_insert(data_path="location", frame=frame)
# Look at the vehicle
direction = coord - cam_location
if direction.length > 0:
rot_quat = direction.to_track_quat('-Z', 'Y')
self.camera.rotation_euler = rot_quat.to_euler()
self.camera.keyframe_insert(data_path="rotation_euler", frame=frame)
def add_particles_effects(self, vehicle):
"""Add particle effects for enhanced visuals"""
# Add dust particles
bpy.context.view_layer.objects.active = vehicle
bpy.ops.object.modifier_add(type='PARTICLE_SYSTEM')
particles = vehicle.modifiers["ParticleSystem"].particle_system
particles.settings.count = 100
particles.settings.lifetime = 30
particles.settings.emit_from = 'FACE'
particles.settings.physics_type = 'NEWTON'
particles.settings.effector_weights.gravity = 0.1
# Set material for particles
particles.settings.material = 1
def render_animation(self, output_path, progress_callback=None):
"""Render the animation to video"""
scene = bpy.context.scene
# Set output settings
scene.render.filepath = output_path
scene.render.image_settings.file_format = 'FFMPEG'
scene.render.ffmpeg.format = 'MPEG4'
scene.render.ffmpeg.codec = 'H264'
# Render animation
total_frames = scene.frame_end - scene.frame_start + 1
for frame in range(scene.frame_start, scene.frame_end + 1):
scene.frame_set(frame)
# Render frame
frame_path = f"{output_path}_{frame:04d}.png"
scene.render.filepath = frame_path
bpy.ops.render.render(write_still=True)
# Update progress
if progress_callback:
progress = ((frame - scene.frame_start) / total_frames) * 100
progress_callback(progress, f"Rendering frame {frame}/{scene.frame_end}")
def create_gps_animation(self, positions_file, output_path, progress_callback=None):
"""Main method to create GPS animation in Blender"""
try:
# Load GPS data
coords, times, speeds = self.load_gps_data(positions_file)
# Create scene elements
terrain = self.create_terrain_mesh(coords)
track = self.create_gps_track_mesh(coords)
vehicle = self.create_vehicle_model()
# Create animations
self.animate_vehicle(vehicle, coords, times, speeds)
self.animate_camera(coords)
# Add effects
self.add_particles_effects(vehicle)
# Render animation
self.render_animation(output_path, progress_callback)
return True
except Exception as e:
print(f"Error creating Blender animation: {e}")
if progress_callback:
progress_callback(-1, f"Error: {e}")
return False
def generate_blender_animation(positions_file, output_folder, progress_callback=None):
"""
Convenience function to generate Blender animation
"""
animator = BlenderGPSAnimator(output_folder)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = os.path.join(output_folder, f"blender_animation_{timestamp}")
success = animator.create_gps_animation(positions_file, output_path, progress_callback)
return f"{output_path}.mp4" if success else None
# Note: This script should be run from within Blender's Python environment
# or with Blender as a Python module (bpy)