From c419667eb43a4f0caf594360d5facd61efa5a8bc Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Thu, 13 Feb 2025 09:54:00 +0800 Subject: [PATCH 01/17] feat: basic python API --- Cargo.lock | 117 ++++++++++++++++++++++++++++++++------ Cargo.toml | 6 +- examples/py/main.py | 22 +++++++ pyproject.toml | 15 +++++ src/animation/entity.rs | 19 ++++--- src/animation/mod.rs | 6 +- src/animation/timeline.rs | 40 ++++++++++++- src/items/mod.rs | 56 +++++++++++++++++- src/lib.rs | 41 ++++++++++++- src/render/mod.rs | 2 +- 10 files changed, 288 insertions(+), 36 deletions(-) create mode 100644 examples/py/main.py create mode 100644 pyproject.toml diff --git a/Cargo.lock b/Cargo.lock index fda5283c..166c4f8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,9 +205,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bevy_color" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00aa2966c7ca0c7dd39f5ba8f3b1eaa5c2005a93ffdefb7a4090150d8327678" +checksum = "9df600cf7c7989d07285c537eb2ea2c053e351391f1ea8ce46cbc591258c8a6f" dependencies = [ "bevy_math", "bevy_reflect", @@ -218,9 +218,9 @@ dependencies = [ [[package]] name = "bevy_macro_utils" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bdb3a681c24abace65bf18ed467ad8befbedb42468b32e459811bfdb01e506c" +checksum = "090371a2cd85574989febff6063a21d1fbbc2939e80f00fe075f62aa8e616136" dependencies = [ "proc-macro2", "quote", @@ -230,9 +230,9 @@ dependencies = [ [[package]] name = "bevy_math" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edec18d90e6bab27b5c6131ee03172ece75b7edd0abe4e482a26d6db906ec357" +checksum = "389aaa6477f247f2c5d508f5a8cb800ea78442c74939c51383305fb1f5ee9939" dependencies = [ "derive_more", "glam", @@ -242,15 +242,15 @@ dependencies = [ [[package]] name = "bevy_ptr" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa65df6a190b7dfc84d79f09cf02d47ae046fa86a613e202c31559e06d8d3710" +checksum = "4da2111eefa2000ea8c9dc1beee2eb7283b29b5ef90a29fe43c748df549f84ad" [[package]] name = "bevy_reflect" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab3264acc3b6f48bc23fbd09fdfea6e5d9b7bfec142e4f3333f532acf195bca" +checksum = "82af24a68fd8feff476d9672ff34d220d3f45e95ef2f2324e7cb674614d18138" dependencies = [ "assert_type_match", "bevy_ptr", @@ -267,9 +267,9 @@ dependencies = [ [[package]] name = "bevy_reflect_derive" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f83876a322130ab38a47d5dcf75258944bf76b3387d1acdb3750920fda63e2" +checksum = "8369e6e779ab3540f9dcd93d062139f62551b3d2fe1ab451c6ddf74757e22ccd" dependencies = [ "bevy_macro_utils", "proc-macro2", @@ -280,9 +280,9 @@ dependencies = [ [[package]] name = "bevy_utils" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f01088c048960ea50ee847c3f668942ecf49ed26be12a1585a5e59b6a941d9a" +checksum = "2993cac374b3f88cfaf59506c71f8e3e7ad8b4961f4e9864bc76e1c9e1e4400c" dependencies = [ "ahash", "bevy_utils_proc_macros", @@ -295,9 +295,9 @@ dependencies = [ [[package]] name = "bevy_utils_proc_macros" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0c3244d543cc964545b7aa074f6fb18a915a7121cf3de5d7ed37a4aae8662d" +checksum = "2606f79dfe359a88e2a59bb6cd632cd42e9d4bcd250ac8bc3a4e7657e82f4f39" dependencies = [ "proc-macro2", "quote", @@ -1050,6 +1050,12 @@ dependencies = [ "web-time", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "interpolate_name" version = "0.2.4" @@ -1255,6 +1261,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "metal" version = "0.31.0" @@ -1540,6 +1555,69 @@ dependencies = [ "syn", ] +[[package]] +name = "pyo3" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + [[package]] name = "qoi" version = "0.4.1" @@ -1615,6 +1693,7 @@ dependencies = [ "itertools 0.14.0", "log", "pollster", + "pyo3", "regex", "usvg", "uuid", @@ -2198,6 +2277,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + [[package]] name = "usvg" version = "0.44.0" diff --git a/Cargo.toml b/Cargo.toml index 4e399d2c..1aa09348 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ keywords = ["animation", "manim"] # [workspace] # members = ["packages/*"] +[lib] +name = "ranim" +crate-type = ["cdylib", "rlib"] [dependencies] # ranim_derive.workspace = true @@ -24,11 +27,12 @@ pollster = "0.4.0" uuid = { version = "1.12.1", features = ["v4"] } wgpu = "24.0.1" anyhow = "1.0.95" -bevy_color = "0.15.2" +bevy_color = "0.15.3" usvg = "0.44.0" regex = "1.11.1" wgpu-profiler = "0.21.0" indicatif = "0.17.11" +pyo3 = { version = "0.23.4", features = ["extension-module"] } # Enable a small amount of optimization in the dev profile. [profile.dev] diff --git a/examples/py/main.py b/examples/py/main.py new file mode 100644 index 00000000..e12ea954 --- /dev/null +++ b/examples/py/main.py @@ -0,0 +1,22 @@ +import ranim + +""" + + + +""" +# print(ranim) +# print(ranim.Timeline) +# print(ranim.Timeline()) + +timeline = ranim.Timeline() + +with open("assets/Ghostscript_Tiger.svg") as f: + svg = f.read() + +svg = ranim.SvgItem(svg) + +timeline.show(svg) +timeline.forward(1.0) + +ranim.render_timeline(timeline, "./") \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..795f73c3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["maturin>=1.7,<2.0"] +build-backend = "maturin" + +[project] +name = "m" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/src/animation/entity.rs b/src/animation/entity.rs index e07aa78e..167127a8 100644 --- a/src/animation/entity.rs +++ b/src/animation/entity.rs @@ -6,7 +6,7 @@ pub mod fading; pub mod freeze; pub mod interpolate; -use std::rc::Rc; +use std::{rc::Rc, sync::{Arc, Mutex}}; use freeze::{freeze, Blank}; use itertools::Itertools; @@ -20,6 +20,7 @@ use crate::{ use super::{Anim, AnimWithParams, Animator, StaticAnim}; +#[derive(Clone)] pub struct EntityTimeline { // pub(super) rabject_id: Id, pub(super) cur_freeze_anim: StaticAnim, @@ -34,7 +35,7 @@ impl EntityTimeline { pub fn new(rabject: &Rabject) -> Self { Self { // rabject_id: rabject.id, - cur_freeze_anim: Rc::new(Box::new(freeze(rabject))), + cur_freeze_anim: Arc::new(Box::new(freeze(rabject))), cur_anim_idx: None, is_showing: true, anims: Vec::new(), @@ -44,7 +45,7 @@ impl EntityTimeline { } fn push(&mut self, anim: AnimWithParams) { let duration = anim.params.duration_secs; - self.anims.push(Box::new(anim)); + self.anims.push(Arc::new(Mutex::new(Box::new(anim)))); let end_sec = self.end_secs.last().copied().unwrap_or(0.0) + duration; self.end_secs.push(end_sec); @@ -80,7 +81,7 @@ impl EntityTimeline { anim.update_alpha(1.0); let end_rabject = anim.anim.rabject.clone(); - self.cur_freeze_anim = Rc::new(Box::new(freeze(&end_rabject))); + self.cur_freeze_anim = Arc::new(Box::new(freeze(&end_rabject))); self.push(anim); end_rabject } @@ -110,7 +111,7 @@ impl Animator for EntityTimeline { } .unwrap_or(0.0); let alpha = (cur_sec - start_sec) / (end_sec - start_sec); - anim.update_alpha(alpha); + anim.lock().unwrap().update_alpha(alpha); } } @@ -126,7 +127,7 @@ impl Renderable for EntityTimeline { camera: &CameraFrame, ) { if let Some(idx) = self.cur_anim_idx { - self.anims[idx].render( + self.anims[idx].lock().unwrap().render( ctx, render_instances, pipelines, @@ -140,7 +141,7 @@ impl Renderable for EntityTimeline { } /// An animator that animates an entity -pub trait PureEvaluator { +pub trait PureEvaluator: Send + Sync { fn eval_alpha(&self, alpha: f32) -> T; } @@ -161,7 +162,7 @@ impl PureEvaluator for Rabject { #[derive(Clone)] pub struct EntityAnim { rabject: Rabject, - evaluator: Rc>>, + evaluator: Arc>>, } impl Animator for EntityAnim { @@ -197,7 +198,7 @@ impl EntityAnim { pub fn new(rabject: Rabject, func: impl PureEvaluator + 'static) -> Self { Self { rabject, - evaluator: Rc::new(Box::new(func)), + evaluator: Arc::new(Box::new(func)), } } pub fn rabject(&self) -> &Rabject { diff --git a/src/animation/mod.rs b/src/animation/mod.rs index 0ae7b4fe..6b2ede85 100644 --- a/src/animation/mod.rs +++ b/src/animation/mod.rs @@ -1,7 +1,7 @@ pub mod entity; pub mod timeline; -use std::rc::Rc; +use std::{rc::Rc, sync::{Arc, Mutex}}; use crate::{ context::WgpuContext, @@ -18,11 +18,11 @@ pub trait Animator: Renderable { } /// An `Anim` is a box of [`Animator`] -pub type Anim = Box; +pub type Anim = Arc>>; /// An `StaticAnim` is a box of [`Renderable`] inside a `Rc` /// /// This implements [`Animator`] but does nothing on `update_alpha` -pub type StaticAnim = Rc>; +pub type StaticAnim = Arc>; impl Renderable for StaticAnim { fn render( diff --git a/src/animation/timeline.rs b/src/animation/timeline.rs index 2574aa84..2d8c45bf 100644 --- a/src/animation/timeline.rs +++ b/src/animation/timeline.rs @@ -1,8 +1,10 @@ -use std::{collections::HashMap, fmt::Debug, time::Duration}; +use std::{collections::HashMap, fmt::Debug, sync::{Arc, Mutex}, time::Duration}; + +use pyo3::{ffi::PyObject, pyclass, pymethods, types::PyAnyMethods, Bound, Py, PyAny}; use crate::{ context::WgpuContext, - items::{Entity, Rabject}, + items::{vitem::VItem, Entity, PySvgItem, PyVItem, Rabject}, render::{primitives::RenderInstances, CameraFrame, RenderTextures, Renderable}, utils::{Id, PipelinesStorage}, }; @@ -12,12 +14,44 @@ use super::{ AnimWithParams, Animator, }; +#[pyclass] +#[pyo3(name = "Timeline")] +#[derive(Debug)] +pub struct PyTimeline { + pub(crate) inner: Timeline, +} + +#[pymethods] +impl PyTimeline { + #[new] + pub fn new() -> Self { + Self { + inner: Timeline::new(), + } + } + pub fn show(&mut self, rabject: &Bound<'_, PyAny>) { + if let Ok(vitem) = rabject.downcast::() { + self.inner.show(&vitem.borrow().inner); + } else if let Ok(svg_item) = rabject.downcast::() { + self.inner.show(&svg_item.borrow().inner); + } + } + pub fn forward(&mut self, secs: f32) { + self.inner.forward(secs); + } + pub fn elapsed_secs(&self) -> f32 { + self.inner.elapsed_secs() + } +} + +// MARK: Timeline + /// Timeline of all rabjects /// /// The Timeline itself is also an [`Animator`] which: /// - update all RabjectTimeline's alpha /// - render all RabjectTimeline -#[derive(Default)] +#[derive(Default, Clone)] pub struct Timeline { /// Rabject's Id -> EntityTimeline rabject_timelines: HashMap, diff --git a/src/items/mod.rs b/src/items/mod.rs index 10a00503..6dece778 100644 --- a/src/items/mod.rs +++ b/src/items/mod.rs @@ -1,8 +1,10 @@ use std::ops::{Deref, DerefMut}; -use glam::Vec2; +use glam::{vec3, Vec2, Vec3}; +use pyo3::{pyclass, pymethods}; use crate::{ + components::vpoint::VPoint, context::WgpuContext, prelude::Empty, render::{ @@ -15,6 +17,44 @@ use crate::{ pub mod svg_item; pub mod vitem; +// #[pyclass] +// pub enum PyEntity { +// VItem(PyVItem), +// } + +#[pyclass] +#[pyo3(name="SvgItem")] +pub struct PySvgItem { + pub(crate) inner: Rabject, +} + +#[pymethods] +impl PySvgItem { + #[new] + pub fn new(svg_str: &str) -> Self { + Self { + inner: Rabject::new(svg_item::SvgItem::from_svg(svg_str)), + } + } +} + +#[pyclass] +#[pyo3(name="VItem")] +pub struct PyVItem { + pub(crate) inner: Rabject, +} + +#[pymethods] +impl PyVItem { + #[new] + pub fn new(vpoints: Vec<[f32; 3]>) -> Self { + let vpoints = vpoints.iter().map(|v| vec3(v[0], v[1], v[2])).collect(); + Self { + inner: Rabject::new(vitem::VItem::from_vpoints(vpoints)), + } + } +} + /// An `Rabject` is a wrapper of an entity that can be rendered. /// /// The `Rabject`s with same `Id` will use the same `EntityTimeline` to animate. @@ -93,7 +133,7 @@ impl> ConvertIntoRabject for Rabject { } } -pub trait Entity: Clone + Empty { +pub trait Entity: Clone + Empty + Send + Sync { type Primitive: Extract + Default; #[allow(unused)] @@ -121,3 +161,15 @@ impl Updatable for T { *self = other.clone(); } } + +// MARK: Py + +pub enum RabjectEnum { + VItem(Rabject), + SvgItem(Rabject), +} + +#[pyclass] +pub struct PyRabject { + inner: RabjectEnum, +} diff --git a/src/lib.rs b/src/lib.rs index 209c3443..320ac609 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,13 +6,22 @@ use std::{ time::Duration, }; -use animation::{timeline::Timeline, AnimWithParams, Animator}; +use animation::{ + timeline::{PyTimeline, Timeline}, + AnimWithParams, Animator, +}; use context::RanimContext; use file_writer::{FileWriter, FileWriterBuilder}; pub use glam; use image::{ImageBuffer, Rgba}; use indicatif::{ProgressBar, ProgressState, ProgressStyle}; +use items::{PySvgItem, PyVItem}; use log::info; +use pyo3::{ + pyfunction, pymodule, + types::{PyModule, PyModuleMethods}, + wrap_pyfunction, Bound, PyResult, +}; use render::{CameraFrame, Renderable, Renderer}; use utils::rate_functions::linear; @@ -264,3 +273,33 @@ impl RanimRenderApp { // info!("[Scene]: SAVE FRAME TO IMAGE END, took {:?}", t.elapsed()); } } + +#[pyfunction] +fn render_timeline(timeline: Bound<'_, PyTimeline>, output_dir: PathBuf) { + let options = AppOptions { + output_dir, + ..Default::default() + }; + + let mut app = RanimRenderApp::new(&options); + let mut timeline = timeline.borrow_mut(); + if timeline.elapsed_secs() == 0.0 { + timeline.forward(0.1); + } + info!("Rendering {:?}", timeline); + let duration_secs = timeline.elapsed_secs(); + app.render_anim( + AnimWithParams::new(timeline.inner.clone()) + .with_duration(duration_secs) + .with_rate_func(linear), + ); +} + +#[pymodule] +fn ranim(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_function(wrap_pyfunction!(render_timeline, m)?)?; + Ok(()) +} diff --git a/src/render/mod.rs b/src/render/mod.rs index fa679daf..21e371a0 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -10,7 +10,7 @@ use crate::{ utils::{wgpu::WgpuBuffer, PipelinesStorage}, }; -pub trait Renderable { +pub trait Renderable: Send + Sync { #[allow(clippy::too_many_arguments)] fn render( &self, From 5f2e7f1fe9a14592af05819968cb0a4f9fda2120 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Wed, 19 Feb 2025 17:16:23 +0800 Subject: [PATCH 02/17] clean up --- src/animation/entity.rs | 4 +++- src/animation/mod.rs | 4 +++- src/animation/timeline.rs | 11 ++++++++--- src/items/mod.rs | 19 +++---------------- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/animation/entity.rs b/src/animation/entity.rs index 167127a8..8020ff13 100644 --- a/src/animation/entity.rs +++ b/src/animation/entity.rs @@ -6,7 +6,9 @@ pub mod fading; pub mod freeze; pub mod interpolate; -use std::{rc::Rc, sync::{Arc, Mutex}}; +use std::{ + sync::{Arc, Mutex}, +}; use freeze::{freeze, Blank}; use itertools::Itertools; diff --git a/src/animation/mod.rs b/src/animation/mod.rs index 6b2ede85..c68a69a0 100644 --- a/src/animation/mod.rs +++ b/src/animation/mod.rs @@ -1,7 +1,9 @@ pub mod entity; pub mod timeline; -use std::{rc::Rc, sync::{Arc, Mutex}}; +use std::{ + sync::{Arc, Mutex}, +}; use crate::{ context::WgpuContext, diff --git a/src/animation/timeline.rs b/src/animation/timeline.rs index 2d8c45bf..120c9270 100644 --- a/src/animation/timeline.rs +++ b/src/animation/timeline.rs @@ -1,10 +1,14 @@ -use std::{collections::HashMap, fmt::Debug, sync::{Arc, Mutex}, time::Duration}; +use std::{ + collections::HashMap, + fmt::Debug, + time::Duration, +}; -use pyo3::{ffi::PyObject, pyclass, pymethods, types::PyAnyMethods, Bound, Py, PyAny}; +use pyo3::{pyclass, pymethods, types::PyAnyMethods, Bound, Py, PyAny}; use crate::{ context::WgpuContext, - items::{vitem::VItem, Entity, PySvgItem, PyVItem, Rabject}, + items::{Entity, PySvgItem, PyVItem, Rabject}, render::{primitives::RenderInstances, CameraFrame, RenderTextures, Renderable}, utils::{Id, PipelinesStorage}, }; @@ -24,6 +28,7 @@ pub struct PyTimeline { #[pymethods] impl PyTimeline { #[new] + #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { inner: Timeline::new(), diff --git a/src/items/mod.rs b/src/items/mod.rs index 6dece778..0c28d289 100644 --- a/src/items/mod.rs +++ b/src/items/mod.rs @@ -1,10 +1,9 @@ use std::ops::{Deref, DerefMut}; -use glam::{vec3, Vec2, Vec3}; +use glam::{vec3, Vec2}; use pyo3::{pyclass, pymethods}; use crate::{ - components::vpoint::VPoint, context::WgpuContext, prelude::Empty, render::{ @@ -23,7 +22,7 @@ pub mod vitem; // } #[pyclass] -#[pyo3(name="SvgItem")] +#[pyo3(name = "SvgItem")] pub struct PySvgItem { pub(crate) inner: Rabject, } @@ -39,7 +38,7 @@ impl PySvgItem { } #[pyclass] -#[pyo3(name="VItem")] +#[pyo3(name = "VItem")] pub struct PyVItem { pub(crate) inner: Rabject, } @@ -161,15 +160,3 @@ impl Updatable for T { *self = other.clone(); } } - -// MARK: Py - -pub enum RabjectEnum { - VItem(Rabject), - SvgItem(Rabject), -} - -#[pyclass] -pub struct PyRabject { - inner: RabjectEnum, -} From c6acde4e1a6daf778047deb2040f8cf756806ee4 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Wed, 19 Feb 2025 19:35:26 +0800 Subject: [PATCH 03/17] refactor: rewrite color system to use `color` instead of `bevy_color` --- Cargo.lock | 291 +----------------------- Cargo.toml | 6 +- examples/arc/main.rs | 12 +- examples/arc_between_points/main.rs | 12 +- examples/basic/main.rs | 3 +- src/animation/entity/creation.rs | 10 +- src/animation/timeline.rs | 2 +- src/color/mod.rs | 34 ++- src/color/palettes.rs | 112 ++++----- src/components/rgba.rs | 45 +++- src/interpolate.rs | 23 +- src/items/svg_item.rs | 39 +--- src/items/vitem.rs | 12 +- src/lib.rs | 1 + src/render/mod.rs | 13 +- xtasks/xtask-build-examples/Cargo.toml | 6 + xtasks/xtask-build-examples/src/main.rs | 3 + 17 files changed, 204 insertions(+), 420 deletions(-) create mode 100644 xtasks/xtask-build-examples/Cargo.toml create mode 100644 xtasks/xtask-build-examples/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 166c4f8c..d3a11319 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,8 +15,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "const-random", - "getrandom", "once_cell", "version_check", "zerocopy", @@ -145,17 +143,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "assert_type_match" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-channel" version = "2.3.1" @@ -203,107 +190,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bevy_color" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df600cf7c7989d07285c537eb2ea2c053e351391f1ea8ce46cbc591258c8a6f" -dependencies = [ - "bevy_math", - "bevy_reflect", - "bytemuck", - "derive_more", - "encase", -] - -[[package]] -name = "bevy_macro_utils" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090371a2cd85574989febff6063a21d1fbbc2939e80f00fe075f62aa8e616136" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "toml_edit", -] - -[[package]] -name = "bevy_math" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389aaa6477f247f2c5d508f5a8cb800ea78442c74939c51383305fb1f5ee9939" -dependencies = [ - "derive_more", - "glam", - "itertools 0.13.0", - "smallvec", -] - -[[package]] -name = "bevy_ptr" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da2111eefa2000ea8c9dc1beee2eb7283b29b5ef90a29fe43c748df549f84ad" - -[[package]] -name = "bevy_reflect" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82af24a68fd8feff476d9672ff34d220d3f45e95ef2f2324e7cb674614d18138" -dependencies = [ - "assert_type_match", - "bevy_ptr", - "bevy_reflect_derive", - "bevy_utils", - "derive_more", - "disqualified", - "downcast-rs", - "erased-serde", - "serde", - "smallvec", - "smol_str", -] - -[[package]] -name = "bevy_reflect_derive" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8369e6e779ab3540f9dcd93d062139f62551b3d2fe1ab451c6ddf74757e22ccd" -dependencies = [ - "bevy_macro_utils", - "proc-macro2", - "quote", - "syn", - "uuid", -] - -[[package]] -name = "bevy_utils" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2993cac374b3f88cfaf59506c71f8e3e7ad8b4961f4e9864bc76e1c9e1e4400c" -dependencies = [ - "ahash", - "bevy_utils_proc_macros", - "getrandom", - "hashbrown 0.14.5", - "thread_local", - "tracing", - "web-time", -] - -[[package]] -name = "bevy_utils_proc_macros" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2606f79dfe359a88e2a59bb6cd632cd42e9d4bcd250ac8bc3a4e7657e82f4f39" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "bit-set" version = "0.8.0" @@ -439,6 +325,12 @@ dependencies = [ "unicode-width 0.1.14", ] +[[package]] +name = "color" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c387f6cef110ee8eaf12fca5586d3d303c07c594f4a5f02c768b6470b70dbd" + [[package]] name = "color_quant" version = "1.1.0" @@ -473,32 +365,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom", - "once_cell", - "tiny-keccak", -] - -[[package]] -name = "const_panic" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "013b6c2c3a14d678f38cd23994b02da3a1a1b6a5d1eedddfe63a5a5f11b13a81" - [[package]] name = "core-foundation" version = "0.9.4" @@ -581,33 +447,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "disqualified" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" - [[package]] name = "document-features" version = "0.2.10" @@ -617,49 +456,12 @@ dependencies = [ "litrs", ] -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -[[package]] -name = "encase" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0a05902cf601ed11d564128448097b98ebe3c6574bd7b6a653a3d56d54aa020" -dependencies = [ - "const_panic", - "encase_derive", - "thiserror 1.0.69", -] - -[[package]] -name = "encase_derive" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "181d475b694e2dd56ae919ce7699d344d1fd259292d590c723a50d1189a2ea85" -dependencies = [ - "encase_derive_impl", -] - -[[package]] -name = "encase_derive_impl" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97b51c5cc57ef7c5f7a0c57c250251c49ee4c28f819f87ac32f4aceabc36792" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "encode_unicode" version = "1.0.0" @@ -695,16 +497,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" -[[package]] -name = "erased-serde" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" -dependencies = [ - "serde", - "typeid", -] - [[package]] name = "event-listener" version = "5.3.1" @@ -829,10 +621,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -955,7 +745,6 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", - "serde", ] [[package]] @@ -1082,15 +871,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -1684,8 +1464,8 @@ version = "0.0.0" dependencies = [ "anyhow", "async-channel", - "bevy_color", "bytemuck", + "color", "env_logger", "glam", "image", @@ -1949,15 +1729,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - [[package]] name = "spirv" version = "0.3.0+sdk-1.3.268.0" @@ -2093,16 +1864,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "tiff" version = "0.9.1" @@ -2114,15 +1875,6 @@ dependencies = [ "weezl", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tiny-skia-path" version = "0.11.4" @@ -2183,25 +1935,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", -] - [[package]] name = "ttf-parser" version = "0.24.1" @@ -2211,12 +1944,6 @@ dependencies = [ "core_maths", ] -[[package]] -name = "typeid" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" - [[package]] name = "unicode-bidi" version = "0.3.17" @@ -2737,6 +2464,10 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "xtask-build-examples" +version = "0.1.0" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 1aa09348..b5f7fb3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,8 @@ readme = "README.md" license = "MIT" keywords = ["animation", "manim"] -# [workspace] -# members = ["packages/*"] +[workspace] +members = ["xtasks/*"] [lib] name = "ranim" crate-type = ["cdylib", "rlib"] @@ -27,12 +27,12 @@ pollster = "0.4.0" uuid = { version = "1.12.1", features = ["v4"] } wgpu = "24.0.1" anyhow = "1.0.95" -bevy_color = "0.15.3" usvg = "0.44.0" regex = "1.11.1" wgpu-profiler = "0.21.0" indicatif = "0.17.11" pyo3 = { version = "0.23.4", features = ["extension-module"] } +color = "0.2.3" # Enable a small amount of optimization in the dev profile. [profile.dev] diff --git a/examples/arc/main.rs b/examples/arc/main.rs index 39714177..5a3c9114 100644 --- a/examples/arc/main.rs +++ b/examples/arc/main.rs @@ -1,9 +1,9 @@ -use bevy_color::{Alpha, Srgba}; use env_logger::Env; use glam::vec2; use ranim::animation::entity::fading::fade_in; use ranim::animation::timeline::Timeline; use ranim::items::vitem::Arc; +use ranim::color::HueDirection; use ranim::{prelude::*, AppOptions, TimelineConstructor}; struct ArcScene; @@ -19,8 +19,8 @@ impl TimelineConstructor for ArcScene { let frame_size = (1920.0, 1080.0); let frame_start = vec2(frame_size.0 as f32 / -2.0, frame_size.1 as f32 / -2.0); - let start_color = Srgba::hex("FF8080FF").unwrap(); - let end_color = Srgba::hex("58C4DDFF").unwrap(); + let start_color = color!("#FF8080FF"); + let end_color = color!("#58C4DDFF"); let nrow = 10; let ncol = 10; @@ -33,7 +33,11 @@ impl TimelineConstructor for ArcScene { for j in 0..ncol { let angle = std::f32::consts::PI * j as f32 / (ncol - 1) as f32 * 360.0 / 180.0; let radius = step_y / 2.0; - let color = start_color.lerp(&end_color, i as f32 / (nrow - 1) as f32); + let color = start_color.lerp( + end_color, + i as f32 / (nrow - 1) as f32, + HueDirection::Increasing, + ); let offset = frame_start + vec2( j as f32 * step_x + step_x / 2.0 + j as f32 * gap + padding, diff --git a/examples/arc_between_points/main.rs b/examples/arc_between_points/main.rs index 50dca40b..30db3f78 100644 --- a/examples/arc_between_points/main.rs +++ b/examples/arc_between_points/main.rs @@ -1,12 +1,12 @@ use std::time::Instant; -use bevy_color::Srgba; use env_logger::Env; use glam::{vec2, Mat2}; use log::info; use ranim::animation::entity::creation::Color; use ranim::animation::entity::fading::fade_in; use ranim::animation::timeline::Timeline; +use ranim::color::HueDirection; use ranim::items::vitem::ArcBetweenPoints; use ranim::{prelude::*, AppOptions, SceneDesc, TimelineConstructor}; @@ -21,8 +21,8 @@ impl TimelineConstructor for ArcBetweenPointsScene { fn construct(&mut self, timeline: &mut Timeline) { let center = vec2(0.0, 0.0); - let start_color = Srgba::hex("FF8080FF").unwrap(); - let end_color = Srgba::hex("58C4DDFF").unwrap(); + let start_color = color!("#FF8080FF"); + let end_color = color!("#58C4DDFF"); let ntan = 16; let nrad = 5; @@ -37,7 +37,11 @@ impl TimelineConstructor for ArcBetweenPointsScene { let angle = angle_step * (i + 1) as f32; for j in 0..ntan { - let color = start_color.lerp(&end_color, j as f32 / (ntan - 1) as f32); + let color = start_color.lerp( + end_color, + j as f32 / (ntan - 1) as f32, + HueDirection::Increasing, + ); let vec = Mat2::from_angle(std::f32::consts::PI * 2.0 / ntan as f32 * j as f32) * vec2(rad, 0.0); let mut arc = ArcBetweenPoints { diff --git a/examples/basic/main.rs b/examples/basic/main.rs index 886f7b39..39194246 100644 --- a/examples/basic/main.rs +++ b/examples/basic/main.rs @@ -1,6 +1,5 @@ use std::f32; -use bevy_color::Srgba; use env_logger::Env; use glam::{vec3, Vec3}; use ranim::animation::entity::creation::{uncreate, unwrite, write, Color}; @@ -60,7 +59,7 @@ impl TimelineConstructor for MainScene { ]) .build(); polygon - .set_color(Srgba::hex("FF8080FF").unwrap()) + .set_color(color!("#FF8080FF")) .set_fill_opacity(0.5) .rotate(std::f32::consts::FRAC_PI_2, Vec3::Z); diff --git a/src/animation/entity/creation.rs b/src/animation/entity/creation.rs index 1d50c0af..3d4b0978 100644 --- a/src/animation/entity/creation.rs +++ b/src/animation/entity/creation.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use bevy_color::Srgba; +use color::{AlphaColor, Srgb}; use crate::animation::AnimWithParams; use crate::items::{Entity, Rabject}; @@ -183,18 +183,18 @@ pub trait Empty { pub trait Fill { fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self; - fn fill_color(&self) -> Srgba; - fn set_fill_color(&mut self, color: Srgba) -> &mut Self; + fn fill_color(&self) -> AlphaColor; + fn set_fill_color(&mut self, color: AlphaColor) -> &mut Self; } pub trait Stroke { fn set_stroke_width(&mut self, width: f32) -> &mut Self; - fn set_stroke_color(&mut self, color: Srgba) -> &mut Self; + fn set_stroke_color(&mut self, color: AlphaColor) -> &mut Self; fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self; } pub trait Color: Fill + Stroke { - fn set_color(&mut self, color: Srgba) -> &mut Self { + fn set_color(&mut self, color: AlphaColor) -> &mut Self { self.set_fill_color(color); self.set_stroke_color(color); self diff --git a/src/animation/timeline.rs b/src/animation/timeline.rs index 120c9270..7a2b74eb 100644 --- a/src/animation/timeline.rs +++ b/src/animation/timeline.rs @@ -4,7 +4,7 @@ use std::{ time::Duration, }; -use pyo3::{pyclass, pymethods, types::PyAnyMethods, Bound, Py, PyAny}; +use pyo3::{pyclass, pymethods, types::PyAnyMethods, Bound, PyAny}; use crate::{ context::WgpuContext, diff --git a/src/color/mod.rs b/src/color/mod.rs index 93b08590..2828e566 100644 --- a/src/color/mod.rs +++ b/src/color/mod.rs @@ -1,3 +1,35 @@ -pub use bevy_color::*; +use color::{AlphaColor, OpaqueColor, Srgb}; pub mod palettes; +pub use ::color::HueDirection; + +pub mod prelude { + pub use super::{rgb, rgb8, rgba, rgba8}; + pub use crate::color; +} + +pub const fn rgb8(r: u8, g: u8, b: u8) -> AlphaColor { + OpaqueColor::from_rgb8(r, g, b).with_alpha(1.0) +} + +pub const fn rgba8(r: u8, g: u8, b: u8, a: u8) -> AlphaColor { + AlphaColor::from_rgba8(r, g, b, a) +} + +pub const fn rgb(r: f32, g: f32, b: f32) -> AlphaColor { + OpaqueColor::new([r, g, b]).with_alpha(1.0) +} + +pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> AlphaColor { + AlphaColor::new([r, g, b, a]) +} + +#[macro_export] +macro_rules! color { + ($color_str:expr) => {{ + use ::color::{parse_color, Srgb}; + parse_color($color_str) + .expect("Invalid color string") + .to_alpha_color::() + }}; +} diff --git a/src/color/palettes.rs b/src/color/palettes.rs index 178f285c..362b67e8 100644 --- a/src/color/palettes.rs +++ b/src/color/palettes.rs @@ -1,129 +1,131 @@ -pub use bevy_color::palettes::*; +pub use color::palette::css; pub mod manim { - use bevy_color::Srgba; + use color::{AlphaColor, Srgb}; + + use crate::color::rgb; /// #1C758A ///
- pub const BLUE_E: Srgba = Srgba::rgb(0.11, 0.46, 0.54); + pub const BLUE_E: AlphaColor = rgb(0.11, 0.46, 0.54); /// #29ABCA ///
- pub const BLUE_D: Srgba = Srgba::rgb(0.16, 0.67, 0.79); + pub const BLUE_D: AlphaColor = rgb(0.16, 0.67, 0.79); /// #58C4DD ///
- pub const BLUE_C: Srgba = Srgba::rgb(0.35, 0.77, 0.87); + pub const BLUE_C: AlphaColor = rgb(0.35, 0.77, 0.87); /// #9CDCEB ///
- pub const BLUE_B: Srgba = Srgba::rgb(0.61, 0.86, 0.92); + pub const BLUE_B: AlphaColor = rgb(0.61, 0.86, 0.92); /// #C7E9F1 ///
- pub const BLUE_A: Srgba = Srgba::rgb(0.78, 0.91, 0.95); + pub const BLUE_A: AlphaColor = rgb(0.78, 0.91, 0.95); ///
- pub const TEAL_E: Srgba = Srgba::rgb(0.29, 0.66, 0.56); + pub const TEAL_E: AlphaColor = rgb(0.29, 0.66, 0.56); ///
- pub const TEAL_D: Srgba = Srgba::rgb(0.33, 0.76, 0.66); + pub const TEAL_D: AlphaColor = rgb(0.33, 0.76, 0.66); ///
- pub const TEAL_C: Srgba = Srgba::rgb(0.36, 0.82, 0.70); + pub const TEAL_C: AlphaColor = rgb(0.36, 0.82, 0.70); ///
- pub const TEAL_B: Srgba = Srgba::rgb(0.46, 0.87, 0.75); + pub const TEAL_B: AlphaColor = rgb(0.46, 0.87, 0.75); ///
- pub const TEAL_A: Srgba = Srgba::rgb(0.68, 0.92, 0.84); + pub const TEAL_A: AlphaColor = rgb(0.68, 0.92, 0.84); ///
- pub const GREEN_E: Srgba = Srgba::rgb(0.41, 0.61, 0.32); + pub const GREEN_E: AlphaColor = rgb(0.41, 0.61, 0.32); ///
- pub const GREEN_D: Srgba = Srgba::rgb(0.47, 0.69, 0.37); + pub const GREEN_D: AlphaColor = rgb(0.47, 0.69, 0.37); ///
- pub const GREEN_C: Srgba = Srgba::rgb(0.51, 0.76, 0.40); + pub const GREEN_C: AlphaColor = rgb(0.51, 0.76, 0.40); ///
- pub const GREEN_B: Srgba = Srgba::rgb(0.65, 0.81, 0.55); + pub const GREEN_B: AlphaColor = rgb(0.65, 0.81, 0.55); ///
- pub const GREEN_A: Srgba = Srgba::rgb(0.79, 0.89, 0.68); + pub const GREEN_A: AlphaColor = rgb(0.79, 0.89, 0.68); ///
- pub const YELLOW_E: Srgba = Srgba::rgb(0.91, 0.76, 0.11); + pub const YELLOW_E: AlphaColor = rgb(0.91, 0.76, 0.11); ///
- pub const YELLOW_D: Srgba = Srgba::rgb(0.96, 0.83, 0.27); + pub const YELLOW_D: AlphaColor = rgb(0.96, 0.83, 0.27); ///
- pub const YELLOW_C: Srgba = Srgba::rgb(1.00, 1.00, 0.00); + pub const YELLOW_C: AlphaColor = rgb(1.00, 1.00, 0.00); ///
- pub const YELLOW_B: Srgba = Srgba::rgb(1.00, 0.92, 0.58); + pub const YELLOW_B: AlphaColor = rgb(1.00, 0.92, 0.58); ///
- pub const YELLOW_A: Srgba = Srgba::rgb(1.00, 0.95, 0.71); + pub const YELLOW_A: AlphaColor = rgb(1.00, 0.95, 0.71); ///
- pub const GOLD_E: Srgba = Srgba::rgb(0.78, 0.55, 0.28); + pub const GOLD_E: AlphaColor = rgb(0.78, 0.55, 0.28); ///
- pub const GOLD_D: Srgba = Srgba::rgb(0.88, 0.63, 0.35); + pub const GOLD_D: AlphaColor = rgb(0.88, 0.63, 0.35); ///
- pub const GOLD_C: Srgba = Srgba::rgb(0.94, 0.68, 0.37); + pub const GOLD_C: AlphaColor = rgb(0.94, 0.68, 0.37); ///
- pub const GOLD_B: Srgba = Srgba::rgb(0.98, 0.72, 0.46); + pub const GOLD_B: AlphaColor = rgb(0.98, 0.72, 0.46); ///
- pub const GOLD_A: Srgba = Srgba::rgb(0.97, 0.78, 0.59); + pub const GOLD_A: AlphaColor = rgb(0.97, 0.78, 0.59); ///
- pub const RED_E: Srgba = Srgba::rgb(0.81, 0.31, 0.27); + pub const RED_E: AlphaColor = rgb(0.81, 0.31, 0.27); ///
- pub const RED_D: Srgba = Srgba::rgb(0.90, 0.35, 0.30); + pub const RED_D: AlphaColor = rgb(0.90, 0.35, 0.30); ///
- pub const RED_C: Srgba = Srgba::rgb(0.99, 0.38, 0.33); + pub const RED_C: AlphaColor = rgb(0.99, 0.38, 0.33); ///
- pub const RED_B: Srgba = Srgba::rgb(1.00, 0.50, 0.50); + pub const RED_B: AlphaColor = rgb(1.00, 0.50, 0.50); ///
- pub const RED_A: Srgba = Srgba::rgb(0.97, 0.63, 0.64); + pub const RED_A: AlphaColor = rgb(0.97, 0.63, 0.64); ///
- pub const MAROON_E: Srgba = Srgba::rgb(0.58, 0.26, 0.31); + pub const MAROON_E: AlphaColor = rgb(0.58, 0.26, 0.31); ///
- pub const MAROON_D: Srgba = Srgba::rgb(0.64, 0.30, 0.38); + pub const MAROON_D: AlphaColor = rgb(0.64, 0.30, 0.38); ///
- pub const MAROON_C: Srgba = Srgba::rgb(0.77, 0.37, 0.45); + pub const MAROON_C: AlphaColor = rgb(0.77, 0.37, 0.45); ///
- pub const MAROON_B: Srgba = Srgba::rgb(0.92, 0.57, 0.67); + pub const MAROON_B: AlphaColor = rgb(0.92, 0.57, 0.67); ///
- pub const MAROON_A: Srgba = Srgba::rgb(0.93, 0.67, 0.76); + pub const MAROON_A: AlphaColor = rgb(0.93, 0.67, 0.76); ///
- pub const PURPLE_E: Srgba = Srgba::rgb(0.39, 0.26, 0.45); + pub const PURPLE_E: AlphaColor = rgb(0.39, 0.26, 0.45); ///
- pub const PURPLE_D: Srgba = Srgba::rgb(0.44, 0.33, 0.51); + pub const PURPLE_D: AlphaColor = rgb(0.44, 0.33, 0.51); ///
- pub const PURPLE_C: Srgba = Srgba::rgb(0.60, 0.45, 0.68); + pub const PURPLE_C: AlphaColor = rgb(0.60, 0.45, 0.68); ///
- pub const PURPLE_B: Srgba = Srgba::rgb(0.69, 0.54, 0.78); + pub const PURPLE_B: AlphaColor = rgb(0.69, 0.54, 0.78); ///
- pub const PURPLE_A: Srgba = Srgba::rgb(0.79, 0.64, 0.91); + pub const PURPLE_A: AlphaColor = rgb(0.79, 0.64, 0.91); ///
- pub const GREY_E: Srgba = Srgba::rgb(0.13, 0.13, 0.13); + pub const GREY_E: AlphaColor = rgb(0.13, 0.13, 0.13); ///
- pub const GREY_D: Srgba = Srgba::rgb(0.27, 0.27, 0.27); + pub const GREY_D: AlphaColor = rgb(0.27, 0.27, 0.27); ///
- pub const GREY_C: Srgba = Srgba::rgb(0.53, 0.53, 0.53); + pub const GREY_C: AlphaColor = rgb(0.53, 0.53, 0.53); ///
- pub const GREY_B: Srgba = Srgba::rgb(0.73, 0.73, 0.73); + pub const GREY_B: AlphaColor = rgb(0.73, 0.73, 0.73); ///
- pub const GREY_A: Srgba = Srgba::rgb(0.87, 0.87, 0.87); + pub const GREY_A: AlphaColor = rgb(0.87, 0.87, 0.87); ///
- pub const WHITE: Srgba = Srgba::rgb(1.00, 1.00, 1.00); + pub const WHITE: AlphaColor = rgb(1.00, 1.00, 1.00); ///
- pub const BLACK: Srgba = Srgba::rgb(0.00, 0.00, 0.00); + pub const BLACK: AlphaColor = rgb(0.00, 0.00, 0.00); ///
- pub const GREEN_SCREEN: Srgba = Srgba::rgb(0.00, 1.00, 0.00); + pub const GREEN_SCREEN: AlphaColor = rgb(0.00, 1.00, 0.00); ///
- pub const GREY_BROWN: Srgba = Srgba::rgb(0.45, 0.39, 0.34); + pub const GREY_BROWN: AlphaColor = rgb(0.45, 0.39, 0.34); ///
- pub const LIGHT_BROWN: Srgba = Srgba::rgb(0.80, 0.52, 0.25); + pub const LIGHT_BROWN: AlphaColor = rgb(0.80, 0.52, 0.25); ///
- pub const PINK: Srgba = Srgba::rgb(0.82, 0.28, 0.74); + pub const PINK: AlphaColor = rgb(0.82, 0.28, 0.74); ///
- pub const LIGHT_PINK: Srgba = Srgba::rgb(0.86, 0.46, 0.80); + pub const LIGHT_PINK: AlphaColor = rgb(0.86, 0.46, 0.80); ///
- pub const ORANGE: Srgba = Srgba::rgb(1.00, 0.53, 0.18); + pub const ORANGE: AlphaColor = rgb(1.00, 0.53, 0.18); } diff --git a/src/components/rgba.rs b/src/components/rgba.rs index d4a3d7dd..a8568b3e 100644 --- a/src/components/rgba.rs +++ b/src/components/rgba.rs @@ -1,6 +1,7 @@ use std::ops::{Deref, DerefMut}; -use bevy_color::{ColorToComponents, LinearRgba}; +// use bevy_color::{ColorToComponents, LinearRgba}; +use color::{AlphaColor, ColorSpace, LinearSrgb, Srgb}; use glam::{vec4, Vec4}; use crate::prelude::{Interpolatable, Opacity}; @@ -29,13 +30,27 @@ impl Opacity for ComponentVec { } } -impl From for Rgba { - fn from(value: bevy_color::Srgba) -> Self { - let rgba = LinearRgba::from(value); - Self(rgba.to_vec4()) +impl From> for Rgba { + fn from(value: AlphaColor) -> Self { + let rgba = value.convert::().components; + Self(Vec4::from_array(rgba)) } } +impl Into> for Rgba { + fn into(self) -> AlphaColor { + let linear_rgba = self.0.to_array(); + AlphaColor::::new(linear_rgba).convert() + } +} + +// impl From for Rgba { +// fn from(value: bevy_color::Srgba) -> Self { +// let rgba = LinearRgba::from(value); +// Self(rgba.to_vec4()) +// } +// } + impl Default for Rgba { fn default() -> Self { vec4(1.0, 0.0, 0.0, 1.0).into() @@ -106,3 +121,23 @@ impl Interpolatable for Rgba { // partial.into() // } // } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_convertion() { + let approx = |a: f32, b: f32| (a - b).abs() < 0.001; + // The `rgb8` and `rgba8` should be in srgb + let color = AlphaColor::from_rgb8(85, 133, 217); + assert!(approx(color.components[0], 0.333)); + assert!(approx(color.components[1], 0.522)); + assert!(approx(color.components[2], 0.851)); + + let linear_rgba = Rgba::from(color); + assert!(approx(linear_rgba.x, 0.091)); + assert!(approx(linear_rgba.y, 0.235)); + assert!(approx(linear_rgba.z, 0.694)); + } +} diff --git a/src/interpolate.rs b/src/interpolate.rs index 491681fd..f7d47065 100644 --- a/src/interpolate.rs +++ b/src/interpolate.rs @@ -1,4 +1,4 @@ -use bevy_color::{LinearRgba, Srgba}; +use color::{AlphaColor, ColorSpace}; pub trait Interpolatable { fn lerp(&self, target: &Self, t: f32) -> Self; @@ -10,24 +10,9 @@ impl Interpolatable for f32 { } } -impl Interpolatable for LinearRgba { - fn lerp(&self, target: &Self, t: f32) -> Self { - Self { - red: self.red.lerp(&target.red, t), - green: self.green.lerp(&target.green, t), - blue: self.blue.lerp(&target.blue, t), - alpha: self.alpha.lerp(&target.alpha, t), - } - } -} - -impl Interpolatable for Srgba { +impl Interpolatable for AlphaColor { fn lerp(&self, other: &Self, t: f32) -> Self { - Self { - red: self.red.lerp(&other.red, t), - green: self.green.lerp(&other.green, t), - blue: self.blue.lerp(&other.blue, t), - alpha: self.alpha.lerp(&other.alpha, t), - } + // TODO: figure out to use `lerp_rect` or `lerp` + AlphaColor::lerp_rect(*self, *other, t) } } diff --git a/src/items/svg_item.rs b/src/items/svg_item.rs index 7de4131f..90edc6b0 100644 --- a/src/items/svg_item.rs +++ b/src/items/svg_item.rs @@ -1,10 +1,11 @@ use std::{cmp::Ordering, f32, path::Path, slice::Iter, vec}; -use bevy_color::{Alpha, Srgba}; +use color::{palette::css, AlphaColor, Srgb}; use glam::{vec2, vec3, Affine2, Vec3}; use log::warn; use crate::{ + color::{rgb8, rgba}, components::{vpoint::VPoint, TransformAnchor}, prelude::{Alignable, Empty, Fill, Interpolatable, Opacity, Partial, Stroke, Transformable}, render::primitives::{svg_item::SvgItemPrimitive, Extract}, @@ -118,24 +119,14 @@ impl SvgItem { let color = parse_paint(fill.paint()).with_alpha(fill.opacity().get()); vitem.set_fill_color(color); } else { - vitem.set_fill_color(bevy_color::Srgba { - red: 0.0, - green: 0.0, - blue: 0.0, - alpha: 0.0, - }); + vitem.set_fill_color(rgba(0.0, 0.0, 0.0, 0.0)); } if let Some(stroke) = path.stroke() { let color = parse_paint(stroke.paint()).with_alpha(stroke.opacity().get()); vitem.set_stroke_color(color); vitem.set_stroke_width(stroke.width().get()); } else { - vitem.set_stroke_color(bevy_color::Srgba { - red: 0.0, - green: 0.0, - blue: 0.0, - alpha: 0.0, - }); + vitem.set_stroke_color(rgba(0.0, 0.0, 0.0, 0.0)); } vitems.push(vitem); } @@ -247,7 +238,7 @@ impl Extract for SvgItemPrimitive { // MARK: Animation impl impl Stroke for SvgItem { - fn set_stroke_color(&mut self, color: bevy_color::Srgba) -> &mut Self { + fn set_stroke_color(&mut self, color: AlphaColor) -> &mut Self { self.vitems.iter_mut().for_each(|vitem| { vitem.set_stroke_color(color); }); @@ -268,13 +259,13 @@ impl Stroke for SvgItem { } impl Fill for SvgItem { - fn fill_color(&self) -> bevy_color::Srgba { + fn fill_color(&self) -> AlphaColor { self.vitems .first() .map(|vitem| vitem.fill_color()) - .unwrap_or(Srgba::WHITE) + .unwrap_or(css::WHITE) } - fn set_fill_color(&mut self, color: bevy_color::Srgba) -> &mut Self { + fn set_fill_color(&mut self, color: AlphaColor) -> &mut Self { self.vitems.iter_mut().for_each(|vitem| { vitem.set_fill_color(color); }); @@ -378,18 +369,12 @@ impl Partial for SvgItem { } // MARK: misc -fn parse_paint(paint: &usvg::Paint) -> bevy_color::Srgba { +fn parse_paint(paint: &usvg::Paint) -> AlphaColor { match paint { - usvg::Paint::Color(color) => { - bevy_color::Color::srgb_u8(color.red, color.green, color.blue).to_srgba() - } - _ => bevy_color::Srgba { - red: 0.0, - green: 1.0, - blue: 0.0, - alpha: 1.0, - }, + usvg::Paint::Color(color) => rgb8(color.red, color.green, color.blue).into(), + _ => css::GREEN, } + .into() } struct SvgElementIterator<'a> { diff --git a/src/items/vitem.rs b/src/items/vitem.rs index 1f2b53df..161e77a1 100644 --- a/src/items/vitem.rs +++ b/src/items/vitem.rs @@ -1,4 +1,4 @@ -use bevy_color::{ColorToComponents, Srgba}; +use color::{palette::css, AlphaColor, Srgb}; use glam::{vec2, vec3, vec4, Vec2, Vec3, Vec4, Vec4Swizzles}; use itertools::Itertools; @@ -185,13 +185,13 @@ impl Empty for VItem { } impl Fill for VItem { - fn fill_color(&self) -> bevy_color::Srgba { + fn fill_color(&self) -> AlphaColor { self.fill_rgbas .first() - .map(|&rgba| Srgba::from_vec4(*rgba)) - .unwrap_or(Srgba::WHITE) + .map(|&rgba| rgba.into()) + .unwrap_or(css::WHITE) } - fn set_fill_color(&mut self, color: bevy_color::Srgba) -> &mut Self { + fn set_fill_color(&mut self, color: AlphaColor) -> &mut Self { self.fill_rgbas.set_all(color); self } @@ -202,7 +202,7 @@ impl Fill for VItem { } impl Stroke for VItem { - fn set_stroke_color(&mut self, color: bevy_color::Srgba) -> &mut Self { + fn set_stroke_color(&mut self, color: AlphaColor) -> &mut Self { self.stroke_rgbas.set_all(color); self } diff --git a/src/lib.rs b/src/lib.rs index 320ac609..37e66226 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ use render::{CameraFrame, Renderable, Renderer}; use utils::rate_functions::linear; pub mod prelude { + pub use crate::color::prelude::*; pub use crate::interpolate::Interpolatable; pub use crate::animation::entity::creation::{Empty, Fill, Partial, Stroke}; diff --git a/src/render/mod.rs b/src/render/mod.rs index 21e371a0..65250634 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,11 +1,12 @@ pub mod pipelines; pub mod primitives; -use bevy_color::Color; +use color::LinearSrgb; use glam::{vec2, Mat4, Vec2, Vec3}; use primitives::RenderInstances; use crate::{ + color::rgba8, context::{RanimContext, WgpuContext}, utils::{wgpu::WgpuBuffer, PipelinesStorage}, }; @@ -217,13 +218,9 @@ impl Renderer { ); let uniforms_bind_group = CameraUniformsBindGroup::new(ctx, &uniforms_buffer); - let bg = Color::srgba_u8(0x33, 0x33, 0x33, 0xff).to_linear(); - let clear_color = wgpu::Color { - r: bg.red as f64, - g: bg.green as f64, - b: bg.blue as f64, - a: bg.alpha as f64, - }; + let bg = rgba8(0x33, 0x33, 0x33, 0xff).convert::(); + let [r, g, b, a] = bg.components.map(|x| x as f64); + let clear_color = wgpu::Color { r, g, b, a }; Self { camera, diff --git a/xtasks/xtask-build-examples/Cargo.toml b/xtasks/xtask-build-examples/Cargo.toml new file mode 100644 index 00000000..095719c1 --- /dev/null +++ b/xtasks/xtask-build-examples/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "xtask-build-examples" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/xtasks/xtask-build-examples/src/main.rs b/xtasks/xtask-build-examples/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/xtasks/xtask-build-examples/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 561bae27f08a1552ec37f63ab1fd475fb4114093 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Wed, 19 Feb 2025 19:35:52 +0800 Subject: [PATCH 04/17] chore(dep): update deps --- Cargo.lock | 44 +++++++++++++++++++++++++++++++++++++------- Cargo.toml | 4 ++-- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3a11319..144646f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,7 +622,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", ] [[package]] @@ -648,9 +660,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.29.2" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" +checksum = "17fcdf9683c406c2fc4d124afd29c0d595e22210d633cbdb8695ba9935ab1dc6" dependencies = [ "bytemuck", ] @@ -1449,7 +1461,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -2045,11 +2057,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.12.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" dependencies = [ - "getrandom", + "getrandom 0.3.1", ] [[package]] @@ -2081,6 +2093,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -2452,6 +2473,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "xml-rs" version = "0.8.23" diff --git a/Cargo.toml b/Cargo.toml index b5f7fb3a..89cfa9de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,12 @@ crate-type = ["cdylib", "rlib"] async-channel = "2.3.1" bytemuck = { version = "1.20.0", features = ["derive"] } env_logger = "0.11.6" -glam = { version = "0.29.2", features = ["bytemuck"] } +glam = { version = "0.30.0", features = ["bytemuck"] } image = "0.25.5" itertools = "0.14" log = "0.4.25" pollster = "0.4.0" -uuid = { version = "1.12.1", features = ["v4"] } +uuid = { version = "1.13.2", features = ["v4"] } wgpu = "24.0.1" anyhow = "1.0.95" usvg = "0.44.0" From 1b17014af9a6ab985d0c631ea6794cca94f24dd7 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Wed, 19 Feb 2025 19:39:03 +0800 Subject: [PATCH 05/17] style: cargo fmt and cargo clippy --- examples/arc/main.rs | 2 +- src/animation/entity.rs | 4 +--- src/animation/mod.rs | 4 +--- src/animation/timeline.rs | 6 +----- src/components/rgba.rs | 6 +++--- src/items/svg_item.rs | 3 +-- 6 files changed, 8 insertions(+), 17 deletions(-) diff --git a/examples/arc/main.rs b/examples/arc/main.rs index 5a3c9114..f8942a32 100644 --- a/examples/arc/main.rs +++ b/examples/arc/main.rs @@ -2,8 +2,8 @@ use env_logger::Env; use glam::vec2; use ranim::animation::entity::fading::fade_in; use ranim::animation::timeline::Timeline; -use ranim::items::vitem::Arc; use ranim::color::HueDirection; +use ranim::items::vitem::Arc; use ranim::{prelude::*, AppOptions, TimelineConstructor}; struct ArcScene; diff --git a/src/animation/entity.rs b/src/animation/entity.rs index 8020ff13..794c83a3 100644 --- a/src/animation/entity.rs +++ b/src/animation/entity.rs @@ -6,9 +6,7 @@ pub mod fading; pub mod freeze; pub mod interpolate; -use std::{ - sync::{Arc, Mutex}, -}; +use std::sync::{Arc, Mutex}; use freeze::{freeze, Blank}; use itertools::Itertools; diff --git a/src/animation/mod.rs b/src/animation/mod.rs index c68a69a0..6534c1bd 100644 --- a/src/animation/mod.rs +++ b/src/animation/mod.rs @@ -1,9 +1,7 @@ pub mod entity; pub mod timeline; -use std::{ - sync::{Arc, Mutex}, -}; +use std::sync::{Arc, Mutex}; use crate::{ context::WgpuContext, diff --git a/src/animation/timeline.rs b/src/animation/timeline.rs index 7a2b74eb..0d5b8329 100644 --- a/src/animation/timeline.rs +++ b/src/animation/timeline.rs @@ -1,8 +1,4 @@ -use std::{ - collections::HashMap, - fmt::Debug, - time::Duration, -}; +use std::{collections::HashMap, fmt::Debug, time::Duration}; use pyo3::{pyclass, pymethods, types::PyAnyMethods, Bound, PyAny}; diff --git a/src/components/rgba.rs b/src/components/rgba.rs index a8568b3e..b9e19661 100644 --- a/src/components/rgba.rs +++ b/src/components/rgba.rs @@ -37,9 +37,9 @@ impl From> for Rgba { } } -impl Into> for Rgba { - fn into(self) -> AlphaColor { - let linear_rgba = self.0.to_array(); +impl From for AlphaColor { + fn from(value: Rgba) -> AlphaColor { + let linear_rgba = value.0.to_array(); AlphaColor::::new(linear_rgba).convert() } } diff --git a/src/items/svg_item.rs b/src/items/svg_item.rs index 90edc6b0..2f316f61 100644 --- a/src/items/svg_item.rs +++ b/src/items/svg_item.rs @@ -371,10 +371,9 @@ impl Partial for SvgItem { // MARK: misc fn parse_paint(paint: &usvg::Paint) -> AlphaColor { match paint { - usvg::Paint::Color(color) => rgb8(color.red, color.green, color.blue).into(), + usvg::Paint::Color(color) => rgb8(color.red, color.green, color.blue), _ => css::GREEN, } - .into() } struct SvgElementIterator<'a> { From 1d401e333596559e882a1152db790237d6e8b078 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Wed, 19 Feb 2025 19:54:56 +0800 Subject: [PATCH 06/17] moved pyo3 implementation to ranimpy package --- Cargo.lock | 9 +++- Cargo.toml | 9 ++-- packages/ranimpy/Cargo.toml | 12 ++++++ .../ranimpy/pyproject.toml | 2 +- packages/ranimpy/src/items.rs | 40 ++++++++++++++++++ packages/ranimpy/src/lib.rs | 42 +++++++++++++++++++ packages/ranimpy/src/timeline.rs | 35 ++++++++++++++++ src/animation/timeline.rs | 35 +--------------- src/items/mod.rs | 41 +----------------- src/lib.rs | 42 +------------------ 10 files changed, 146 insertions(+), 121 deletions(-) create mode 100644 packages/ranimpy/Cargo.toml rename pyproject.toml => packages/ranimpy/pyproject.toml (96%) create mode 100644 packages/ranimpy/src/items.rs create mode 100644 packages/ranimpy/src/lib.rs create mode 100644 packages/ranimpy/src/timeline.rs diff --git a/Cargo.lock b/Cargo.lock index 144646f5..3272befd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1485,7 +1485,6 @@ dependencies = [ "itertools 0.14.0", "log", "pollster", - "pyo3", "regex", "usvg", "uuid", @@ -1493,6 +1492,14 @@ dependencies = [ "wgpu-profiler", ] +[[package]] +name = "ranimpy" +version = "0.1.0" +dependencies = [ + "pyo3", + "ranim", +] + [[package]] name = "rav1e" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 89cfa9de..b13d8eb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,10 @@ license = "MIT" keywords = ["animation", "manim"] [workspace] -members = ["xtasks/*"] -[lib] -name = "ranim" -crate-type = ["cdylib", "rlib"] +members = ["packages/*", "xtasks/*"] + +[workspace.dependencies] +ranim = { path = "." } [dependencies] # ranim_derive.workspace = true @@ -31,7 +31,6 @@ usvg = "0.44.0" regex = "1.11.1" wgpu-profiler = "0.21.0" indicatif = "0.17.11" -pyo3 = { version = "0.23.4", features = ["extension-module"] } color = "0.2.3" # Enable a small amount of optimization in the dev profile. diff --git a/packages/ranimpy/Cargo.toml b/packages/ranimpy/Cargo.toml new file mode 100644 index 00000000..350d47a1 --- /dev/null +++ b/packages/ranimpy/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "ranimpy" +version = "0.1.0" +edition = "2021" + +[lib] +name = "ranimpy" +crate-type = ["cdylib", "rlib"] + +[dependencies] +ranim.workspace = true +pyo3 = { version = "0.23.4", features = ["extension-module"] } diff --git a/pyproject.toml b/packages/ranimpy/pyproject.toml similarity index 96% rename from pyproject.toml rename to packages/ranimpy/pyproject.toml index 795f73c3..75ba8a60 100644 --- a/pyproject.toml +++ b/packages/ranimpy/pyproject.toml @@ -3,7 +3,7 @@ requires = ["maturin>=1.7,<2.0"] build-backend = "maturin" [project] -name = "m" +name = "ranim" requires-python = ">=3.8" classifiers = [ "Programming Language :: Rust", diff --git a/packages/ranimpy/src/items.rs b/packages/ranimpy/src/items.rs new file mode 100644 index 00000000..2ed1c89b --- /dev/null +++ b/packages/ranimpy/src/items.rs @@ -0,0 +1,40 @@ +use pyo3::{pyclass, pymethods}; +use ranim::{glam::vec3, items::{svg_item::SvgItem, vitem::VItem, Rabject}}; + +// MARK: SvgItem + +#[pyclass] +#[pyo3(name = "SvgItem")] +pub struct PySvgItem { + pub(crate) inner: Rabject, +} + +#[pymethods] +impl PySvgItem { + #[new] + pub fn new(svg_str: &str) -> Self { + Self { + inner: Rabject::new(SvgItem::from_svg(svg_str)), + } + } +} + +// MARK: VItem + +#[pyclass] +#[pyo3(name = "VItem")] +pub struct PyVItem { + pub(crate) inner: Rabject, +} + +#[pymethods] +impl PyVItem { + #[new] + pub fn new(vpoints: Vec<[f32; 3]>) -> Self { + let vpoints = vpoints.iter().map(|v| vec3(v[0], v[1], v[2])).collect(); + Self { + inner: Rabject::new(VItem::from_vpoints(vpoints)), + } + } +} + diff --git a/packages/ranimpy/src/lib.rs b/packages/ranimpy/src/lib.rs new file mode 100644 index 00000000..2766053d --- /dev/null +++ b/packages/ranimpy/src/lib.rs @@ -0,0 +1,42 @@ +pub mod timeline; +pub mod items; + +use std::path::PathBuf; + +use items::{PySvgItem, PyVItem}; +use pyo3::{ + pyfunction, pymodule, + types::{PyModule, PyModuleMethods}, + wrap_pyfunction, Bound, PyResult, +}; +use ::ranim::{animation::AnimWithParams, utils::rate_functions::linear, AppOptions, RanimRenderApp}; +use timeline::PyTimeline; + +#[pyfunction] +fn render_timeline(timeline: Bound<'_, PyTimeline>, output_dir: PathBuf) { + let options = AppOptions { + output_dir, + ..Default::default() + }; + + let mut app = RanimRenderApp::new(&options); + let mut timeline = timeline.borrow_mut(); + if timeline.elapsed_secs() == 0.0 { + timeline.forward(0.1); + } + let duration_secs = timeline.elapsed_secs(); + app.render_anim( + AnimWithParams::new(timeline.inner.clone()) + .with_duration(duration_secs) + .with_rate_func(linear), + ); +} + +#[pymodule] +fn ranim(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_function(wrap_pyfunction!(render_timeline, m)?)?; + Ok(()) +} diff --git a/packages/ranimpy/src/timeline.rs b/packages/ranimpy/src/timeline.rs new file mode 100644 index 00000000..8e364541 --- /dev/null +++ b/packages/ranimpy/src/timeline.rs @@ -0,0 +1,35 @@ +use pyo3::{pyclass, pymethods, types::PyAnyMethods, Bound, PyAny}; +use ranim::animation::timeline::Timeline; + +use crate::items::{PySvgItem, PyVItem}; + +#[pyclass] +#[pyo3(name = "Timeline")] +#[derive(Debug)] +pub struct PyTimeline { + pub(crate) inner: Timeline, +} + +#[pymethods] +impl PyTimeline { + #[new] + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + inner: Timeline::new(), + } + } + pub fn show(&mut self, rabject: &Bound<'_, PyAny>) { + if let Ok(vitem) = rabject.downcast::() { + self.inner.show(&vitem.borrow().inner); + } else if let Ok(svg_item) = rabject.downcast::() { + self.inner.show(&svg_item.borrow().inner); + } + } + pub fn forward(&mut self, secs: f32) { + self.inner.forward(secs); + } + pub fn elapsed_secs(&self) -> f32 { + self.inner.elapsed_secs() + } +} diff --git a/src/animation/timeline.rs b/src/animation/timeline.rs index 0d5b8329..42c42d15 100644 --- a/src/animation/timeline.rs +++ b/src/animation/timeline.rs @@ -1,10 +1,8 @@ use std::{collections::HashMap, fmt::Debug, time::Duration}; -use pyo3::{pyclass, pymethods, types::PyAnyMethods, Bound, PyAny}; - use crate::{ context::WgpuContext, - items::{Entity, PySvgItem, PyVItem, Rabject}, + items::{Entity, Rabject}, render::{primitives::RenderInstances, CameraFrame, RenderTextures, Renderable}, utils::{Id, PipelinesStorage}, }; @@ -14,37 +12,6 @@ use super::{ AnimWithParams, Animator, }; -#[pyclass] -#[pyo3(name = "Timeline")] -#[derive(Debug)] -pub struct PyTimeline { - pub(crate) inner: Timeline, -} - -#[pymethods] -impl PyTimeline { - #[new] - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self { - inner: Timeline::new(), - } - } - pub fn show(&mut self, rabject: &Bound<'_, PyAny>) { - if let Ok(vitem) = rabject.downcast::() { - self.inner.show(&vitem.borrow().inner); - } else if let Ok(svg_item) = rabject.downcast::() { - self.inner.show(&svg_item.borrow().inner); - } - } - pub fn forward(&mut self, secs: f32) { - self.inner.forward(secs); - } - pub fn elapsed_secs(&self) -> f32 { - self.inner.elapsed_secs() - } -} - // MARK: Timeline /// Timeline of all rabjects diff --git a/src/items/mod.rs b/src/items/mod.rs index 0c28d289..8b924321 100644 --- a/src/items/mod.rs +++ b/src/items/mod.rs @@ -1,7 +1,6 @@ use std::ops::{Deref, DerefMut}; -use glam::{vec3, Vec2}; -use pyo3::{pyclass, pymethods}; +use glam::Vec2; use crate::{ context::WgpuContext, @@ -16,44 +15,6 @@ use crate::{ pub mod svg_item; pub mod vitem; -// #[pyclass] -// pub enum PyEntity { -// VItem(PyVItem), -// } - -#[pyclass] -#[pyo3(name = "SvgItem")] -pub struct PySvgItem { - pub(crate) inner: Rabject, -} - -#[pymethods] -impl PySvgItem { - #[new] - pub fn new(svg_str: &str) -> Self { - Self { - inner: Rabject::new(svg_item::SvgItem::from_svg(svg_str)), - } - } -} - -#[pyclass] -#[pyo3(name = "VItem")] -pub struct PyVItem { - pub(crate) inner: Rabject, -} - -#[pymethods] -impl PyVItem { - #[new] - pub fn new(vpoints: Vec<[f32; 3]>) -> Self { - let vpoints = vpoints.iter().map(|v| vec3(v[0], v[1], v[2])).collect(); - Self { - inner: Rabject::new(vitem::VItem::from_vpoints(vpoints)), - } - } -} - /// An `Rabject` is a wrapper of an entity that can be rendered. /// /// The `Rabject`s with same `Id` will use the same `EntityTimeline` to animate. diff --git a/src/lib.rs b/src/lib.rs index 37e66226..a7a4bc76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,22 +6,14 @@ use std::{ time::Duration, }; -use animation::{ - timeline::{PyTimeline, Timeline}, - AnimWithParams, Animator, -}; +use animation::{timeline::Timeline, AnimWithParams, Animator}; use context::RanimContext; use file_writer::{FileWriter, FileWriterBuilder}; pub use glam; use image::{ImageBuffer, Rgba}; use indicatif::{ProgressBar, ProgressState, ProgressStyle}; -use items::{PySvgItem, PyVItem}; use log::info; -use pyo3::{ - pyfunction, pymodule, - types::{PyModule, PyModuleMethods}, - wrap_pyfunction, Bound, PyResult, -}; + use render::{CameraFrame, Renderable, Renderer}; use utils::rate_functions::linear; @@ -274,33 +266,3 @@ impl RanimRenderApp { // info!("[Scene]: SAVE FRAME TO IMAGE END, took {:?}", t.elapsed()); } } - -#[pyfunction] -fn render_timeline(timeline: Bound<'_, PyTimeline>, output_dir: PathBuf) { - let options = AppOptions { - output_dir, - ..Default::default() - }; - - let mut app = RanimRenderApp::new(&options); - let mut timeline = timeline.borrow_mut(); - if timeline.elapsed_secs() == 0.0 { - timeline.forward(0.1); - } - info!("Rendering {:?}", timeline); - let duration_secs = timeline.elapsed_secs(); - app.render_anim( - AnimWithParams::new(timeline.inner.clone()) - .with_duration(duration_secs) - .with_rate_func(linear), - ); -} - -#[pymodule] -fn ranim(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_function(wrap_pyfunction!(render_timeline, m)?)?; - Ok(()) -} From 724323d6cc95c0498d607d4cf0ec3dec748b56d8 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Wed, 19 Feb 2025 19:56:25 +0800 Subject: [PATCH 07/17] chore: gh deploy only when push to main --- .github/workflows/website.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 63224a05..d0f67806 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -36,6 +36,7 @@ jobs: with: path: ./website/public deploy: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest needs: build environment: From 614729dc2f82e9750b548ef1693020e1b22622c3 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Wed, 19 Feb 2025 19:56:39 +0800 Subject: [PATCH 08/17] style: cargo fmt and cargo clippy --- packages/ranimpy/src/items.rs | 6 ++++-- packages/ranimpy/src/lib.rs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/ranimpy/src/items.rs b/packages/ranimpy/src/items.rs index 2ed1c89b..a37bcee8 100644 --- a/packages/ranimpy/src/items.rs +++ b/packages/ranimpy/src/items.rs @@ -1,5 +1,8 @@ use pyo3::{pyclass, pymethods}; -use ranim::{glam::vec3, items::{svg_item::SvgItem, vitem::VItem, Rabject}}; +use ranim::{ + glam::vec3, + items::{svg_item::SvgItem, vitem::VItem, Rabject}, +}; // MARK: SvgItem @@ -37,4 +40,3 @@ impl PyVItem { } } } - diff --git a/packages/ranimpy/src/lib.rs b/packages/ranimpy/src/lib.rs index 2766053d..de16de1a 100644 --- a/packages/ranimpy/src/lib.rs +++ b/packages/ranimpy/src/lib.rs @@ -1,15 +1,17 @@ -pub mod timeline; pub mod items; +pub mod timeline; use std::path::PathBuf; +use ::ranim::{ + animation::AnimWithParams, utils::rate_functions::linear, AppOptions, RanimRenderApp, +}; use items::{PySvgItem, PyVItem}; use pyo3::{ pyfunction, pymodule, types::{PyModule, PyModuleMethods}, wrap_pyfunction, Bound, PyResult, }; -use ::ranim::{animation::AnimWithParams, utils::rate_functions::linear, AppOptions, RanimRenderApp}; use timeline::PyTimeline; #[pyfunction] From d002720d4b1412620754e422dbd812cec4aa7ccd Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Wed, 19 Feb 2025 21:52:51 +0800 Subject: [PATCH 09/17] fixed module name --- examples/py/main.py | 8 ++++---- packages/ranimpy/pyproject.toml | 2 +- packages/ranimpy/src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/py/main.py b/examples/py/main.py index e12ea954..5f6e8784 100644 --- a/examples/py/main.py +++ b/examples/py/main.py @@ -1,4 +1,4 @@ -import ranim +import ranimpy """ @@ -9,14 +9,14 @@ # print(ranim.Timeline) # print(ranim.Timeline()) -timeline = ranim.Timeline() +timeline = ranimpy.Timeline() with open("assets/Ghostscript_Tiger.svg") as f: svg = f.read() -svg = ranim.SvgItem(svg) +svg = ranimpy.SvgItem(svg) timeline.show(svg) timeline.forward(1.0) -ranim.render_timeline(timeline, "./") \ No newline at end of file +ranimpy.render_timeline(timeline, "./") \ No newline at end of file diff --git a/packages/ranimpy/pyproject.toml b/packages/ranimpy/pyproject.toml index 75ba8a60..503f812d 100644 --- a/packages/ranimpy/pyproject.toml +++ b/packages/ranimpy/pyproject.toml @@ -3,7 +3,7 @@ requires = ["maturin>=1.7,<2.0"] build-backend = "maturin" [project] -name = "ranim" +name = "ranimpy" requires-python = ">=3.8" classifiers = [ "Programming Language :: Rust", diff --git a/packages/ranimpy/src/lib.rs b/packages/ranimpy/src/lib.rs index de16de1a..44fa2225 100644 --- a/packages/ranimpy/src/lib.rs +++ b/packages/ranimpy/src/lib.rs @@ -35,7 +35,7 @@ fn render_timeline(timeline: Bound<'_, PyTimeline>, output_dir: PathBuf) { } #[pymodule] -fn ranim(m: &Bound<'_, PyModule>) -> PyResult<()> { +fn ranimpy(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; From 123469203875d9d3966d292656f65b0179d46b4e Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Wed, 19 Feb 2025 21:52:10 +0800 Subject: [PATCH 10/17] basic attempt to interpret script in rust successed --- Cargo.lock | 16 ++++ Cargo.toml | 1 + examples/py/main.py | 6 +- examples/py_cli/main.py | 25 ++++++ packages/ranim-cli/Cargo.toml | 14 ++++ packages/ranim-cli/src/main.rs | 132 +++++++++++++++++++++++++++++++ packages/ranim-cli/test/test.py | 4 + packages/ranimpy/src/lib.rs | 2 +- packages/ranimpy/src/timeline.rs | 2 +- src/lib.rs | 2 - src/render/mod.rs | 1 + 11 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 examples/py_cli/main.py create mode 100644 packages/ranim-cli/Cargo.toml create mode 100644 packages/ranim-cli/src/main.rs create mode 100644 packages/ranim-cli/test/test.py diff --git a/Cargo.lock b/Cargo.lock index 3272befd..f05cecd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,6 +456,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.13.0" @@ -1492,6 +1498,16 @@ dependencies = [ "wgpu-profiler", ] +[[package]] +name = "ranim-cli" +version = "0.1.0" +dependencies = [ + "dunce", + "pyo3", + "ranim", + "ranimpy", +] + [[package]] name = "ranimpy" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b13d8eb6..a89ad59f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = ["packages/*", "xtasks/*"] [workspace.dependencies] ranim = { path = "." } +ranimpy = { path = "packages/ranimpy" } [dependencies] # ranim_derive.workspace = true diff --git a/examples/py/main.py b/examples/py/main.py index 5f6e8784..1df2294b 100644 --- a/examples/py/main.py +++ b/examples/py/main.py @@ -19,4 +19,8 @@ timeline.show(svg) timeline.forward(1.0) -ranimpy.render_timeline(timeline, "./") \ No newline at end of file +ranimpy.render_timeline(timeline, "./") +print(timeline) +print(type(timeline)) + +# ranimpy.render_timeline(timeline, "./") diff --git a/examples/py_cli/main.py b/examples/py_cli/main.py new file mode 100644 index 00000000..f928068b --- /dev/null +++ b/examples/py_cli/main.py @@ -0,0 +1,25 @@ +import ranimpy + +""" + + + +""" +# print(ranim) +# print(ranim.Timeline) +# print(ranim.Timeline()) + +def build_timeline() -> ranimpy.Timeline: + timeline = ranimpy.Timeline() + + with open("assets/Ghostscript_Tiger.svg") as f: + svg = f.read() + + svg = ranimpy.SvgItem(svg) + + timeline.show(svg) + timeline.forward(1.0) + + return timeline + +print(build_timeline()) \ No newline at end of file diff --git a/packages/ranim-cli/Cargo.toml b/packages/ranim-cli/Cargo.toml new file mode 100644 index 00000000..e75015cb --- /dev/null +++ b/packages/ranim-cli/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ranim-cli" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "ranim" +path = "src/main.rs" + +[dependencies] +ranim.workspace = true +ranimpy.workspace = true +pyo3 = "0.23.4" +dunce = "1.0.5" diff --git a/packages/ranim-cli/src/main.rs b/packages/ranim-cli/src/main.rs new file mode 100644 index 00000000..6966849c --- /dev/null +++ b/packages/ranim-cli/src/main.rs @@ -0,0 +1,132 @@ +use std::{ + env::args, + ffi::CString, + path::{Path, PathBuf}, +}; + +use pyo3::{ + ffi::c_str, + types::{PyAnyMethods, PyModule}, + Python, +}; +use ranim::{animation::AnimWithParams, utils::rate_functions::linear, AppOptions, RanimRenderApp}; +use ranimpy::{ranimpy as ranimpy_module, timeline::PyTimeline}; + +fn main() { + let args = args().skip(1).collect::>(); + if args.is_empty() || args.len() > 2 { + panic!("usage: ranim []") + } + + let input_file = &args[0]; + let input_file = PathBuf::from(input_file); + assert!(input_file.extension() == Some("py".as_ref())); + + // let filename = input_file.file_name().unwrap().to_str().unwrap(); + // let filename_without_ext = input_file.file_stem().unwrap().to_str().unwrap(); + + let content = std::fs::read_to_string(&input_file).expect("failed to read from file"); + let content = CString::new(content).expect("failed to convert to CString"); + + pyo3::append_to_inittab!(ranimpy_module); + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let sys = PyModule::import(py, "sys").unwrap(); + + let executable = sys.getattr("executable").unwrap(); + let path = sys.getattr("path").unwrap(); + let version = sys.getattr("version").unwrap(); + if args.len() == 2 { + let venv_dir = Path::new(&args[1]); + let site_packages_path = + dunce::canonicalize(venv_dir.join("Lib/site-packages")).unwrap(); + path.call_method1("append", (site_packages_path.to_str().unwrap(),)) + .unwrap(); + } + println!("pyo3 sys.executable: {}", executable); + println!("pyo3 sys.path: {}", path); + println!("pyo3 sys.version: {}", version); + + let module = PyModule::from_code( + py, + &content, + c_str!("scene.py"), + c_str!("scene"), + // &CString::new(filename).unwrap(), + // &CString::new(filename_without_ext).unwrap(), + ) + .expect("failed to load module"); + + let timeline = module + .getattr("build_timeline") + .expect("failed to get build_timeline attr") + .call0() + .expect("failed to call0 on build_timeline"); + println!("{:?}", timeline); + let timeline = timeline + .downcast::() + .expect("failed to downcast to PyTimeline"); + let mut timeline = timeline.borrow_mut(); + + let mut app = RanimRenderApp::new(&AppOptions::default()); + if timeline.elapsed_secs() == 0.0 { + timeline.forward(0.1); + } + let duration_secs = timeline.elapsed_secs(); + app.render_anim( + AnimWithParams::new(timeline.inner.clone()) + .with_duration(duration_secs) + .with_rate_func(linear), + ); + }) +} + +#[cfg(test)] +mod test { + use pyo3::PyResult; + + use super::*; + + const TEST: &str = include_str!("../test/test.py"); + + #[test] + fn test_main() -> PyResult<()> { + pyo3::append_to_inittab!(ranimpy_module); + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let sys = PyModule::import(py, "sys")?; + + let executable = sys.getattr("executable")?; + let path = sys.getattr("path")?; + let version = sys.getattr("version")?; + // if args.len() == 2 { + // let venv_dir = Path::new(&args[1]); + let site_packages_path = dunce::canonicalize("../../.venv/Lib/site-packages").unwrap(); + path.call_method1("append", (site_packages_path.to_str().unwrap(),)) + .unwrap(); + // } + println!("pyo3 sys.executable: {}", executable); + println!("pyo3 sys.path: {}", path); + println!("pyo3 sys.version: {}", version); + + let content = CString::new(TEST).expect("failed to convert to CString"); + let module = PyModule::from_code( + py, + &content, + c_str!("scene.py"), + c_str!("scene"), + // &CString::new(filename).unwrap(), + // &CString::new(filename_without_ext).unwrap(), + )?; + + let timeline = module.getattr("build_timeline")?.call0()?; + + // let ranimpy = py.import("ranimpy")?; + // let timeline = py.eval(c_str!("ranimpy.Timeline()"), None, None)?; + println!("{:?}", timeline); + let timeline = timeline.downcast_into::()?; + + Ok(()) + }) + } +} diff --git a/packages/ranim-cli/test/test.py b/packages/ranim-cli/test/test.py new file mode 100644 index 00000000..09427c33 --- /dev/null +++ b/packages/ranim-cli/test/test.py @@ -0,0 +1,4 @@ +import ranimpy + +def build_timeline(): + return ranimpy.Timeline() \ No newline at end of file diff --git a/packages/ranimpy/src/lib.rs b/packages/ranimpy/src/lib.rs index 44fa2225..fa4a765c 100644 --- a/packages/ranimpy/src/lib.rs +++ b/packages/ranimpy/src/lib.rs @@ -35,7 +35,7 @@ fn render_timeline(timeline: Bound<'_, PyTimeline>, output_dir: PathBuf) { } #[pymodule] -fn ranimpy(m: &Bound<'_, PyModule>) -> PyResult<()> { +pub fn ranimpy(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/packages/ranimpy/src/timeline.rs b/packages/ranimpy/src/timeline.rs index 8e364541..c7b4323c 100644 --- a/packages/ranimpy/src/timeline.rs +++ b/packages/ranimpy/src/timeline.rs @@ -7,7 +7,7 @@ use crate::items::{PySvgItem, PyVItem}; #[pyo3(name = "Timeline")] #[derive(Debug)] pub struct PyTimeline { - pub(crate) inner: Timeline, + pub inner: Timeline, } #[pymethods] diff --git a/src/lib.rs b/src/lib.rs index a7a4bc76..cc156431 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,8 +115,6 @@ impl RenderScene for T { pub struct RanimRenderApp { ctx: RanimContext, - // world: World, - // anim: Box, renderer: Renderer, camera_frame: CameraFrame, diff --git a/src/render/mod.rs b/src/render/mod.rs index 65250634..e324ca47 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -84,6 +84,7 @@ impl CameraUniformsBindGroup { } } +/// pub struct Renderer { size: (usize, usize), camera: CameraFrame, From 26cfa6c164168a6f241f43020fa6653987ff30a3 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Thu, 20 Feb 2025 14:53:23 +0800 Subject: [PATCH 11/17] style: cargo clippy and cargo fmt --- src/render/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/render/mod.rs b/src/render/mod.rs index e324ca47..9af256ca 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -84,7 +84,7 @@ impl CameraUniformsBindGroup { } } -/// +/// A renderer for anything implementes [`Renderable`] pub struct Renderer { size: (usize, usize), camera: CameraFrame, From 8b03d8cc8d9c2fa81647ecf1508f7ae2f63e316d Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Thu, 20 Feb 2025 14:59:16 +0800 Subject: [PATCH 12/17] cleanup --- justfile | 5 ++++- packages/ranim-cli/src/main.rs | 20 ++++++++------------ packages/ranim-cli/test/test.py | 4 ---- 3 files changed, 12 insertions(+), 17 deletions(-) delete mode 100644 packages/ranim-cli/test/test.py diff --git a/justfile b/justfile index 74b27443..e352cc47 100644 --- a/justfile +++ b/justfile @@ -16,4 +16,7 @@ examples: just example palettes clean: - -rm *.log \ No newline at end of file + -rm *.log + +lint: + cargo clippy --workspace --all-targets -- -D warnings \ No newline at end of file diff --git a/packages/ranim-cli/src/main.rs b/packages/ranim-cli/src/main.rs index 6966849c..0d8f4cf4 100644 --- a/packages/ranim-cli/src/main.rs +++ b/packages/ranim-cli/src/main.rs @@ -87,10 +87,8 @@ mod test { use super::*; - const TEST: &str = include_str!("../test/test.py"); - #[test] - fn test_main() -> PyResult<()> { + fn test_downcast() -> PyResult<()> { pyo3::append_to_inittab!(ranimpy_module); pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { @@ -109,22 +107,20 @@ mod test { println!("pyo3 sys.path: {}", path); println!("pyo3 sys.version: {}", version); - let content = CString::new(TEST).expect("failed to convert to CString"); let module = PyModule::from_code( py, - &content, + c_str!(r#" + import ranimpy + + def build_timeline(): + return ranimpy.Timeline() + "#), c_str!("scene.py"), c_str!("scene"), - // &CString::new(filename).unwrap(), - // &CString::new(filename_without_ext).unwrap(), )?; let timeline = module.getattr("build_timeline")?.call0()?; - - // let ranimpy = py.import("ranimpy")?; - // let timeline = py.eval(c_str!("ranimpy.Timeline()"), None, None)?; - println!("{:?}", timeline); - let timeline = timeline.downcast_into::()?; + assert!(timeline.downcast_into::().is_ok()); Ok(()) }) diff --git a/packages/ranim-cli/test/test.py b/packages/ranim-cli/test/test.py deleted file mode 100644 index 09427c33..00000000 --- a/packages/ranim-cli/test/test.py +++ /dev/null @@ -1,4 +0,0 @@ -import ranimpy - -def build_timeline(): - return ranimpy.Timeline() \ No newline at end of file From da0a9fe8f4e171ed5093642083bf4ad683b8db07 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Thu, 20 Feb 2025 15:01:49 +0800 Subject: [PATCH 13/17] cargo fmt --- justfile | 1 + packages/ranim-cli/src/main.rs | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/justfile b/justfile index e352cc47..2e691c08 100644 --- a/justfile +++ b/justfile @@ -19,4 +19,5 @@ clean: -rm *.log lint: + cargo fmt --all --check cargo clippy --workspace --all-targets -- -D warnings \ No newline at end of file diff --git a/packages/ranim-cli/src/main.rs b/packages/ranim-cli/src/main.rs index 0d8f4cf4..84b43210 100644 --- a/packages/ranim-cli/src/main.rs +++ b/packages/ranim-cli/src/main.rs @@ -109,12 +109,14 @@ mod test { let module = PyModule::from_code( py, - c_str!(r#" + c_str!( + r#" import ranimpy def build_timeline(): return ranimpy.Timeline() - "#), + "# + ), c_str!("scene.py"), c_str!("scene"), )?; From 48be24eda8e160166dd8b9e18706abd844173079 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Fri, 21 Feb 2025 11:59:27 +0800 Subject: [PATCH 14/17] feat: read timelines from module, then build and render them all to output/ dir --- .cargo/config.toml | 2 + Cargo.lock | 27 ++++- examples/py/main.py | 22 ++-- justfile | 4 +- packages/ranim-cli/Cargo.toml | 2 + packages/ranim-cli/src/main.rs | 213 +++++++++++++++++++++++++++------ 6 files changed, 220 insertions(+), 50 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..712ed190 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +PYO3_PYTHON = "python" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f05cecd9..a0e977d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" [[package]] name = "arbitrary" @@ -331,6 +331,27 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c387f6cef110ee8eaf12fca5586d3d303c07c594f4a5f02c768b6470b70dbd" +[[package]] +name = "color-print" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aa954171903797d5623e047d9ab69d91b493657917bdfb8c2c80ecaf9cdb6f4" +dependencies = [ + "color-print-proc-macro", +] + +[[package]] +name = "color-print-proc-macro" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692186b5ebe54007e45a59aea47ece9eb4108e141326c304cdc91699a7118a22" +dependencies = [ + "nom", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -1502,6 +1523,8 @@ dependencies = [ name = "ranim-cli" version = "0.1.0" dependencies = [ + "anyhow", + "color-print", "dunce", "pyo3", "ranim", diff --git a/examples/py/main.py b/examples/py/main.py index 1df2294b..425a5126 100644 --- a/examples/py/main.py +++ b/examples/py/main.py @@ -9,18 +9,18 @@ # print(ranim.Timeline) # print(ranim.Timeline()) -timeline = ranimpy.Timeline() +def timeline_test() -> ranimpy.Timeline: + timeline = ranimpy.Timeline() -with open("assets/Ghostscript_Tiger.svg") as f: - svg = f.read() + with open("assets/Ghostscript_Tiger.svg") as f: + svg = f.read() -svg = ranimpy.SvgItem(svg) + svg = ranimpy.SvgItem(svg) -timeline.show(svg) -timeline.forward(1.0) + timeline.show(svg) + timeline.forward(1.0) -ranimpy.render_timeline(timeline, "./") -print(timeline) -print(type(timeline)) - -# ranimpy.render_timeline(timeline, "./") + return timeline + # ranimpy.render_timeline(timeline, "./") + # print(timeline) + # print(type(timeline)) diff --git a/justfile b/justfile index 2e691c08..30982d76 100644 --- a/justfile +++ b/justfile @@ -19,5 +19,5 @@ clean: -rm *.log lint: - cargo fmt --all --check - cargo clippy --workspace --all-targets -- -D warnings \ No newline at end of file + cargo clippy --workspace --all-targets -- -D warnings + cargo fmt --all --check \ No newline at end of file diff --git a/packages/ranim-cli/Cargo.toml b/packages/ranim-cli/Cargo.toml index e75015cb..2eb40a87 100644 --- a/packages/ranim-cli/Cargo.toml +++ b/packages/ranim-cli/Cargo.toml @@ -12,3 +12,5 @@ ranim.workspace = true ranimpy.workspace = true pyo3 = "0.23.4" dunce = "1.0.5" +color-print = "0.3.7" +anyhow = "1.0.96" diff --git a/packages/ranim-cli/src/main.rs b/packages/ranim-cli/src/main.rs index 84b43210..3b0bfd2a 100644 --- a/packages/ranim-cli/src/main.rs +++ b/packages/ranim-cli/src/main.rs @@ -4,15 +4,68 @@ use std::{ path::{Path, PathBuf}, }; +use color_print::cprintln; use pyo3::{ ffi::c_str, - types::{PyAnyMethods, PyModule}, - Python, + types::{PyAnyMethods, PyDictMethods, PyModule, PyModuleMethods}, + Bound, PyAny, PyResult, Python, }; use ranim::{animation::AnimWithParams, utils::rate_functions::linear, AppOptions, RanimRenderApp}; use ranimpy::{ranimpy as ranimpy_module, timeline::PyTimeline}; -fn main() { +/// 从 Python 模块中获取符合条件的 timeline 构建函数 +/// 条件: +/// 1. 函数名以 timeline_ 开头 +/// 2. 无参数 +/// 3. 返回类型为 ranimpy.Timeline +fn get_timeline_funcs<'py>( + py: &Python<'py>, + module: &Bound<'py, PyModule>, +) -> PyResult)>> { + let dict = module.dict(); + let timeline_type = py.import("ranimpy")?.getattr("Timeline")?; + + let mut result = Vec::new(); + + for (_, obj) in dict.iter() { + let Ok(func) = obj.downcast::() else { + continue; + }; + + // 检查函数名 + let name = func.getattr("__name__")?.to_string(); + if !name.starts_with("timeline_") { + continue; + } + + // 检查参数个数 + if let Ok(code) = func.getattr("__code__") { + if code.getattr("co_argcount")?.extract::()? != 0 { + continue; + } + } + + // 检查返回类型注解 + let Ok(return_type) = func + .getattr("__annotations__") + .and_then(|annotations| annotations.get_item("return")) + else { + continue; + }; + + if !return_type.is(&timeline_type) { + continue; + } + + // 提取 timeline_xxx 中的 xxx 部分 + let name = name.strip_prefix("timeline_").unwrap().to_string(); + result.push((name, obj)); + } + + Ok(result) +} + +fn main() -> anyhow::Result<()> { let args = args().skip(1).collect::>(); if args.is_empty() || args.len() > 2 { panic!("usage: ranim []") @@ -31,22 +84,24 @@ fn main() { pyo3::append_to_inittab!(ranimpy_module); pyo3::prepare_freethreaded_python(); Python::with_gil(|py| { - let sys = PyModule::import(py, "sys").unwrap(); + let sys = PyModule::import(py, "sys")?; + + let executable = sys.getattr("executable")?; + let version = sys.getattr("version")?; + cprintln!("pyo3 sys.executable: {}", executable); + cprintln!("pyo3 sys.version: {}", version); - let executable = sys.getattr("executable").unwrap(); - let path = sys.getattr("path").unwrap(); - let version = sys.getattr("version").unwrap(); + let path = sys.getattr("path")?; if args.len() == 2 { + cprintln!("using venv {:?}", args[1]); let venv_dir = Path::new(&args[1]); let site_packages_path = dunce::canonicalize(venv_dir.join("Lib/site-packages")).unwrap(); - path.call_method1("append", (site_packages_path.to_str().unwrap(),)) - .unwrap(); + path.call_method1("append", (site_packages_path.to_str().unwrap(),))?; } - println!("pyo3 sys.executable: {}", executable); - println!("pyo3 sys.path: {}", path); - println!("pyo3 sys.version: {}", version); + cprintln!("pyo3 sys.path: {}", path); + cprintln!("[ranim]: loading module {:?}...", input_file); let module = PyModule::from_code( py, &content, @@ -54,30 +109,51 @@ fn main() { c_str!("scene"), // &CString::new(filename).unwrap(), // &CString::new(filename_without_ext).unwrap(), - ) - .expect("failed to load module"); - - let timeline = module - .getattr("build_timeline") - .expect("failed to get build_timeline attr") - .call0() - .expect("failed to call0 on build_timeline"); - println!("{:?}", timeline); - let timeline = timeline - .downcast::() - .expect("failed to downcast to PyTimeline"); - let mut timeline = timeline.borrow_mut(); - - let mut app = RanimRenderApp::new(&AppOptions::default()); - if timeline.elapsed_secs() == 0.0 { - timeline.forward(0.1); - } - let duration_secs = timeline.elapsed_secs(); - app.render_anim( - AnimWithParams::new(timeline.inner.clone()) - .with_duration(duration_secs) - .with_rate_func(linear), + )?; + + cprintln!("[ranim]: getting timeline funcs..."); + let timeline_funcs = get_timeline_funcs(&py, &module)?; + + cprintln!("[ranim]: building timelines..."); + let timelines = timeline_funcs + .into_iter() + .map(|(name, func)| { + cprintln!("[ranim]: building timeline {}...", name); + let timeline = func + .call0() + .map_err(|err| anyhow::anyhow!("{:?}", err)) + .and_then(|t| { + t.downcast_into::() + .map_err(|err| anyhow::anyhow!("{:?}", err)) + })?; + Ok::<_, anyhow::Error>((name, timeline)) + }) + .collect::>>()?; + cprintln!( + "[ranim]: built timelines: {:?}", + timelines.iter().map(|(name, _)| name).collect::>() ); + + cprintln!("[ranim]: rendering timelines..."); + for (name, timeline) in timelines { + cprintln!("[ranim]: rendering timeline {}...", name); + let mut timeline = timeline.borrow_mut(); + let mut app = RanimRenderApp::new(&AppOptions { + output_dir: PathBuf::from(format!("output/{}", name)), + ..Default::default() + }); + if timeline.elapsed_secs() == 0.0 { + timeline.forward(0.1); + } + let duration_secs = timeline.elapsed_secs(); + app.render_anim( + AnimWithParams::new(timeline.inner.clone()) + .with_duration(duration_secs) + .with_rate_func(linear), + ); + } + + Ok(()) }) } @@ -127,4 +203,71 @@ mod test { Ok(()) }) } + + #[test] + fn test_python_module_methods() -> PyResult<()> { + pyo3::append_to_inittab!(ranimpy_module); + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| { + let sys = PyModule::import(py, "sys")?; + + let executable = sys.getattr("executable")?; + let path = sys.getattr("path")?; + let version = sys.getattr("version")?; + // if args.len() == 2 { + // let venv_dir = Path::new(&args[1]); + let site_packages_path = dunce::canonicalize("../../.venv/Lib/site-packages").unwrap(); + path.call_method1("append", (site_packages_path.to_str().unwrap(),)) + .unwrap(); + // } + println!("pyo3 sys.executable: {}", executable); + println!("pyo3 sys.path: {}", path); + println!("pyo3 sys.version: {}", version); + + let module = PyModule::from_code( + py, + c_str!( + r#" +import ranimpy +a = 1 + +def foo1() -> int: + return 0 + +def foo2(a: int): + pass + +def timeline_foo1(): + return ranimpy.Timeline() + +def timeline_foo2() -> ranimpy.Timeline: + return ranimpy.Timeline() + +def timeline_foo3(): + pass + +def timeline_foo4(): + a = 0 + +def timeline_foo5() -> int: + pass + +def timeline_foo6(a) -> ranimpy.Timeline: + return ranimpy.Timeline() + +foo1() +foo2(foo1()) + "# + ), + c_str!("scene.py"), + c_str!("scene"), + )?; + + let timelines = get_timeline_funcs(&py, &module)?; + assert!(timelines.len() == 1); + assert!(timelines[0].0 == "foo2"); + + Ok(()) + }) + } } From 21c42f2ef3c88d67a16099dc2bfbdf8d65107cc0 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Fri, 21 Feb 2025 21:24:46 +0800 Subject: [PATCH 15/17] use clap for cli --- Cargo.lock | 47 +++++++++++++++++++++ examples/py/main.py | 6 +-- examples/py_cli/main.py | 25 ----------- packages/ranim-cli/.cargo/config.toml | 2 + packages/ranim-cli/Cargo.toml | 13 +++--- packages/ranim-cli/src/main.rs | 60 ++++++++++++++------------- packages/ranimpy/.cargo/config.toml | 2 + 7 files changed, 93 insertions(+), 62 deletions(-) delete mode 100644 examples/py_cli/main.py create mode 100644 packages/ranim-cli/.cargo/config.toml create mode 100644 packages/ranimpy/.cargo/config.toml diff --git a/Cargo.lock b/Cargo.lock index a0e977d7..d49afff2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -315,6 +315,46 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "clap" +version = "4.5.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1524,6 +1564,7 @@ name = "ranim-cli" version = "0.1.0" dependencies = [ "anyhow", + "clap", "color-print", "dunce", "pyo3", @@ -1811,6 +1852,12 @@ dependencies = [ "float-cmp", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.3" diff --git a/examples/py/main.py b/examples/py/main.py index 425a5126..e7206f9c 100644 --- a/examples/py/main.py +++ b/examples/py/main.py @@ -21,6 +21,6 @@ def timeline_test() -> ranimpy.Timeline: timeline.forward(1.0) return timeline - # ranimpy.render_timeline(timeline, "./") - # print(timeline) - # print(type(timeline)) + +if __name__ == '__main__': + ranimpy.render_timeline(timeline_test(), "./") \ No newline at end of file diff --git a/examples/py_cli/main.py b/examples/py_cli/main.py deleted file mode 100644 index f928068b..00000000 --- a/examples/py_cli/main.py +++ /dev/null @@ -1,25 +0,0 @@ -import ranimpy - -""" - - - -""" -# print(ranim) -# print(ranim.Timeline) -# print(ranim.Timeline()) - -def build_timeline() -> ranimpy.Timeline: - timeline = ranimpy.Timeline() - - with open("assets/Ghostscript_Tiger.svg") as f: - svg = f.read() - - svg = ranimpy.SvgItem(svg) - - timeline.show(svg) - timeline.forward(1.0) - - return timeline - -print(build_timeline()) \ No newline at end of file diff --git a/packages/ranim-cli/.cargo/config.toml b/packages/ranim-cli/.cargo/config.toml new file mode 100644 index 00000000..712ed190 --- /dev/null +++ b/packages/ranim-cli/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +PYO3_PYTHON = "python" \ No newline at end of file diff --git a/packages/ranim-cli/Cargo.toml b/packages/ranim-cli/Cargo.toml index 2eb40a87..bcf12f4b 100644 --- a/packages/ranim-cli/Cargo.toml +++ b/packages/ranim-cli/Cargo.toml @@ -3,14 +3,15 @@ name = "ranim-cli" version = "0.1.0" edition = "2021" -[[bin]] -name = "ranim" -path = "src/main.rs" - [dependencies] -ranim.workspace = true ranimpy.workspace = true -pyo3 = "0.23.4" +ranim.workspace = true dunce = "1.0.5" color-print = "0.3.7" anyhow = "1.0.96" +pyo3 = { version = "0.23.4", features = ["extension-module"] } +clap = { version = "4.5.30", features = ["derive"] } + +[[bin]] +name = "ranim" +path = "src/main.rs" diff --git a/packages/ranim-cli/src/main.rs b/packages/ranim-cli/src/main.rs index 3b0bfd2a..4e1091c5 100644 --- a/packages/ranim-cli/src/main.rs +++ b/packages/ranim-cli/src/main.rs @@ -1,8 +1,4 @@ -use std::{ - env::args, - ffi::CString, - path::{Path, PathBuf}, -}; +use std::{ffi::CString, path::PathBuf}; use color_print::cprintln; use pyo3::{ @@ -65,20 +61,36 @@ fn get_timeline_funcs<'py>( Ok(result) } -fn main() -> anyhow::Result<()> { - let args = args().skip(1).collect::>(); - if args.is_empty() || args.len() > 2 { - panic!("usage: ranim []") - } +use clap::Parser; - let input_file = &args[0]; - let input_file = PathBuf::from(input_file); - assert!(input_file.extension() == Some("py".as_ref())); +/// ranim CLI 参数 +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +#[command(arg_required_else_help = true)] +struct Args { + /// Python 源文件路径 + #[arg(value_name = "INPUT_FILE")] + input_file: PathBuf, - // let filename = input_file.file_name().unwrap().to_str().unwrap(); - // let filename_without_ext = input_file.file_stem().unwrap().to_str().unwrap(); + /// 虚拟环境目录路径 + #[arg(value_name = "VENV_DIR")] + venv_dir: Option, +} + +fn main() -> anyhow::Result<()> { + // let args = args_os() + // .into_iter() + // .skip_while(|arg| !arg.to_str().unwrap().contains("ranim")) + // .collect::>(); + // println!("{:?}", args); + let args = Args::parse(); + + // 验证输入文件扩展名 + if args.input_file.extension() != Some("py".as_ref()) { + anyhow::bail!("Input file must have .py extension"); + } - let content = std::fs::read_to_string(&input_file).expect("failed to read from file"); + let content = std::fs::read_to_string(&args.input_file).expect("failed to read from file"); let content = CString::new(content).expect("failed to convert to CString"); pyo3::append_to_inittab!(ranimpy_module); @@ -92,24 +104,16 @@ fn main() -> anyhow::Result<()> { cprintln!("pyo3 sys.version: {}", version); let path = sys.getattr("path")?; - if args.len() == 2 { - cprintln!("using venv {:?}", args[1]); - let venv_dir = Path::new(&args[1]); + if let Some(venv_dir) = args.venv_dir { + cprintln!("using venv {:?}", venv_dir); let site_packages_path = dunce::canonicalize(venv_dir.join("Lib/site-packages")).unwrap(); path.call_method1("append", (site_packages_path.to_str().unwrap(),))?; } cprintln!("pyo3 sys.path: {}", path); - cprintln!("[ranim]: loading module {:?}...", input_file); - let module = PyModule::from_code( - py, - &content, - c_str!("scene.py"), - c_str!("scene"), - // &CString::new(filename).unwrap(), - // &CString::new(filename_without_ext).unwrap(), - )?; + cprintln!("[ranim]: loading module {:?}...", args.input_file); + let module = PyModule::from_code(py, &content, c_str!("scene.py"), c_str!("scene"))?; cprintln!("[ranim]: getting timeline funcs..."); let timeline_funcs = get_timeline_funcs(&py, &module)?; diff --git a/packages/ranimpy/.cargo/config.toml b/packages/ranimpy/.cargo/config.toml new file mode 100644 index 00000000..712ed190 --- /dev/null +++ b/packages/ranimpy/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +PYO3_PYTHON = "python" \ No newline at end of file From b047cab3e8027e5dea2bbd168616f24b816cc9c9 Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Sat, 22 Feb 2025 19:29:05 +0800 Subject: [PATCH 16/17] refactor: module structure --- examples/arc/main.rs | 4 +- examples/arc_between_points/main.rs | 6 +- examples/basic/main.rs | 9 +- examples/palettes/main.rs | 4 +- examples/test_scene/main.rs | 8 +- justfile | 3 + packages/ranimpy/src/items.rs | 13 +- packages/ranimpy/src/lib.rs | 13 +- packages/ranimpy/src/timeline.rs | 2 +- src/animation/{entity => }/composition.rs | 0 src/animation/{entity => }/creation.rs | 10 +- src/animation/entity.rs | 207 ---------------------- src/animation/{entity => }/fading.rs | 4 +- src/animation/{entity => }/freeze.rs | 9 +- src/animation/{entity => }/interpolate.rs | 10 +- src/animation/mod.rs | 78 +++++++- src/lib.rs | 10 +- src/render/mod.rs | 4 +- src/{animation => }/timeline.rs | 136 +++++++++++++- 19 files changed, 258 insertions(+), 272 deletions(-) rename src/animation/{entity => }/composition.rs (100%) rename src/animation/{entity => }/creation.rs (98%) delete mode 100644 src/animation/entity.rs rename src/animation/{entity => }/fading.rs (94%) rename src/animation/{entity => }/freeze.rs (84%) rename src/animation/{entity => }/interpolate.rs (90%) rename src/{animation => }/timeline.rs (61%) diff --git a/examples/arc/main.rs b/examples/arc/main.rs index f8942a32..416bcb8c 100644 --- a/examples/arc/main.rs +++ b/examples/arc/main.rs @@ -1,9 +1,9 @@ use env_logger::Env; use glam::vec2; -use ranim::animation::entity::fading::fade_in; -use ranim::animation::timeline::Timeline; +use ranim::animation::fading::fade_in; use ranim::color::HueDirection; use ranim::items::vitem::Arc; +use ranim::timeline::Timeline; use ranim::{prelude::*, AppOptions, TimelineConstructor}; struct ArcScene; diff --git a/examples/arc_between_points/main.rs b/examples/arc_between_points/main.rs index 30db3f78..dd06c71a 100644 --- a/examples/arc_between_points/main.rs +++ b/examples/arc_between_points/main.rs @@ -3,11 +3,11 @@ use std::time::Instant; use env_logger::Env; use glam::{vec2, Mat2}; use log::info; -use ranim::animation::entity::creation::Color; -use ranim::animation::entity::fading::fade_in; -use ranim::animation::timeline::Timeline; +use ranim::animation::creation::Color; +use ranim::animation::fading::fade_in; use ranim::color::HueDirection; use ranim::items::vitem::ArcBetweenPoints; +use ranim::timeline::Timeline; use ranim::{prelude::*, AppOptions, SceneDesc, TimelineConstructor}; pub struct ArcBetweenPointsScene; diff --git a/examples/basic/main.rs b/examples/basic/main.rs index 39194246..318a15d0 100644 --- a/examples/basic/main.rs +++ b/examples/basic/main.rs @@ -2,15 +2,14 @@ use std::f32; use env_logger::Env; use glam::{vec3, Vec3}; -use ranim::animation::entity::creation::{uncreate, unwrite, write, Color}; -use ranim::animation::entity::fading::{fade_in, fade_out}; -use ranim::animation::entity::interpolate::interpolate; -use ranim::animation::timeline::Timeline; - +use ranim::animation::creation::{uncreate, unwrite, write, Color}; +use ranim::animation::fading::{fade_in, fade_out}; +use ranim::animation::interpolate::interpolate; use ranim::color::palettes::manim; use ranim::items::svg_item::SvgItem; use ranim::items::vitem::{Arc, Polygon}; use ranim::items::Rabject; +use ranim::timeline::Timeline; use ranim::{prelude::*, typst_svg, AppOptions, SceneDesc, TimelineConstructor}; const SVG: &str = include_str!("../../assets/Ghostscript_Tiger.svg"); diff --git a/examples/palettes/main.rs b/examples/palettes/main.rs index 672f23c0..dc3e2ef2 100644 --- a/examples/palettes/main.rs +++ b/examples/palettes/main.rs @@ -1,9 +1,9 @@ use env_logger::Env; use glam::vec3; -use ranim::animation::entity::creation::Color; -use ranim::animation::timeline::Timeline; +use ranim::animation::creation::Color; use ranim::color::palettes::manim::*; use ranim::items::vitem::Rectangle; +use ranim::timeline::Timeline; use ranim::{prelude::*, TimelineConstructor}; struct Palettes; diff --git a/examples/test_scene/main.rs b/examples/test_scene/main.rs index 3f0ada94..28322d10 100644 --- a/examples/test_scene/main.rs +++ b/examples/test_scene/main.rs @@ -6,11 +6,8 @@ use env_logger::Env; use glam::Vec3; use ranim::{ animation::{ - entity::{ - creation::{create, unwrite, write}, - freeze::freeze, - }, - timeline::Timeline, + creation::{create, unwrite, write}, + freeze::freeze, }, components::TransformAnchor, items::{ @@ -19,6 +16,7 @@ use ranim::{ Rabject, }, prelude::*, + timeline::Timeline, AppOptions, TimelineConstructor, }; diff --git a/justfile b/justfile index 30982d76..d86b2dc0 100644 --- a/justfile +++ b/justfile @@ -18,6 +18,9 @@ examples: clean: -rm *.log +fmt: + cargo fmt --all + lint: cargo clippy --workspace --all-targets -- -D warnings cargo fmt --all --check \ No newline at end of file diff --git a/packages/ranimpy/src/items.rs b/packages/ranimpy/src/items.rs index a37bcee8..9502aa11 100644 --- a/packages/ranimpy/src/items.rs +++ b/packages/ranimpy/src/items.rs @@ -1,9 +1,20 @@ -use pyo3::{pyclass, pymethods}; +use pyo3::{ + pyclass, pymethods, pymodule, + types::{PyModule, PyModuleMethods}, + Bound, PyResult, +}; use ranim::{ glam::vec3, items::{svg_item::SvgItem, vitem::VItem, Rabject}, }; +#[pymodule] +pub fn items(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + Ok(()) +} + // MARK: SvgItem #[pyclass] diff --git a/packages/ranimpy/src/lib.rs b/packages/ranimpy/src/lib.rs index fa4a765c..6fa93dd3 100644 --- a/packages/ranimpy/src/lib.rs +++ b/packages/ranimpy/src/lib.rs @@ -3,15 +3,12 @@ pub mod timeline; use std::path::PathBuf; -use ::ranim::{ - animation::AnimWithParams, utils::rate_functions::linear, AppOptions, RanimRenderApp, -}; -use items::{PySvgItem, PyVItem}; use pyo3::{ pyfunction, pymodule, types::{PyModule, PyModuleMethods}, - wrap_pyfunction, Bound, PyResult, + wrap_pyfunction, wrap_pymodule, Bound, PyResult, }; +use ranim::{animation::AnimWithParams, utils::rate_functions::linear, AppOptions, RanimRenderApp}; use timeline::PyTimeline; #[pyfunction] @@ -36,9 +33,9 @@ fn render_timeline(timeline: Bound<'_, PyTimeline>, output_dir: PathBuf) { #[pymodule] pub fn ranimpy(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; m.add_function(wrap_pyfunction!(render_timeline, m)?)?; + m.add_class::()?; + + m.add_wrapped(wrap_pymodule!(items::items))?; Ok(()) } diff --git a/packages/ranimpy/src/timeline.rs b/packages/ranimpy/src/timeline.rs index c7b4323c..abba16d6 100644 --- a/packages/ranimpy/src/timeline.rs +++ b/packages/ranimpy/src/timeline.rs @@ -1,5 +1,5 @@ use pyo3::{pyclass, pymethods, types::PyAnyMethods, Bound, PyAny}; -use ranim::animation::timeline::Timeline; +use ranim::timeline::Timeline; use crate::items::{PySvgItem, PyVItem}; diff --git a/src/animation/entity/composition.rs b/src/animation/composition.rs similarity index 100% rename from src/animation/entity/composition.rs rename to src/animation/composition.rs diff --git a/src/animation/entity/creation.rs b/src/animation/creation.rs similarity index 98% rename from src/animation/entity/creation.rs rename to src/animation/creation.rs index 3d4b0978..b2a83872 100644 --- a/src/animation/entity/creation.rs +++ b/src/animation/creation.rs @@ -1,12 +1,8 @@ -use std::ops::Range; - -use color::{AlphaColor, Srgb}; - -use crate::animation::AnimWithParams; +use super::{AnimWithParams, EntityAnim, PureEvaluator}; use crate::items::{Entity, Rabject}; use crate::prelude::Interpolatable; - -use crate::animation::entity::{EntityAnim, PureEvaluator}; +use color::{AlphaColor, Srgb}; +use std::ops::Range; pub fn create( rabject: &Rabject, diff --git a/src/animation/entity.rs b/src/animation/entity.rs deleted file mode 100644 index 794c83a3..00000000 --- a/src/animation/entity.rs +++ /dev/null @@ -1,207 +0,0 @@ -//! The EntityAnimation is applied to an entity -//! -pub mod composition; -pub mod creation; -pub mod fading; -pub mod freeze; -pub mod interpolate; - -use std::sync::{Arc, Mutex}; - -use freeze::{freeze, Blank}; -use itertools::Itertools; - -use crate::{ - context::WgpuContext, - items::{Entity, Rabject}, - render::{primitives::RenderInstances, CameraFrame, RenderTextures, Renderable}, - utils::PipelinesStorage, -}; - -use super::{Anim, AnimWithParams, Animator, StaticAnim}; - -#[derive(Clone)] -pub struct EntityTimeline { - // pub(super) rabject_id: Id, - pub(super) cur_freeze_anim: StaticAnim, - pub(super) is_showing: bool, - pub(super) cur_anim_idx: Option, - pub(super) anims: Vec, - pub(super) end_secs: Vec, - pub(super) elapsed_secs: f32, -} - -impl EntityTimeline { - pub fn new(rabject: &Rabject) -> Self { - Self { - // rabject_id: rabject.id, - cur_freeze_anim: Arc::new(Box::new(freeze(rabject))), - cur_anim_idx: None, - is_showing: true, - anims: Vec::new(), - end_secs: Vec::new(), - elapsed_secs: 0.0, - } - } - fn push(&mut self, anim: AnimWithParams) { - let duration = anim.params.duration_secs; - self.anims.push(Arc::new(Mutex::new(Box::new(anim)))); - - let end_sec = self.end_secs.last().copied().unwrap_or(0.0) + duration; - self.end_secs.push(end_sec); - self.elapsed_secs += duration; - } - - /// Simply [`Self::append_freeze`] after used [`super::timeline::Timeline::show`], - /// and [`Self::append_blank`] after used [`super::timeline::Timeline::hide`]. - pub fn forward(&mut self, secs: f32) { - if self.is_showing { - self.append_freeze(secs); - } else { - self.append_blank(secs); - } - } - /// Append a freeze animation to the timeline - /// - /// A freeze animation just keeps the last frame of the previous animation - pub fn append_freeze(&mut self, secs: f32) { - self.push(AnimWithParams::new(self.cur_freeze_anim.clone()).with_duration(secs)) - } - /// Append a blank animation to the timeline - /// - /// A blank animation renders nothing - pub fn append_blank(&mut self, secs: f32) { - self.push(AnimWithParams::new(Blank).with_duration(secs)); - } - /// Append an animation to the timeline - pub fn append_anim( - &mut self, - mut anim: AnimWithParams>, - ) -> Rabject { - anim.update_alpha(1.0); - let end_rabject = anim.anim.rabject.clone(); - - self.cur_freeze_anim = Arc::new(Box::new(freeze(&end_rabject))); - self.push(anim); - end_rabject - } -} - -impl Animator for EntityTimeline { - fn update_alpha(&mut self, alpha: f32) { - // TODO: handle no anim - if self.anims.is_empty() { - return; - } - // trace!("update_alpha: {alpha}, {}", self.elapsed_secs); - let cur_sec = alpha * self.elapsed_secs; - let (idx, (anim, end_sec)) = self - .anims - .iter_mut() - .zip(self.end_secs.iter()) - .find_position(|(_, end_sec)| **end_sec >= cur_sec) - .unwrap(); - // trace!("{cur_sec}[{idx}] {:?}", self.end_secs); - self.cur_anim_idx = Some(idx); - - let start_sec = if idx > 0 { - self.end_secs.get(idx - 1).cloned() - } else { - None - } - .unwrap_or(0.0); - let alpha = (cur_sec - start_sec) / (end_sec - start_sec); - anim.lock().unwrap().update_alpha(alpha); - } -} - -impl Renderable for EntityTimeline { - fn render( - &self, - ctx: &WgpuContext, - render_instances: &mut RenderInstances, - pipelines: &mut PipelinesStorage, - encoder: &mut wgpu::CommandEncoder, - uniforms_bind_group: &wgpu::BindGroup, - render_textures: &RenderTextures, - camera: &CameraFrame, - ) { - if let Some(idx) = self.cur_anim_idx { - self.anims[idx].lock().unwrap().render( - ctx, - render_instances, - pipelines, - encoder, - uniforms_bind_group, - render_textures, - camera, - ); - } - } -} - -/// An animator that animates an entity -pub trait PureEvaluator: Send + Sync { - fn eval_alpha(&self, alpha: f32) -> T; -} - -impl PureEvaluator for T { - fn eval_alpha(&self, _alpha: f32) -> T { - self.clone() - } -} -impl PureEvaluator for Rabject { - fn eval_alpha(&self, _alpha: f32) -> T { - self.data.clone() - } -} - -/// An animation that is applied to an entity -/// -/// This type implements [`Animator`] and [`Renderable`] -#[derive(Clone)] -pub struct EntityAnim { - rabject: Rabject, - evaluator: Arc>>, -} - -impl Animator for EntityAnim { - fn update_alpha(&mut self, alpha: f32) { - self.rabject.data = self.evaluator.eval_alpha(alpha); - } -} - -impl Renderable for EntityAnim { - fn render( - &self, - ctx: &WgpuContext, - render_instances: &mut RenderInstances, - pipelines: &mut PipelinesStorage, - encoder: &mut wgpu::CommandEncoder, - uniforms_bind_group: &wgpu::BindGroup, - render_textures: &RenderTextures, - camera: &CameraFrame, - ) { - self.rabject.render( - ctx, - render_instances, - pipelines, - encoder, - uniforms_bind_group, - render_textures, - camera, - ); - } -} - -impl EntityAnim { - pub fn new(rabject: Rabject, func: impl PureEvaluator + 'static) -> Self { - Self { - rabject, - evaluator: Arc::new(Box::new(func)), - } - } - pub fn rabject(&self) -> &Rabject { - &self.rabject - } -} diff --git a/src/animation/entity/fading.rs b/src/animation/fading.rs similarity index 94% rename from src/animation/entity/fading.rs rename to src/animation/fading.rs index ac1f9b36..0b1b3363 100644 --- a/src/animation/entity/fading.rs +++ b/src/animation/fading.rs @@ -1,9 +1,7 @@ use crate::items::{Entity, Rabject}; use crate::prelude::Interpolatable; -use crate::animation::entity::{EntityAnim, PureEvaluator}; - -use crate::animation::AnimWithParams; +use super::{AnimWithParams, EntityAnim, PureEvaluator}; pub fn fade_in( rabject: &Rabject, diff --git a/src/animation/entity/freeze.rs b/src/animation/freeze.rs similarity index 84% rename from src/animation/entity/freeze.rs rename to src/animation/freeze.rs index 3d5acbdb..8be32f44 100644 --- a/src/animation/entity/freeze.rs +++ b/src/animation/freeze.rs @@ -1,19 +1,14 @@ +use super::{AnimWithParams, Animator, EntityAnim}; use crate::{ - animation::Animator, items::{Entity, Rabject}, - render::RenderTextures, + render::{RenderTextures, Renderable}, }; -use super::EntityAnim; -use crate::animation::AnimWithParams; - pub fn freeze(rabject: &Rabject) -> AnimWithParams> { let data = rabject.data.clone(); AnimWithParams::new(EntityAnim::new(rabject.clone(), data)) } -use crate::render::Renderable; - pub struct Blank; impl Renderable for Blank { diff --git a/src/animation/entity/interpolate.rs b/src/animation/interpolate.rs similarity index 90% rename from src/animation/entity/interpolate.rs rename to src/animation/interpolate.rs index e9712412..b62341d2 100644 --- a/src/animation/entity/interpolate.rs +++ b/src/animation/interpolate.rs @@ -1,9 +1,7 @@ -use crate::items::ConvertIntoRabject; -use crate::{interpolate::Interpolatable, items::Entity}; - -use crate::animation::{ - entity::{EntityAnim, PureEvaluator, Rabject}, - AnimWithParams, +use super::{AnimWithParams, EntityAnim, PureEvaluator, Rabject}; +use crate::{ + interpolate::Interpolatable, + items::{ConvertIntoRabject, Entity}, }; pub fn interpolate>( diff --git a/src/animation/mod.rs b/src/animation/mod.rs index 6534c1bd..bcd306db 100644 --- a/src/animation/mod.rs +++ b/src/animation/mod.rs @@ -1,10 +1,14 @@ -pub mod entity; -pub mod timeline; +pub mod composition; +pub mod creation; +pub mod fading; +pub mod freeze; +pub mod interpolate; use std::sync::{Arc, Mutex}; use crate::{ context::WgpuContext, + items::{Entity, Rabject}, render::{primitives::RenderInstances, CameraFrame, RenderTextures, Renderable}, utils::{rate_functions::smooth, PipelinesStorage}, }; @@ -52,6 +56,76 @@ impl Animator for StaticAnim { } } +/// An animator that animates an entity +pub trait PureEvaluator: Send + Sync { + fn eval_alpha(&self, alpha: f32) -> T; +} + +impl PureEvaluator for T { + fn eval_alpha(&self, _alpha: f32) -> T { + self.clone() + } +} +impl PureEvaluator for Rabject { + fn eval_alpha(&self, _alpha: f32) -> T { + self.data.clone() + } +} + +// MARK: EntityAnim + +/// An animation that is applied to an entity +/// +/// This type implements [`Animator`] and [`Renderable`] +#[derive(Clone)] +pub struct EntityAnim { + pub(crate) rabject: Rabject, + evaluator: Arc>>, +} + +impl Animator for EntityAnim { + fn update_alpha(&mut self, alpha: f32) { + self.rabject.data = self.evaluator.eval_alpha(alpha); + } +} + +impl Renderable for EntityAnim { + fn render( + &self, + ctx: &WgpuContext, + render_instances: &mut RenderInstances, + pipelines: &mut PipelinesStorage, + encoder: &mut wgpu::CommandEncoder, + uniforms_bind_group: &wgpu::BindGroup, + render_textures: &RenderTextures, + camera: &CameraFrame, + ) { + self.rabject.render( + ctx, + render_instances, + pipelines, + encoder, + uniforms_bind_group, + render_textures, + camera, + ); + } +} + +impl EntityAnim { + pub fn new(rabject: Rabject, func: impl PureEvaluator + 'static) -> Self { + Self { + rabject, + evaluator: Arc::new(Box::new(func)), + } + } + pub fn rabject(&self) -> &Rabject { + &self.rabject + } +} + +// MARK: AnimParams + /// The param of an animation #[derive(Debug, Clone)] pub struct AnimParams { diff --git a/src/lib.rs b/src/lib.rs index cc156431..df85549f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,13 +6,14 @@ use std::{ time::Duration, }; -use animation::{timeline::Timeline, AnimWithParams, Animator}; +use animation::{AnimWithParams, Animator}; use context::RanimContext; use file_writer::{FileWriter, FileWriterBuilder}; pub use glam; use image::{ImageBuffer, Rgba}; use indicatif::{ProgressBar, ProgressState, ProgressStyle}; use log::info; +use timeline::Timeline; use render::{CameraFrame, Renderable, Renderer}; use utils::rate_functions::linear; @@ -21,9 +22,9 @@ pub mod prelude { pub use crate::color::prelude::*; pub use crate::interpolate::Interpolatable; - pub use crate::animation::entity::creation::{Empty, Fill, Partial, Stroke}; - pub use crate::animation::entity::fading::Opacity; - pub use crate::animation::entity::interpolate::Alignable; + pub use crate::animation::creation::{Empty, Fill, Partial, Stroke}; + pub use crate::animation::fading::Opacity; + pub use crate::animation::interpolate::Alignable; pub use crate::items::Blueprint; pub use crate::RenderScene; @@ -34,6 +35,7 @@ pub mod prelude { pub mod color; mod file_writer; mod interpolate; +pub mod timeline; pub mod updater; pub mod animation; diff --git a/src/render/mod.rs b/src/render/mod.rs index 9af256ca..ddfa9993 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -518,11 +518,11 @@ mod test { use crate::{ animation::{ - entity::creation::{create, uncreate}, - timeline::Timeline, + creation::{create, uncreate}, AnimWithParams, }, items::{vitem::Polygon, Blueprint}, + timeline::Timeline, utils::rate_functions::linear, AppOptions, RanimRenderApp, }; diff --git a/src/animation/timeline.rs b/src/timeline.rs similarity index 61% rename from src/animation/timeline.rs rename to src/timeline.rs index 42c42d15..d791c651 100644 --- a/src/animation/timeline.rs +++ b/src/timeline.rs @@ -1,16 +1,16 @@ -use std::{collections::HashMap, fmt::Debug, time::Duration}; - use crate::{ + animation::{ + freeze::{freeze, Blank}, + Anim, AnimWithParams, Animator, EntityAnim, StaticAnim, + }, context::WgpuContext, items::{Entity, Rabject}, render::{primitives::RenderInstances, CameraFrame, RenderTextures, Renderable}, utils::{Id, PipelinesStorage}, }; - -use super::{ - entity::{EntityAnim, EntityTimeline}, - AnimWithParams, Animator, -}; +use itertools::Itertools; +use std::sync::{Arc, Mutex}; +use std::{collections::HashMap, fmt::Debug, time::Duration}; // MARK: Timeline @@ -198,3 +198,125 @@ impl Animator for Timeline { } } } + +// MARK: EntityTimeline + +#[derive(Clone)] +pub struct EntityTimeline { + // pub(super) rabject_id: Id, + pub(super) cur_freeze_anim: StaticAnim, + pub(super) is_showing: bool, + pub(super) cur_anim_idx: Option, + pub(super) anims: Vec, + pub(super) end_secs: Vec, + pub(super) elapsed_secs: f32, +} + +impl EntityTimeline { + pub fn new(rabject: &Rabject) -> Self { + Self { + // rabject_id: rabject.id, + cur_freeze_anim: Arc::new(Box::new(freeze(rabject))), + cur_anim_idx: None, + is_showing: true, + anims: Vec::new(), + end_secs: Vec::new(), + elapsed_secs: 0.0, + } + } + fn push(&mut self, anim: AnimWithParams) { + let duration = anim.params.duration_secs; + self.anims.push(Arc::new(Mutex::new(Box::new(anim)))); + + let end_sec = self.end_secs.last().copied().unwrap_or(0.0) + duration; + self.end_secs.push(end_sec); + self.elapsed_secs += duration; + } + + /// Simply [`Self::append_freeze`] after used [`super::timeline::Timeline::show`], + /// and [`Self::append_blank`] after used [`super::timeline::Timeline::hide`]. + pub fn forward(&mut self, secs: f32) { + if self.is_showing { + self.append_freeze(secs); + } else { + self.append_blank(secs); + } + } + /// Append a freeze animation to the timeline + /// + /// A freeze animation just keeps the last frame of the previous animation + pub fn append_freeze(&mut self, secs: f32) { + self.push(AnimWithParams::new(self.cur_freeze_anim.clone()).with_duration(secs)) + } + /// Append a blank animation to the timeline + /// + /// A blank animation renders nothing + pub fn append_blank(&mut self, secs: f32) { + self.push(AnimWithParams::new(Blank).with_duration(secs)); + } + /// Append an animation to the timeline + pub fn append_anim( + &mut self, + mut anim: AnimWithParams>, + ) -> Rabject { + anim.update_alpha(1.0); + let end_rabject = anim.anim.rabject.clone(); + + self.cur_freeze_anim = Arc::new(Box::new(freeze(&end_rabject))); + self.push(anim); + end_rabject + } +} + +impl Animator for EntityTimeline { + fn update_alpha(&mut self, alpha: f32) { + // TODO: handle no anim + if self.anims.is_empty() { + return; + } + // trace!("update_alpha: {alpha}, {}", self.elapsed_secs); + let cur_sec = alpha * self.elapsed_secs; + let (idx, (anim, end_sec)) = self + .anims + .iter_mut() + .zip(self.end_secs.iter()) + .find_position(|(_, end_sec)| **end_sec >= cur_sec) + .unwrap(); + // trace!("{cur_sec}[{idx}] {:?}", self.end_secs); + self.cur_anim_idx = Some(idx); + + let start_sec = if idx > 0 { + self.end_secs.get(idx - 1).cloned() + } else { + None + } + .unwrap_or(0.0); + let alpha = (cur_sec - start_sec) / (end_sec - start_sec); + anim.lock().unwrap().update_alpha(alpha); + } +} + +impl Renderable for EntityTimeline { + fn render( + &self, + ctx: &WgpuContext, + render_instances: &mut RenderInstances, + pipelines: &mut PipelinesStorage, + encoder: &mut wgpu::CommandEncoder, + uniforms_bind_group: &wgpu::BindGroup, + render_textures: &RenderTextures, + camera: &CameraFrame, + ) { + if let Some(idx) = self.cur_anim_idx { + self.anims[idx].lock().unwrap().render( + ctx, + render_instances, + pipelines, + encoder, + uniforms_bind_group, + render_textures, + camera, + ); + } + } +} From e15f33d227656c4d85f174211eb69585355a3a3b Mon Sep 17 00:00:00 2001 From: AzurIce <973562770@qq.com> Date: Sat, 22 Feb 2025 20:40:53 +0800 Subject: [PATCH 17/17] feat: created pyo3 bindings for Transformable methods --- Cargo.lock | 10 ++ packages/ranimpy/Cargo.toml | 2 +- .../ranimpy/examples/hello_ranimpy}/main.py | 3 +- packages/ranimpy/src/items.rs | 108 +++++++++++++++++- packages/ranimpy/src/lib.rs | 9 +- 5 files changed, 126 insertions(+), 6 deletions(-) rename {examples/py => packages/ranimpy/examples/hello_ranimpy}/main.py (86%) diff --git a/Cargo.lock b/Cargo.lock index d49afff2..b87e0977 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -935,6 +935,15 @@ dependencies = [ "syn", ] +[[package]] +name = "inventory" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b12ebb6799019b044deaf431eadfe23245b259bba5a2c0796acec3943a3cdb" +dependencies = [ + "rustversion", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1422,6 +1431,7 @@ checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc" dependencies = [ "cfg-if", "indoc", + "inventory", "libc", "memoffset", "once_cell", diff --git a/packages/ranimpy/Cargo.toml b/packages/ranimpy/Cargo.toml index 350d47a1..dc91dfbb 100644 --- a/packages/ranimpy/Cargo.toml +++ b/packages/ranimpy/Cargo.toml @@ -9,4 +9,4 @@ crate-type = ["cdylib", "rlib"] [dependencies] ranim.workspace = true -pyo3 = { version = "0.23.4", features = ["extension-module"] } +pyo3 = { version = "0.23.4", features = ["extension-module", "multiple-pymethods"] } diff --git a/examples/py/main.py b/packages/ranimpy/examples/hello_ranimpy/main.py similarity index 86% rename from examples/py/main.py rename to packages/ranimpy/examples/hello_ranimpy/main.py index e7206f9c..814b21a4 100644 --- a/examples/py/main.py +++ b/packages/ranimpy/examples/hello_ranimpy/main.py @@ -12,10 +12,11 @@ def timeline_test() -> ranimpy.Timeline: timeline = ranimpy.Timeline() - with open("assets/Ghostscript_Tiger.svg") as f: + with open("../../assets/Ghostscript_Tiger.svg") as f: svg = f.read() svg = ranimpy.SvgItem(svg) + svg.shift((100, 100, 0)) timeline.show(svg) timeline.forward(1.0) diff --git a/packages/ranimpy/src/items.rs b/packages/ranimpy/src/items.rs index 9502aa11..1c94a3a6 100644 --- a/packages/ranimpy/src/items.rs +++ b/packages/ranimpy/src/items.rs @@ -1,5 +1,5 @@ use pyo3::{ - pyclass, pymethods, pymodule, + pyclass, pymethods, types::{PyModule, PyModuleMethods}, Bound, PyResult, }; @@ -8,7 +8,6 @@ use ranim::{ items::{svg_item::SvgItem, vitem::VItem, Rabject}, }; -#[pymodule] pub fn items(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; @@ -17,6 +16,7 @@ pub fn items(m: &Bound<'_, PyModule>) -> PyResult<()> { // MARK: SvgItem +/// SvgItem #[pyclass] #[pyo3(name = "SvgItem")] pub struct PySvgItem { @@ -51,3 +51,107 @@ impl PyVItem { } } } +// MARK: macro impl_transformable + +use ranim::components::TransformAnchor; +use ranim::glam::{IVec3, Vec3}; +use ranim::prelude::*; + +macro_rules! impl_transformable { + ($py_class:ident) => { + #[pymethods] + impl $py_class { + // 基础变换方法 + fn shift(&mut self, shift: (f32, f32, f32)) { + self.inner.shift(Vec3::from(shift)); + } + + fn scale(&mut self, scale: (f32, f32, f32)) { + self.inner.scale(Vec3::from(scale)); + } + + // fn scale_by_anchor( + // &mut self, + // scale: (f32, f32, f32), + // anchor: TransformAnchor + // ) { + // self.inner.scale_by_anchor(Vec3::from(scale), anchor); + // } + + // 旋转方法 + fn rotate(&mut self, angle: f32, axis: (f32, f32, f32)) { + self.inner.rotate(angle, Vec3::from(axis)); + } + + fn rotate_by_point( + &mut self, + angle: f32, + axis: (f32, f32, f32), + anchor: (f32, f32, f32), + ) { + self.inner.rotate_by_anchor( + angle, + Vec3::from(axis), + TransformAnchor::Point(Vec3::from(anchor)), + ); + } + + fn rotate_by_edge( + &mut self, + angle: f32, + axis: (f32, f32, f32), + anchor: (i32, i32, i32), + ) { + self.inner.rotate_by_anchor( + angle, + Vec3::from(axis), + TransformAnchor::Edge(IVec3::from(anchor)), + ); + } + // 定位方法 + fn put_center_on(&mut self, point: (f32, f32, f32)) { + self.inner.put_center_on(Vec3::from(point)); + } + + fn put_start_and_end_on(&mut self, start: (f32, f32, f32), end: (f32, f32, f32)) { + self.inner + .put_start_and_end_on(Vec3::from(start), Vec3::from(end)); + } + + // 获取信息方法 + #[getter] + fn start_position(&self) -> Option<(f32, f32, f32)> { + self.inner.get_start_position().map(Into::into) + } + + #[getter] + fn end_position(&self) -> Option<(f32, f32, f32)> { + self.inner.get_end_position().map(Into::into) + } + + #[getter] + fn bounding_box(&self) -> Vec<(f32, f32, f32)> { + self.inner + .get_bounding_box() + .iter() + .map(|v| (*v).into()) + .collect() + } + + fn get_bounding_box_point(&self, edge: (i32, i32, i32)) -> (f32, f32, f32) { + self.inner.get_bounding_box_point(IVec3::from(edge)).into() + } + + fn get_bounding_box_corners(&self) -> Vec<(f32, f32, f32)> { + self.inner + .get_bounding_box() + .iter() + .map(|v| (*v).into()) + .collect() + } + } + }; +} + +impl_transformable! {PyVItem} +impl_transformable! {PySvgItem} diff --git a/packages/ranimpy/src/lib.rs b/packages/ranimpy/src/lib.rs index 6fa93dd3..dff20bd2 100644 --- a/packages/ranimpy/src/lib.rs +++ b/packages/ranimpy/src/lib.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use pyo3::{ pyfunction, pymodule, types::{PyModule, PyModuleMethods}, - wrap_pyfunction, wrap_pymodule, Bound, PyResult, + wrap_pyfunction, Bound, PyResult, }; use ranim::{animation::AnimWithParams, utils::rate_functions::linear, AppOptions, RanimRenderApp}; use timeline::PyTimeline; @@ -35,7 +35,12 @@ fn render_timeline(timeline: Bound<'_, PyTimeline>, output_dir: PathBuf) { pub fn ranimpy(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(render_timeline, m)?)?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; - m.add_wrapped(wrap_pymodule!(items::items))?; + // m.add_wrapped(wrap_pymodule!(items::items))?; + // let submodule = PyModule::new(py, "items")?; + // items::items(&submodule)?; + // m.add_submodule(&submodule)?; Ok(()) }