1+ mod export;
2+
13use clap:: { Args , Parser , Subcommand } ;
24use fern:: colors:: { Color , ColoredLevelConfig } ;
35use futures:: executor:: block_on;
46use graph_craft:: document:: * ;
5- use graph_craft:: graphene_compiler:: { Compiler , Executor } ;
7+ use graph_craft:: graphene_compiler:: Compiler ;
68use graph_craft:: proto:: ProtoNetwork ;
79use graph_craft:: util:: load_network;
810use graph_craft:: wasm_application_io:: EditorPreferences ;
9- use graphene_std:: application_io:: { ApplicationIo , NodeGraphUpdateMessage , NodeGraphUpdateSender , RenderConfig } ;
11+ use graphene_std:: application_io:: { ApplicationIo , NodeGraphUpdateMessage , NodeGraphUpdateSender } ;
1012use graphene_std:: text:: FontCache ;
1113use graphene_std:: wasm_application_io:: { WasmApplicationIo , WasmEditorApi } ;
1214use interpreted_executor:: dynamic_executor:: DynamicExecutor ;
@@ -44,17 +46,34 @@ enum Command {
4446 /// Path to the .graphite document
4547 document : PathBuf ,
4648 } ,
47- /// Help message for run .
48- Run {
49+ /// Export a .graphite document to a file (SVG, PNG, or JPG) .
50+ Export {
4951 /// Path to the .graphite document
5052 document : PathBuf ,
5153
52- /// Path to the .graphite document
54+ /// Output file path (extension determines format: .svg, .png, .jpg)
55+ #[ clap( long, short = 'o' ) ]
56+ output : PathBuf ,
57+
58+ /// Optional input image resource
59+ #[ clap( long) ]
5360 image : Option < PathBuf > ,
5461
55- /// Run the document in a loop. This is useful for spawning and maintaining a window
56- #[ clap( long, short = 'l' ) ]
57- run_loop : bool ,
62+ /// Scale factor for export (default: 1.0)
63+ #[ clap( long, default_value = "1.0" ) ]
64+ scale : f64 ,
65+
66+ /// Output width in pixels
67+ #[ clap( long) ]
68+ width : Option < u32 > ,
69+
70+ /// Output height in pixels
71+ #[ clap( long) ]
72+ height : Option < u32 > ,
73+
74+ /// Transparent background for PNG exports
75+ #[ clap( long) ]
76+ transparent : bool ,
5877 } ,
5978 ListNodeIdentifiers ,
6079}
@@ -76,7 +95,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
7695
7796 let document_path = match app. command {
7897 Command :: Compile { ref document, .. } => document,
79- Command :: Run { ref document, .. } => document,
98+ Command :: Export { ref document, .. } => document,
8099 Command :: ListNodeIdentifiers => {
81100 let mut ids: Vec < _ > = graphene_std:: registry:: NODE_METADATA . lock ( ) . unwrap ( ) . keys ( ) . cloned ( ) . collect ( ) ;
82101 ids. sort_by_key ( |x| x. name . clone ( ) ) ;
@@ -92,15 +111,24 @@ async fn main() -> Result<(), Box<dyn Error>> {
92111 log:: info!( "creating gpu context" , ) ;
93112 let mut application_io = block_on ( WasmApplicationIo :: new_offscreen ( ) ) ;
94113
95- if let Command :: Run { image : Some ( ref image_path) , .. } = app. command {
114+ if let Command :: Export { image : Some ( ref image_path) , .. } = app. command {
96115 application_io. resources . insert ( "null" . to_string ( ) , Arc :: from ( std:: fs:: read ( image_path) . expect ( "Failed to read image" ) ) ) ;
97116 }
98- let device = application_io. gpu_executor ( ) . unwrap ( ) . context . device . clone ( ) ;
117+
118+ // Convert application_io to Arc first
119+ let application_io_arc = Arc :: new ( application_io) ;
120+
121+ // Clone the application_io Arc before borrowing to extract executor
122+ let application_io_for_api = application_io_arc. clone ( ) ;
123+
124+ // Get reference to wgpu executor and clone device handle
125+ let wgpu_executor_ref = application_io_arc. gpu_executor ( ) . unwrap ( ) ;
126+ let device = wgpu_executor_ref. context . device . clone ( ) ;
99127
100128 let preferences = EditorPreferences { use_vello : true } ;
101129 let editor_api = Arc :: new ( WasmEditorApi {
102130 font_cache : FontCache :: default ( ) ,
103- application_io : Some ( application_io . into ( ) ) ,
131+ application_io : Some ( application_io_for_api ) ,
104132 node_graph_message_sender : Box :: new ( UpdateLogger { } ) ,
105133 editor_preferences : Box :: new ( preferences) ,
106134 } ) ;
@@ -113,24 +141,30 @@ async fn main() -> Result<(), Box<dyn Error>> {
113141 println ! ( "{proto_graph}" ) ;
114142 }
115143 }
116- Command :: Run { run_loop, .. } => {
144+ Command :: Export {
145+ output,
146+ scale,
147+ width,
148+ height,
149+ transparent,
150+ ..
151+ } => {
152+ // Spawn thread to poll GPU device
117153 std:: thread:: spawn ( move || {
118154 loop {
119155 std:: thread:: sleep ( std:: time:: Duration :: from_nanos ( 10 ) ) ;
120156 device. poll ( wgpu:: PollType :: Poll ) . unwrap ( ) ;
121157 }
122158 } ) ;
159+
160+ // Detect output file type
161+ let file_type = export:: detect_file_type ( & output) ?;
162+
163+ // Create executor
123164 let executor = create_executor ( proto_graph) ?;
124- let render_config = RenderConfig :: default ( ) ;
125165
126- loop {
127- let result = ( & executor) . execute ( render_config) . await ?;
128- if !run_loop {
129- println ! ( "{result:?}" ) ;
130- break ;
131- }
132- tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 16 ) ) . await ;
133- }
166+ // Perform export
167+ export:: export_document ( & executor, wgpu_executor_ref, output, file_type, scale, width, height, transparent) . await ?;
134168 }
135169 _ => unreachable ! ( "All other commands should be handled before this match statement is run" ) ,
136170 }
0 commit comments