Merge pull request #2 from enaut/copilot/add-cmdline-parameters-svg-export
Add --export-svg CLI parameter to turtle_main macro
This commit is contained in:
commit
3820f20048
31
README.md
31
README.md
@ -195,7 +195,32 @@ Add the `svg` feature to enable SVG export functionality:
|
|||||||
cargo run --example export_svg --features svg
|
cargo run --example export_svg --features svg
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usage
|
### Command-Line SVG Export
|
||||||
|
|
||||||
|
When using the `turtle_main` macro with the `svg` feature enabled, you can export drawings directly to SVG files using the `--export-svg` command-line parameter:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Export any example to SVG without showing the window
|
||||||
|
cargo run --example macro_demo --features svg -- --export-svg output.svg
|
||||||
|
|
||||||
|
# Works with all turtle_main-based examples
|
||||||
|
cargo run --example hello_turtle --features svg -- --export-svg square.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- Execute all drawing commands instantly (no animation)
|
||||||
|
- Export the result to an SVG file
|
||||||
|
- Exit immediately without opening a window
|
||||||
|
|
||||||
|
**Note**: The program still requires a display context to initialize. In headless environments, use `xvfb-run`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xvfb-run -a cargo run --example macro_demo --features svg -- --export-svg output.svg
|
||||||
|
```
|
||||||
|
|
||||||
|
### Programmatic SVG Export
|
||||||
|
|
||||||
|
You can also export SVG programmatically from your code:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use turtle_lib::*;
|
use turtle_lib::*;
|
||||||
@ -239,6 +264,10 @@ cargo run --example nikolaus
|
|||||||
# SVG export example (requires --features svg)
|
# SVG export example (requires --features svg)
|
||||||
cargo run --example export_svg --features svg
|
cargo run --example export_svg --features svg
|
||||||
|
|
||||||
|
# Export any example to SVG using CLI parameter (requires --features svg)
|
||||||
|
cargo run --example macro_demo --features svg -- --export-svg output.svg
|
||||||
|
cargo run --example hello_turtle --features svg -- --export-svg square.svg
|
||||||
|
|
||||||
# Logging example - shows how to enable debug output
|
# Logging example - shows how to enable debug output
|
||||||
cargo run --example logging_example
|
cargo run --example logging_example
|
||||||
RUST_LOG=turtle_lib=debug cargo run --example logging_example
|
RUST_LOG=turtle_lib=debug cargo run --example logging_example
|
||||||
|
|||||||
@ -16,6 +16,14 @@ use syn::{parse_macro_input, ItemFn};
|
|||||||
/// - Creates a turtle instance (`turtle`)
|
/// - Creates a turtle instance (`turtle`)
|
||||||
/// - Sets up the `TurtleApp` with your drawing commands
|
/// - Sets up the `TurtleApp` with your drawing commands
|
||||||
/// - Provides a main loop with rendering and quit handling (ESC or Q)
|
/// - Provides a main loop with rendering and quit handling (ESC or Q)
|
||||||
|
/// - Adds command-line parameter support for SVG export (when `svg` feature is enabled)
|
||||||
|
///
|
||||||
|
/// # Command-Line Parameters
|
||||||
|
///
|
||||||
|
/// When the `svg` feature is enabled, the following command-line parameter is available:
|
||||||
|
///
|
||||||
|
/// * `--export-svg <filename>` - Exports the drawing to an SVG file and exits immediately
|
||||||
|
/// without opening the window. Example: `cargo run --features svg -- --export-svg output.svg`
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@ -45,6 +53,13 @@ use syn::{parse_macro_input, ItemFn};
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// # SVG Export Example
|
||||||
|
///
|
||||||
|
/// ```bash
|
||||||
|
/// # Run with SVG export (requires svg feature)
|
||||||
|
/// cargo run --package turtle-lib --example macro_demo --features svg -- --export-svg output.svg
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// This expands to approximately:
|
/// This expands to approximately:
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
@ -53,6 +68,10 @@ use syn::{parse_macro_input, ItemFn};
|
|||||||
///
|
///
|
||||||
/// #[macroquad::main("My Turtle Drawing")]
|
/// #[macroquad::main("My Turtle Drawing")]
|
||||||
/// async fn main() {
|
/// async fn main() {
|
||||||
|
/// // Parse CLI args for --export-svg flag
|
||||||
|
/// let args: Vec<String> = std::env::args().collect();
|
||||||
|
/// // ... (argument parsing logic)
|
||||||
|
///
|
||||||
/// let mut turtle = create_turtle_plan();
|
/// let mut turtle = create_turtle_plan();
|
||||||
///
|
///
|
||||||
/// // Your drawing code here
|
/// // Your drawing code here
|
||||||
@ -63,6 +82,8 @@ use syn::{parse_macro_input, ItemFn};
|
|||||||
///
|
///
|
||||||
/// let mut app = TurtleApp::new().with_commands(turtle.build());
|
/// let mut app = TurtleApp::new().with_commands(turtle.build());
|
||||||
///
|
///
|
||||||
|
/// // If --export-svg flag is present, export and exit
|
||||||
|
/// // Otherwise, enter normal rendering loop
|
||||||
/// loop {
|
/// loop {
|
||||||
/// clear_background(WHITE);
|
/// clear_background(WHITE);
|
||||||
/// app.update();
|
/// app.update();
|
||||||
@ -97,11 +118,60 @@ pub fn turtle_main(args: TokenStream, input: TokenStream) -> TokenStream {
|
|||||||
// Check if the function has the expected signature
|
// Check if the function has the expected signature
|
||||||
let has_turtle_param = input_fn.sig.inputs.len() == 1;
|
let has_turtle_param = input_fn.sig.inputs.len() == 1;
|
||||||
|
|
||||||
|
// Note: The following code has some duplication between the two branches
|
||||||
|
// (with/without turtle parameter). This is intentional in proc macros as
|
||||||
|
// we're generating different code paths, and extracting the common parts
|
||||||
|
// into helper functions would make the macro more complex without significant benefit.
|
||||||
|
|
||||||
let expanded = if has_turtle_param {
|
let expanded = if has_turtle_param {
|
||||||
// Function takes a turtle parameter
|
// Function takes a turtle parameter
|
||||||
quote! {
|
quote! {
|
||||||
#[macroquad::main(#window_title)]
|
#[macroquad::main(#window_title)]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
// Parse command-line arguments for SVG export FIRST (before any graphics init)
|
||||||
|
let export_svg_path = turtle_lib::export::parse_svg_export_arg();
|
||||||
|
|
||||||
|
// Handle SVG export mode (execute instantly without rendering)
|
||||||
|
if let Some(filename) = export_svg_path {
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
{
|
||||||
|
let mut turtle = turtle_lib::create_turtle_plan();
|
||||||
|
#fn_name(&mut turtle);
|
||||||
|
|
||||||
|
// Create app and execute instantly
|
||||||
|
let mut app = turtle_lib::TurtleApp::new()
|
||||||
|
.with_commands(turtle.build());
|
||||||
|
|
||||||
|
// Set instant speed to execute all commands immediately
|
||||||
|
// Use a high draw call limit (1000) to ensure all commands execute in one frame
|
||||||
|
app.set_all_turtles_speed(turtle_lib::AnimationSpeed::Instant(1000));
|
||||||
|
|
||||||
|
// Execute all commands instantly (no rendering needed)
|
||||||
|
while !app.all_animations_complete() {
|
||||||
|
app.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export to SVG
|
||||||
|
match app.export_drawing(&filename, turtle_lib::export::DrawingFormat::Svg) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("SVG exported successfully to: {}", filename);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error exporting SVG: {:?}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "svg"))]
|
||||||
|
{
|
||||||
|
eprintln!("Error: SVG export feature is not enabled.");
|
||||||
|
eprintln!("Please rebuild with --features svg");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal rendering mode (with window)
|
||||||
let mut turtle = turtle_lib::create_turtle_plan();
|
let mut turtle = turtle_lib::create_turtle_plan();
|
||||||
|
|
||||||
// Call the user's function with the turtle
|
// Call the user's function with the turtle
|
||||||
@ -139,9 +209,51 @@ pub fn turtle_main(args: TokenStream, input: TokenStream) -> TokenStream {
|
|||||||
quote! {
|
quote! {
|
||||||
#[macroquad::main(#window_title)]
|
#[macroquad::main(#window_title)]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let mut turtle = turtle_lib::create_turtle_plan();
|
// Parse command-line arguments for SVG export FIRST (before any graphics init)
|
||||||
|
let export_svg_path = turtle_lib::export::parse_svg_export_arg();
|
||||||
|
|
||||||
// Inline the user's code
|
// Handle SVG export mode (execute instantly without rendering)
|
||||||
|
if let Some(filename) = export_svg_path {
|
||||||
|
#[cfg(feature = "svg")]
|
||||||
|
{
|
||||||
|
let mut turtle = turtle_lib::create_turtle_plan();
|
||||||
|
#fn_block
|
||||||
|
|
||||||
|
// Create app and execute instantly
|
||||||
|
let mut app = turtle_lib::TurtleApp::new()
|
||||||
|
.with_commands(turtle.build());
|
||||||
|
|
||||||
|
// Set instant speed to execute all commands immediately
|
||||||
|
// Use a high draw call limit (1000) to ensure all commands execute in one frame
|
||||||
|
app.set_all_turtles_speed(turtle_lib::AnimationSpeed::Instant(1000));
|
||||||
|
|
||||||
|
// Execute all commands instantly (no rendering needed)
|
||||||
|
while !app.all_animations_complete() {
|
||||||
|
app.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export to SVG
|
||||||
|
match app.export_drawing(&filename, turtle_lib::export::DrawingFormat::Svg) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("SVG exported successfully to: {}", filename);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error exporting SVG: {:?}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "svg"))]
|
||||||
|
{
|
||||||
|
eprintln!("Error: SVG export feature is not enabled.");
|
||||||
|
eprintln!("Please rebuild with --features svg");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal rendering mode (with window)
|
||||||
|
let mut turtle = turtle_lib::create_turtle_plan();
|
||||||
#fn_block
|
#fn_block
|
||||||
|
|
||||||
let mut app = turtle_lib::TurtleApp::new()
|
let mut app = turtle_lib::TurtleApp::new()
|
||||||
|
|||||||
@ -80,10 +80,10 @@ fn draw(turtle: &mut TurtlePlan) {
|
|||||||
|
|
||||||
// Draw and label 4 points (one per quadrant)
|
// Draw and label 4 points (one per quadrant)
|
||||||
let points = vec![
|
let points = vec![
|
||||||
(vec2(120.0, 100.0), "A(2|1)"),
|
(vec2(100.0, 50.0), "A(2|1)"),
|
||||||
(vec2(-120.0, 100.0), "B(-2|1)"),
|
(vec2(-100.0, 50.0), "B(-2|1)"),
|
||||||
(vec2(-120.0, -100.0), "C(-2|-1)"),
|
(vec2(-100.0, -50.0), "C(-2|-1)"),
|
||||||
(vec2(120.0, -100.0), "D(2|-1)"),
|
(vec2(100.0, -50.0), "D(2|-1)"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (position, label) in points {
|
for (position, label) in points {
|
||||||
|
|||||||
36
turtle-lib/examples/test_svg_export.rs
Normal file
36
turtle-lib/examples/test_svg_export.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//! Test example for CLI SVG export feature
|
||||||
|
//!
|
||||||
|
//! Run this with: cargo run --package turtle-lib --example test_svg_export --features svg -- --export-svg test_output.svg
|
||||||
|
|
||||||
|
use turtle_lib::*;
|
||||||
|
|
||||||
|
#[turtle_main("SVG Export Test")]
|
||||||
|
fn draw_test(turtle: &mut TurtlePlan) {
|
||||||
|
turtle.set_pen_color(RED);
|
||||||
|
turtle.set_pen_width(3.0);
|
||||||
|
|
||||||
|
// Draw a square
|
||||||
|
for _ in 0..4 {
|
||||||
|
turtle.forward(100.0);
|
||||||
|
turtle.right(90.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a circle
|
||||||
|
turtle.set_pen_color(BLUE);
|
||||||
|
turtle.pen_up();
|
||||||
|
turtle.forward(150.0);
|
||||||
|
turtle.pen_down();
|
||||||
|
turtle.circle_left(50.0, 360.0, 36);
|
||||||
|
|
||||||
|
// Draw a filled triangle
|
||||||
|
turtle.set_fill_color(GREEN);
|
||||||
|
turtle.pen_up();
|
||||||
|
turtle.go_to(vec2(-50.0, 100.0));
|
||||||
|
turtle.pen_down();
|
||||||
|
turtle.begin_fill();
|
||||||
|
for _ in 0..3 {
|
||||||
|
turtle.forward(80.0);
|
||||||
|
turtle.right(120.0);
|
||||||
|
}
|
||||||
|
turtle.end_fill();
|
||||||
|
}
|
||||||
@ -24,3 +24,15 @@ pub trait DrawingExporter {
|
|||||||
/// Returns an error if the export fails (e.g., file I/O error)
|
/// Returns an error if the export fails (e.g., file I/O error)
|
||||||
fn export(&self, world: &TurtleWorld, filename: &str) -> Result<(), ExportError>;
|
fn export(&self, world: &TurtleWorld, filename: &str) -> Result<(), ExportError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_svg_export_arg() -> Option<String> {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
let mut i = 1;
|
||||||
|
while i < args.len() {
|
||||||
|
if args[i] == "--export-svg" && i + 1 < args.len() {
|
||||||
|
return Some(args[i + 1].clone());
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|||||||
@ -367,6 +367,23 @@ impl TurtleApp {
|
|||||||
.all(|turtle| turtle.tween_controller.is_complete())
|
.all(|turtle| turtle.tween_controller.is_complete())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if all animations are complete (alias for is_complete)
|
||||||
|
#[must_use]
|
||||||
|
pub fn all_animations_complete(&self) -> bool {
|
||||||
|
self.is_complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the animation speed for all turtles
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `speed` - The animation speed to set for all turtles
|
||||||
|
pub fn set_all_turtles_speed(&mut self, speed: AnimationSpeed) {
|
||||||
|
for turtle in &mut self.world.turtles {
|
||||||
|
turtle.set_speed(speed);
|
||||||
|
turtle.tween_controller.set_speed(speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get reference to the world state
|
/// Get reference to the world state
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn world(&self) -> &TurtleWorld {
|
pub fn world(&self) -> &TurtleWorld {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user