Merge pull request #15 from Block-Party-VR/14-create-an-animation-studio-tool-for-generating-animations-on-the-board
14 create an animation studio tool for generating animations on the board
This commit is contained in:
@@ -13,7 +13,7 @@ namespace AnimationHelpers{
|
||||
V3D<uint8_t> cyan{0,255,255};
|
||||
V3D<uint8_t> magenta{255,0,255};
|
||||
|
||||
Cell CreateCell(float x_percent, float y_percent, float z_percent, V3D<uint8_t> &color){
|
||||
Cell CreateCell(float x_percent, float y_percent, float z_percent, const V3D<uint8_t> &color){
|
||||
float continuousMaxValue{static_cast<float>(std::numeric_limits<uint32_t>::max())};
|
||||
Cell cell{
|
||||
.position = V3D<uint32_t>{
|
||||
@@ -211,11 +211,42 @@ namespace RisingCubes{
|
||||
.delay = std::chrono::milliseconds(1)
|
||||
};
|
||||
|
||||
AnimationFrame frame00{
|
||||
.frame = {
|
||||
CreateCell(0.0,0.0,1.0,V3D<uint8_t>(128.0,128.0,255.0)),
|
||||
CreateCell(0.5,0.0,1.0,V3D<uint8_t>(128.0,128.0,255.0)),
|
||||
CreateCell(1.0,0.0,0.0,V3D<uint8_t>(255.0,118.0,205.0)),
|
||||
CreateCell(1.0,0.0,0.5,V3D<uint8_t>(255.0,118.0,205.0)),
|
||||
CreateCell(1.0,0.0,1.0,V3D<uint8_t>(255.0,116.0,0.0)),
|
||||
CreateCell(1.0,0.5,1.0,V3D<uint8_t>(183.0,0.0,255.0)),
|
||||
CreateCell(1.0,1.0,1.0,V3D<uint8_t>(183.0,0.0,255.0))
|
||||
},
|
||||
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
|
||||
.frameInterpolation = FrameInterpolation::FADE,
|
||||
.delay = std::chrono::milliseconds(1000)
|
||||
};
|
||||
|
||||
AnimationFrame frame01{
|
||||
.frame = {
|
||||
CreateCell(0.0,0.0,1.0,V3D<uint8_t>(255.0,255.0,171.0)),
|
||||
CreateCell(0.5,0.0,1.0,V3D<uint8_t>(255.0,255.0,171.0)),
|
||||
CreateCell(1.0,0.0,0.0,V3D<uint8_t>(0.0,195.0,88.0)),
|
||||
CreateCell(1.0,0.0,0.5,V3D<uint8_t>(0.0,195.0,88.0)),
|
||||
CreateCell(1.0,0.0,1.0,V3D<uint8_t>(0.0,195.0,88.0)),
|
||||
CreateCell(1.0,0.5,1.0,V3D<uint8_t>(112.0,222.0,255.0)),
|
||||
CreateCell(1.0,1.0,1.0,V3D<uint8_t>(112.0,222.0,255.0))
|
||||
},
|
||||
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
|
||||
.frameInterpolation = FrameInterpolation::FADE,
|
||||
.delay = std::chrono::milliseconds(1000)
|
||||
};
|
||||
|
||||
std::vector<AnimationFrame> rising{
|
||||
frame1, // 0
|
||||
frame2, // 1
|
||||
frame3, // 2
|
||||
frame4, // 3
|
||||
frame5
|
||||
frame00, frame01, frame00
|
||||
// frame1, // 0
|
||||
// frame2, // 1
|
||||
// frame3, // 2
|
||||
// frame4, // 3
|
||||
// frame5
|
||||
};
|
||||
}
|
||||
@@ -7,34 +7,37 @@
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = esp32s3_release ; this ensures that only this environment is built for anything but the debug build
|
||||
default_envs = esp32s3_release
|
||||
extra_configs = platformio-local.ini
|
||||
|
||||
[env]
|
||||
; platform = espressif32
|
||||
upload_protocol = esptool
|
||||
platform = https://github.com/platformio/platform-espressif32.git
|
||||
board = esp32-s3-devkitc-1
|
||||
framework = arduino
|
||||
build_flags = -Iinclude
|
||||
|
||||
monitor_speed = 9600
|
||||
monitor_filters = colorize, send_on_enter
|
||||
upload_speed = 2000000 ;ESP32S3 USB-Serial Converter maximum 2000000bps
|
||||
lib_deps = adafruit/Adafruit NeoPixel@^1.12.0
|
||||
upload_speed = 2000000
|
||||
lib_deps =
|
||||
adafruit/Adafruit NeoPixel@^1.12.3
|
||||
fastled/FastLED@^3.7.3
|
||||
|
||||
|
||||
[env:esp32s3_release]
|
||||
build_type = release
|
||||
monitor_port = COM21
|
||||
upload_port = COM20
|
||||
|
||||
[env:esp32s3_debug]
|
||||
debug_init_break = tbreak setup
|
||||
debug_tool = esp-builtin
|
||||
build_type = debug
|
||||
debug_speed = 20000
|
||||
; debug_port = COM7
|
||||
; monitor_port = COM14
|
||||
build_flags =
|
||||
-D DEBUG = 1
|
||||
-I include
|
||||
-D DEBUG = 1
|
||||
-I include
|
||||
monitor_port = COM21
|
||||
upload_port = COM20
|
||||
|
||||
@@ -30,9 +30,10 @@
|
||||
TaskHandle_t updateCommunicaitonTask;
|
||||
TaskHandle_t updateBoardTask;
|
||||
|
||||
std::array<std::vector<AnimationFrame>*, 2> animations = {
|
||||
// WARNING! This array size should always be equal to the number of entries in it!!
|
||||
std::array<std::vector<AnimationFrame>*, 1> animations = {
|
||||
&RisingCubes::rising,
|
||||
&RotatingCubes::rotating,
|
||||
// &RotatingCubes::rotating,
|
||||
};
|
||||
|
||||
// BluetoothSerial SerialBT;
|
||||
@@ -112,6 +113,7 @@ void parseData(Message<SERIAL_CHAR_LENGTH, SERIAL_ARG_LENGTH> &message){
|
||||
}
|
||||
case Commands::PING:{
|
||||
GlobalPrint::Println("!" + String(Commands::PING) + ";");
|
||||
boardManager.PrintColorState();
|
||||
break;
|
||||
}
|
||||
case Commands::SetStackColors:{
|
||||
|
||||
3
tools/animation-tools/.gitignore
vendored
Normal file
3
tools/animation-tools/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
venv/
|
||||
__pycache__/
|
||||
output.txt
|
||||
78
tools/animation-tools/AnimationExporter.py
Normal file
78
tools/animation-tools/AnimationExporter.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from Scene import Scene
|
||||
from enum import StrEnum
|
||||
from pygame.math import Vector3
|
||||
from Shapes import Mesh
|
||||
|
||||
class FillInterpolation(StrEnum):
|
||||
NO_FILL = "NO_FILL"
|
||||
CLOSEST_COLOR = "CLOSEST_COLOR"
|
||||
LINEAR_WEIGHTED_DISTANCE = "LINEAR_WEIGHTED_DISTANCE"
|
||||
SQUARE_WEIGHTED_DISTANCE = "SQUARE_WEIGHTED_DISTANCE"
|
||||
# options = [NO_FILL, CLOSEST_COLOR, LINEAR_WEIGHTED_DISTANCE, SQUARE_WEIGHTED_DISTANCE]
|
||||
|
||||
class FrameInterpolation(StrEnum):
|
||||
SNAP = "SNAP"
|
||||
FADE = "FADE"
|
||||
# options = [SNAP, FADE]
|
||||
|
||||
class Cell:
|
||||
def __init__(self, position: Vector3, color: Vector3):
|
||||
self.position: Vector3 = position
|
||||
self.color: Vector3 = color
|
||||
|
||||
def to_string(self):
|
||||
message = f"CreateCell({self.position.x},{self.position.y},{self.position.z}"
|
||||
message += f",V3D<uint8_t>({self.color.x},{self.color.y},{self.color.z})),"
|
||||
return message
|
||||
|
||||
class AnimationFrame:
|
||||
def __init__(self, cells: list[Cell], fill_interpolation: FillInterpolation, frame_interpolation: FrameInterpolation, delay: int):
|
||||
self.cells = cells
|
||||
self.fill = fill_interpolation
|
||||
self.frame_interp = frame_interpolation
|
||||
self.delay = delay
|
||||
|
||||
def to_string(self, frame_number: int):
|
||||
cell_str = ""
|
||||
for i, cell in enumerate(self.cells):
|
||||
if cell.color.x != 0 or cell.color.y != 0 or cell.color.z != 0:
|
||||
if i != 0:
|
||||
cell_str += "\t\t"
|
||||
cell_str += cell.to_string() + "\n\t"
|
||||
cell_str = cell_str[:-3]
|
||||
message = f"""
|
||||
AnimationFrame frame{frame_number}{{
|
||||
.frame = {{
|
||||
{cell_str}
|
||||
}},
|
||||
.fillInterpolation = FillInterpolation::{self.fill},
|
||||
.frameInterpolation = FrameInterpolation::{self.frame_interp},
|
||||
.delay = std::chrono::milliseconds({self.delay})
|
||||
}};
|
||||
"""
|
||||
return message
|
||||
|
||||
|
||||
#TODO: Impliment this
|
||||
def generate_animation_frame_struct(animation_types_path: str):
|
||||
with open(animation_types_path, 'r') as AnimationTypes:
|
||||
with open("AnimationTypes.py", 'w') as output:
|
||||
pass
|
||||
|
||||
def mesh_to_cell(mesh: Mesh) -> Cell:
|
||||
if mesh.face_color[0] != 0 or mesh.face_color[1] != 0 or mesh.face_color[2] != 0:
|
||||
pass
|
||||
pos = (mesh.get_average_position() + Vector3(1,1,1)) / 2
|
||||
# need to swap z and y for the coordinate system
|
||||
z = pos.y
|
||||
pos.y = pos.z
|
||||
pos.z = z
|
||||
testVector = Vector3(0.1,0.5,0.9)
|
||||
|
||||
cell = Cell(pos, Vector3(mesh.face_color))
|
||||
return cell
|
||||
|
||||
def scene_to_frame(scene: Scene, fill_interpolation: FillInterpolation, frame_interpolation: FrameInterpolation, delay: int, scene_number: int) -> str:
|
||||
cells = [mesh_to_cell(cube) for cube in scene.meshes]
|
||||
frame = AnimationFrame(cells, fill_interpolation, frame_interpolation, delay)
|
||||
return frame.to_string(scene_number)
|
||||
17
tools/animation-tools/MatrixMath.py
Normal file
17
tools/animation-tools/MatrixMath.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import math
|
||||
from pygame.math import Vector3
|
||||
|
||||
def project(vector, w, h, fov, distance):
|
||||
factor = math.atan(fov / 2 * math.pi / 180) / (distance + vector.z)
|
||||
x = vector.x * factor * w + w / 2
|
||||
y = -vector.y * factor * w + h / 2
|
||||
return Vector3(x, y, vector.z)
|
||||
|
||||
def rotate_vertices(vertices, angle, axis):
|
||||
return [v.rotate(angle, axis) for v in vertices]
|
||||
def scale_vertices(vertices, s):
|
||||
return [Vector3(v[0]*s[0], v[1]*s[1], v[2]*s[2]) for v in vertices]
|
||||
def translate_vertices(vertices, t):
|
||||
return [v + Vector3(t) for v in vertices]
|
||||
def project_vertices(vertices, w, h, fov, distance):
|
||||
return [project(v, w, h, fov, distance) for v in vertices]
|
||||
31
tools/animation-tools/README.md
Normal file
31
tools/animation-tools/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Setup
|
||||
In order to run this tool follow these steps:
|
||||
|
||||
1. ensure you're using python 3.12+. You can verify this by running
|
||||
```
|
||||
python --version
|
||||
```
|
||||
in a terminal.
|
||||
|
||||
2. Inside of a terminal in the
|
||||
```
|
||||
tools/animation-tools
|
||||
```
|
||||
folder, run:
|
||||
```
|
||||
python -m venv venv
|
||||
```
|
||||
to create your virtual environment.
|
||||
|
||||
3. Now activate the virtual environment. In powershell run
|
||||
```
|
||||
venv\Scripts\activate
|
||||
```
|
||||
You may need to fix permission issues if this is your first time using a virtual environment. For git bash just run:
|
||||
```
|
||||
source venv/Scripts/activate
|
||||
```
|
||||
4. Run
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
70
tools/animation-tools/Scene.py
Normal file
70
tools/animation-tools/Scene.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from Shapes import Mesh
|
||||
from MatrixMath import *
|
||||
import pygame
|
||||
|
||||
class Scene:
|
||||
def __init__(self, meshes, fov, distance):
|
||||
self.meshes: list[Mesh] = meshes
|
||||
self.fov = fov
|
||||
self.distance = distance
|
||||
self.euler_angles = [0, 0, 0]
|
||||
|
||||
def transform_vertices(self, vertices, width, height):
|
||||
transformed_vertices = vertices
|
||||
axis_list = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
|
||||
for angle, axis in reversed(list(zip(list(self.euler_angles), axis_list))):
|
||||
transformed_vertices = rotate_vertices(transformed_vertices, angle, axis)
|
||||
transformed_vertices = project_vertices(transformed_vertices, width, height, self.fov, self.distance)
|
||||
return transformed_vertices
|
||||
|
||||
def point_in_polygon(self, point, polygon):
|
||||
""" Determine if the point (x, y) is inside the polygon """
|
||||
x, y = point
|
||||
n = len(polygon)
|
||||
inside = False
|
||||
p1x, p1y = polygon[0]
|
||||
for i in range(n + 1):
|
||||
p2x, p2y = polygon[i % n]
|
||||
if y > min(p1y, p2y):
|
||||
if y <= max(p1y, p2y):
|
||||
if x <= max(p1x, p2x):
|
||||
if p1y != p2y:
|
||||
xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
|
||||
if p1x == p2x or x <= xints:
|
||||
inside = not inside
|
||||
p1x, p1y = p2x, p2y
|
||||
return inside
|
||||
|
||||
def get_mesh_from_xy(self, pos: tuple[int]) -> Mesh:
|
||||
x, y = pos
|
||||
closest_mesh = None
|
||||
closest_z = float('inf')
|
||||
|
||||
for mesh in self.meshes:
|
||||
transformed_vertices = self.transform_vertices(mesh.get_vertices(), *pygame.display.get_surface().get_size())
|
||||
avg_z = mesh.calculate_average_z(transformed_vertices)
|
||||
for i, avg_z_value in avg_z:
|
||||
polygon = mesh.create_polygon(mesh.get_face(i), transformed_vertices)
|
||||
if self.point_in_polygon((x, y), polygon):
|
||||
if avg_z_value < closest_z:
|
||||
closest_z = avg_z_value
|
||||
closest_mesh = mesh
|
||||
|
||||
return closest_mesh
|
||||
|
||||
def draw(self, surface):
|
||||
|
||||
polygons = []
|
||||
for mesh in self.meshes:
|
||||
transformed_vertices = self.transform_vertices(mesh.get_vertices(), *surface.get_size())
|
||||
avg_z = mesh.calculate_average_z(transformed_vertices)
|
||||
for z in avg_z:
|
||||
#for z in sorted(avg_z, key=lambda x: x[1], reverse=True):
|
||||
pointlist = mesh.create_polygon(mesh.get_face(z[0]), transformed_vertices)
|
||||
polygons.append((pointlist, z[1], mesh.face_color, mesh.edge_color))
|
||||
#pygame.draw.polygon(surface, (128, 128, 192), pointlist)
|
||||
#pygame.draw.polygon(surface, (0, 0, 0), pointlist, 3)
|
||||
|
||||
for poly in sorted(polygons, key=lambda x: x[1], reverse=True):
|
||||
pygame.draw.polygon(surface, poly[2], poly[0])
|
||||
pygame.draw.polygon(surface, poly[3], poly[0], 3)
|
||||
48
tools/animation-tools/Shapes.py
Normal file
48
tools/animation-tools/Shapes.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from MatrixMath import *
|
||||
from pygame.math import Vector3
|
||||
|
||||
class Mesh():
|
||||
def __init__(self, vertices, faces):
|
||||
self.__vertices = [Vector3(v) for v in vertices]
|
||||
self.__faces = faces
|
||||
self.face_color = (0, 0, 0)
|
||||
self.edge_color = (0,0,0)
|
||||
|
||||
def rotate(self, angle, axis):
|
||||
self.__vertices = rotate_vertices(self.__vertices, angle, axis)
|
||||
def scale(self, s):
|
||||
self.__vertices = scale_vertices(self.__vertices, s)
|
||||
def translate(self, t):
|
||||
self.__vertices = translate_vertices(self.__vertices, t)
|
||||
|
||||
def calculate_average_z(self, vertices):
|
||||
return [(i, sum([vertices[j].z for j in f]) / len(f)) for i, f in enumerate(self.__faces)]
|
||||
|
||||
def get_average_position(self):
|
||||
vertex_sum: Vector3 = Vector3()
|
||||
for vertex in self.__vertices:
|
||||
vertex_sum += vertex
|
||||
return vertex_sum / len(self.__vertices)
|
||||
|
||||
def get_face(self, index):
|
||||
return self.__faces[index]
|
||||
def get_vertices(self):
|
||||
return self.__vertices
|
||||
|
||||
def create_polygon(self, face, vertices):
|
||||
return [(vertices[i].x, vertices[i].y) for i in [*face, face[0]]]
|
||||
|
||||
def set_face_color(self, color : tuple[int]):
|
||||
self.face_color = color
|
||||
|
||||
def set_edge_color(self, color : tuple[int]):
|
||||
self.edge_color = color
|
||||
|
||||
class Cube(Mesh):
|
||||
def __init__(self):
|
||||
vertices = [(-1,-1,1), (1,-1,1), (1,1,1), (-1,1,1), (-1,-1,-1), (1,-1,-1), (1,1,-1), (-1,1,-1)]
|
||||
faces = [(0,1,2,3), (1,5,6,2), (5,4,7,6), (4,0,3,7), (3,2,6,7), (1,0,4,5)]
|
||||
super().__init__(vertices, faces)
|
||||
|
||||
def set_position(self, position : tuple):
|
||||
super().translate(position)
|
||||
210
tools/animation-tools/UI.py
Normal file
210
tools/animation-tools/UI.py
Normal file
@@ -0,0 +1,210 @@
|
||||
from itertools import product
|
||||
from pygame_widgets.button import Button
|
||||
from pygame_widgets.slider import Slider
|
||||
from pygame_widgets.dropdown import Dropdown
|
||||
from pygame_widgets.textbox import TextBox
|
||||
from Scene import Scene
|
||||
from Shapes import *
|
||||
import pygame
|
||||
from AnimationExporter import *
|
||||
|
||||
class ColorPicker:
|
||||
def __init__(self, screen, x_pos: int, y_pos: int, width: int, height: int):
|
||||
width = max(50, width)
|
||||
slider_colors = ((255,0,0),(0,255,0),(0,0,255))
|
||||
self.sliders: list[Slider] = [
|
||||
Slider(screen, int(x_pos + i*(width/3)), y_pos, int((width-50)/3), height, min=0, max=255, step=1, vertical=True, handleColour=slider_colors[i]) for i in range(3)
|
||||
]
|
||||
for slider in self.sliders:
|
||||
slider.enable()
|
||||
|
||||
def get_color(self) -> tuple[int]:
|
||||
return tuple([slider.getValue() for slider in self.sliders])
|
||||
|
||||
def set_color(self, color: tuple[int]):
|
||||
for i, slider in enumerate(self.sliders):
|
||||
slider.setValue(color[i])
|
||||
|
||||
class SceneStore:
|
||||
def __init__(self, scene: Scene, fill: FillInterpolation, fade: FrameInterpolation, delay: int):
|
||||
self.scene: Scene = scene
|
||||
self.fill: FillInterpolation = fill
|
||||
self.fade: FrameInterpolation = fade
|
||||
self.delay: int = delay
|
||||
|
||||
class SceneManager:
|
||||
def __init__(self, window, color_picker: ColorPicker):
|
||||
self.file_data: str = ""
|
||||
self._scenes: list[SceneStore] = [SceneStore(self.new_scene(), FillInterpolation.NO_FILL, FrameInterpolation.FADE, 1000)]
|
||||
self._current_scene_idx: int = 0
|
||||
self.window = window
|
||||
self.color_picker = color_picker
|
||||
self._selected_meshes: list[Mesh] = []
|
||||
|
||||
def save_frame_to_file(self):
|
||||
with open("tools/animation-tools/output.txt", 'w') as file:
|
||||
for i, scene in enumerate(self._scenes):
|
||||
file.write(scene_to_frame(scene.scene, scene.fill, scene.fade, scene.delay, i))
|
||||
|
||||
def generate_meshes(self) -> list[Mesh]:
|
||||
# generate a list of cubes
|
||||
meshes: list[Mesh] = []
|
||||
for origin in product([-1, 0, 1],[-1, 0, 1],[-1, 0, 1]):
|
||||
cube = Cube()
|
||||
cube.scale((0.35, 0.35, 0.35))
|
||||
cube.set_position(origin)
|
||||
cube.set_face_color((0, 0, 0))
|
||||
meshes.append(cube)
|
||||
return meshes
|
||||
|
||||
def new_scene(self) -> Scene:
|
||||
return Scene(self.generate_meshes(), 90, 5)
|
||||
|
||||
def draw(self):
|
||||
for mesh in self._selected_meshes:
|
||||
mesh.set_face_color(self.color_picker.get_color())
|
||||
self.get_current_scene().scene.draw(self.window)
|
||||
|
||||
def get_current_scene(self) -> SceneStore:
|
||||
return self._scenes[self._current_scene_idx]
|
||||
|
||||
def click_mesh(self, coordinates: tuple[int, int]):
|
||||
mesh = self.get_current_scene().scene.get_mesh_from_xy(coordinates)
|
||||
|
||||
if mesh == None:
|
||||
return
|
||||
|
||||
if mesh in self._selected_meshes:
|
||||
mesh.set_edge_color((0,0,0))
|
||||
self._selected_meshes.remove(mesh)
|
||||
else:
|
||||
mesh.set_edge_color((255,0,0))
|
||||
self._selected_meshes.append(mesh)
|
||||
|
||||
def deselect_all_mesh(self):
|
||||
for mesh in self._selected_meshes:
|
||||
mesh.set_edge_color((0,0,0))
|
||||
self._selected_meshes = []
|
||||
|
||||
def next_scene(self):
|
||||
self.deselect_all_mesh()
|
||||
current_angles = self.get_current_scene().scene.euler_angles
|
||||
if len(self._scenes)-1 == self._current_scene_idx:
|
||||
self._scenes.append(SceneStore(self.new_scene(), FillInterpolation.NO_FILL, FrameInterpolation.FADE, 1000))
|
||||
|
||||
self._current_scene_idx += 1
|
||||
|
||||
self.get_current_scene().scene.euler_angles = [angle for angle in current_angles]
|
||||
|
||||
def last_scene(self):
|
||||
if self._current_scene_idx > 0:
|
||||
current_angles = self.get_current_scene().scene.euler_angles
|
||||
self._current_scene_idx -= 1
|
||||
self.get_current_scene().scene.euler_angles = [angle for angle in current_angles]
|
||||
self.deselect_all_mesh()
|
||||
|
||||
def update_scene_options(self, fill: FillInterpolation, fade: FrameInterpolation, delay: int):
|
||||
cur_scene: SceneStore = self.get_current_scene()
|
||||
cur_scene.fill = fill
|
||||
cur_scene.fade = fade
|
||||
cur_scene.delay = delay
|
||||
|
||||
class AnimatorUI:
|
||||
def __init__(self, window):
|
||||
scr_wdt, scr_hgt = window.get_size()
|
||||
|
||||
self.window = window
|
||||
self.colorPicker: ColorPicker = ColorPicker(self.window, *self.rel2abs(5, 5, 20, 60))
|
||||
self.sceneManager: SceneManager = SceneManager(window, self.colorPicker)
|
||||
|
||||
self.save_button: Button = Button(self.window, *self.rel2abs(5, 90, 10, 10), text="Save",onClick=self.sceneManager.save_frame_to_file)
|
||||
self.next_frame_button: Button = Button(self.window, *self.rel2abs(75, 90, 25, 5), text="Next Frame",onClick=self._next_scene)
|
||||
self.last_frame_button: Button = Button(self.window, *self.rel2abs(50, 90, 25, 5), text="last Frame",onClick=self._last_scene)
|
||||
self.trackMouseMotion: bool = False
|
||||
|
||||
self.fill_dropdown: Dropdown = Dropdown(self.window, *self.rel2abs(30, 0, 40, 5), name="Fill Type",
|
||||
choices=[option.value for option in FillInterpolation], onRelease=self.set_update_options_flag)
|
||||
|
||||
self.fade_dropdown: Dropdown = Dropdown(self.window, *self.rel2abs(70, 0, 20, 5), name="Fade Type",
|
||||
choices=[option.value for option in FrameInterpolation], onRelease=self.set_update_options_flag)
|
||||
|
||||
self.updateOptions = False
|
||||
self._set_dropdown_options()
|
||||
|
||||
self.delay_text_entry: TextBox = TextBox(self.window, *self.rel2abs(30, 5, 60, 7), placeholderText="Transition Time (ms)", onSubmit=self.set_update_options_flag)
|
||||
|
||||
# Make a frame counter as a button but make it not look like a button
|
||||
default_color=(150,150,150)
|
||||
self.frame_counter_text: Button = Button(self.window, *self.rel2abs(15, 90, 10, 10), text="0", inactiveColour=default_color, hoverColour=default_color, pressedColour=default_color)
|
||||
|
||||
def rel2abs(self, x: float, y: float, width: float, height: float) -> tuple[int,int,int,int]:
|
||||
scr_wdt, scr_hgt = self.window.get_size()
|
||||
x_abs = int(x*scr_wdt/100)
|
||||
y_abs = int(y*scr_hgt/100)
|
||||
w_abs = int(width*scr_wdt/100)
|
||||
h_abs = int(height*scr_hgt/100)
|
||||
return (x_abs, y_abs, w_abs, h_abs)
|
||||
|
||||
|
||||
def update_interaction(self, game_event):
|
||||
if not self.trackMouseMotion:
|
||||
pygame.mouse.get_rel()
|
||||
|
||||
if game_event.type == pygame.MOUSEBUTTONDOWN:
|
||||
if game_event.button == 2: # middle mouse click
|
||||
self.trackMouseMotion = True
|
||||
elif game_event.button == 1: # left click
|
||||
self.sceneManager.click_mesh(pygame.mouse.get_pos())
|
||||
elif game_event.type == pygame.MOUSEBUTTONUP:
|
||||
if game_event.button == 2: # middle mouse release
|
||||
self.trackMouseMotion = False
|
||||
elif self.trackMouseMotion and game_event.type == pygame.MOUSEMOTION:
|
||||
mouseMovement = pygame.mouse.get_rel()
|
||||
current_scene = self.sceneManager.get_current_scene()
|
||||
current_scene.euler_angles[0] -= mouseMovement[1]
|
||||
current_scene.euler_angles[1] -= mouseMovement[0]
|
||||
|
||||
def draw(self):
|
||||
if self.updateOptions:
|
||||
self.set_scene_options()
|
||||
# make the preview window for the color picker
|
||||
pygame.draw.rect(self.window, self.colorPicker.get_color(), self.rel2abs(11, 70, 5, 5))
|
||||
pygame.draw.rect(self.window, (0,0,0), self.rel2abs(11, 70, 5, 5), 2)
|
||||
|
||||
self.sceneManager.draw()
|
||||
|
||||
def _next_scene(self):
|
||||
self.sceneManager.next_scene()
|
||||
self._set_dropdown_options()
|
||||
self.frame_counter_text.setText(str(self.sceneManager._current_scene_idx))
|
||||
|
||||
|
||||
def _last_scene(self):
|
||||
self.sceneManager.last_scene()
|
||||
self._set_dropdown_options()
|
||||
self.frame_counter_text.setText(str(self.sceneManager._current_scene_idx))
|
||||
|
||||
def _set_dropdown_options(self):
|
||||
# cursed method to set dropdown options because for some reason pygame_widgets dropdown doesn't allow you to manually set the dropdown option
|
||||
scene = self.sceneManager.get_current_scene()
|
||||
for choice in self.fill_dropdown._Dropdown__choices:
|
||||
if choice.text.find(scene.fill) != -1:
|
||||
self.fill_dropdown.chosen = choice
|
||||
break
|
||||
|
||||
for choice in self.fade_dropdown._Dropdown__choices:
|
||||
if choice.text.find(scene.fade) != -1:
|
||||
self.fade_dropdown.chosen = choice
|
||||
break
|
||||
|
||||
def set_update_options_flag(self):
|
||||
self.updateOptions = True
|
||||
|
||||
def set_scene_options(self):
|
||||
self.updateOptions = False
|
||||
delay_time = 1000
|
||||
try:
|
||||
delay_time = int(self.delay_text_entry.getText())
|
||||
except ValueError:
|
||||
self.delay_text_entry.setText(str(delay_time))
|
||||
self.sceneManager.update_scene_options(self.fill_dropdown.getSelected(), self.fade_dropdown.getSelected(), delay_time)
|
||||
37
tools/animation-tools/animation-tool.py
Normal file
37
tools/animation-tools/animation-tool.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import pygame
|
||||
import pygame_widgets
|
||||
from Shapes import *
|
||||
from UI import AnimatorUI
|
||||
|
||||
WINDOW_W = 500
|
||||
WINDOW_H = 500
|
||||
|
||||
|
||||
file_data = ""
|
||||
|
||||
pygame.init()
|
||||
window = pygame.display.set_mode((WINDOW_W, WINDOW_H))
|
||||
|
||||
ui = AnimatorUI(window)
|
||||
|
||||
clock = pygame.time.Clock()
|
||||
run = True
|
||||
|
||||
while run:
|
||||
clock.tick(60)
|
||||
window.fill((255, 255, 255))
|
||||
|
||||
events = pygame.event.get()
|
||||
for event in events:
|
||||
if event.type == pygame.QUIT:
|
||||
run = False
|
||||
# if the event isn't handled above as a global event, let the ui handle it
|
||||
else:
|
||||
ui.update_interaction(event)
|
||||
|
||||
pygame_widgets.update(events)
|
||||
|
||||
ui.draw()
|
||||
pygame.display.flip()
|
||||
|
||||
pygame.quit()
|
||||
2
tools/animation-tools/requirements.txt
Normal file
2
tools/animation-tools/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
pygame==2.6.0
|
||||
pygame-widgets==1.1.5
|
||||
Reference in New Issue
Block a user