Spaces:
Running
on
Zero
Running
on
Zero
Commit
·
2c24bbf
1
Parent(s):
643e04c
limiting gpu usage by moving autoforge into its own function
Browse files
app.py
CHANGED
@@ -310,6 +310,28 @@ if os.path.exists(DEFAULT_MATERIALS_CSV):
|
|
310 |
else:
|
311 |
initial_df.to_csv(DEFAULT_MATERIALS_CSV, index=False)
|
312 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
313 |
|
314 |
# Helper for creating an empty 10-tuple for error returns
|
315 |
def create_empty_error_outputs(log_message=""):
|
@@ -625,14 +647,14 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
625 |
)
|
626 |
|
627 |
with gr.Row():
|
628 |
-
|
629 |
-
label="Download
|
|
|
630 |
interactive=True,
|
631 |
visible=False,
|
632 |
)
|
633 |
|
634 |
# --- Backend Function for Running the Script ---
|
635 |
-
@spaces.GPU(duration=120)
|
636 |
def execute_autoforge_script(
|
637 |
current_filaments_df_state_val, input_image, *accordion_param_values
|
638 |
):
|
@@ -737,40 +759,6 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
737 |
"extra": {"command": cmd_str}, # still searchable
|
738 |
}
|
739 |
)
|
740 |
-
process = subprocess.Popen(
|
741 |
-
command,
|
742 |
-
stdout=subprocess.PIPE,
|
743 |
-
stderr=subprocess.PIPE,
|
744 |
-
text=True,
|
745 |
-
bufsize=1,
|
746 |
-
universal_newlines=True,
|
747 |
-
)
|
748 |
-
|
749 |
-
# ---- helper: read stdout in a background thread -------------------
|
750 |
-
from threading import Thread
|
751 |
-
from queue import Queue, Empty
|
752 |
-
|
753 |
-
def _enqueue(pipe, q):
|
754 |
-
"""Forward stdout/stderr to a queue, emitting on both '\n' and '\r'."""
|
755 |
-
buf = ""
|
756 |
-
while True:
|
757 |
-
ch = pipe.read(1) # read a single character
|
758 |
-
if ch == "": # EOF
|
759 |
-
if buf:
|
760 |
-
q.put(buf) # flush whatever is left
|
761 |
-
break
|
762 |
-
buf += ch
|
763 |
-
if ch in ("\n", "\r"): # tqdm uses '\r'
|
764 |
-
q.put(buf)
|
765 |
-
buf = ""
|
766 |
-
pipe.close()
|
767 |
-
|
768 |
-
q_out = Queue()
|
769 |
-
Thread(target=_enqueue, args=(process.stdout, q_out), daemon=True).start()
|
770 |
-
Thread(target=_enqueue, args=(process.stderr, q_out), daemon=True).start()
|
771 |
-
|
772 |
-
preview_mtime = 0
|
773 |
-
last_push = 0
|
774 |
|
775 |
def _maybe_new_preview():
|
776 |
"""
|
@@ -792,28 +780,45 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
792 |
|
793 |
return src # → refresh image
|
794 |
|
795 |
-
# ----
|
796 |
-
|
797 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
798 |
try:
|
799 |
-
while True:
|
800 |
-
|
|
|
|
|
|
|
|
|
801 |
except Empty:
|
802 |
pass
|
803 |
|
804 |
now = time.time()
|
805 |
-
if now - last_push >= 1.0: #
|
806 |
current_preview = _maybe_new_preview()
|
807 |
yield (
|
808 |
log_output,
|
809 |
current_preview,
|
810 |
-
gr.update(), #
|
811 |
)
|
812 |
last_push = now
|
813 |
|
814 |
time.sleep(0.05) # keep CPU load low
|
815 |
|
816 |
-
return_code = process.wait()
|
817 |
if return_code != 0:
|
818 |
err = RuntimeError(f"Autoforge exited with code {return_code} \n {log_output}")
|
819 |
capture_exception(err) # send to Sentry
|
@@ -833,22 +838,17 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
833 |
}
|
834 |
)
|
835 |
|
836 |
-
|
837 |
-
|
838 |
-
|
839 |
-
|
840 |
-
|
841 |
-
|
842 |
-
|
843 |
-
|
844 |
-
|
845 |
-
|
846 |
-
# 4. Prepare output file paths
|
847 |
png_path = os.path.join(run_output_dir_val, "final_model.png")
|
848 |
-
stl_path = os.path.join(run_output_dir_val, "final_model.stl")
|
849 |
-
txt_path = os.path.join(run_output_dir_val, "swap_instructions.txt")
|
850 |
-
hfp_path = os.path.join(run_output_dir_val, "project_file.hfp")
|
851 |
-
|
852 |
out_png = png_path if os.path.exists(png_path) else None
|
853 |
|
854 |
if out_png is None:
|
@@ -856,10 +856,12 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
856 |
|
857 |
yield (
|
858 |
log_output, # progress_output
|
859 |
-
out_png, # final_image_preview
|
860 |
-
gr.update(
|
861 |
-
value=
|
862 |
-
|
|
|
|
|
863 |
)
|
864 |
|
865 |
run_inputs = [filament_df_state, input_image_component] + [
|
@@ -868,7 +870,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
|
868 |
run_outputs = [
|
869 |
progress_output,
|
870 |
final_image_preview,
|
871 |
-
|
872 |
]
|
873 |
|
874 |
run_button.click(execute_autoforge_script, inputs=run_inputs, outputs=run_outputs)
|
|
|
310 |
else:
|
311 |
initial_df.to_csv(DEFAULT_MATERIALS_CSV, index=False)
|
312 |
|
313 |
+
@spaces.GPU()
|
314 |
+
def run_autoforge_process(cmd, q):
|
315 |
+
"""
|
316 |
+
Start the Autoforge CLI, stream its stdout to Queue *q*,
|
317 |
+
then drop a sentinel ('__RC__', return_code) at the end.
|
318 |
+
"""
|
319 |
+
import subprocess, os, sys
|
320 |
+
|
321 |
+
proc = subprocess.Popen(
|
322 |
+
cmd,
|
323 |
+
stdout=subprocess.PIPE,
|
324 |
+
stderr=subprocess.STDOUT,
|
325 |
+
text=True,
|
326 |
+
bufsize=1,
|
327 |
+
universal_newlines=True,
|
328 |
+
)
|
329 |
+
|
330 |
+
for line in proc.stdout: # line-by-line streaming
|
331 |
+
q.put(line)
|
332 |
+
proc.wait()
|
333 |
+
q.put(("__RC__", proc.returncode))
|
334 |
+
|
335 |
|
336 |
# Helper for creating an empty 10-tuple for error returns
|
337 |
def create_empty_error_outputs(log_message=""):
|
|
|
647 |
)
|
648 |
|
649 |
with gr.Row():
|
650 |
+
download_results = gr.File(
|
651 |
+
label="Download results",
|
652 |
+
file_count="multiple",
|
653 |
interactive=True,
|
654 |
visible=False,
|
655 |
)
|
656 |
|
657 |
# --- Backend Function for Running the Script ---
|
|
|
658 |
def execute_autoforge_script(
|
659 |
current_filaments_df_state_val, input_image, *accordion_param_values
|
660 |
):
|
|
|
759 |
"extra": {"command": cmd_str}, # still searchable
|
760 |
}
|
761 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
762 |
|
763 |
def _maybe_new_preview():
|
764 |
"""
|
|
|
780 |
|
781 |
return src # → refresh image
|
782 |
|
783 |
+
# ---- run Autoforge on the GPU in a helper thread ------------------
|
784 |
+
from threading import Thread
|
785 |
+
from queue import Queue, Empty
|
786 |
+
|
787 |
+
q_out = Queue()
|
788 |
+
worker = Thread(
|
789 |
+
target=run_autoforge_process,
|
790 |
+
args=(command, q_out),
|
791 |
+
daemon=True,
|
792 |
+
)
|
793 |
+
worker.start()
|
794 |
+
|
795 |
+
preview_mtime = 0
|
796 |
+
last_push = 0
|
797 |
+
return_code = None
|
798 |
+
|
799 |
+
while worker.is_alive() or not q_out.empty():
|
800 |
try:
|
801 |
+
while True: # drain whatever is ready
|
802 |
+
msg = q_out.get_nowait()
|
803 |
+
if isinstance(msg, tuple): # ('__RC__', code) sentinel
|
804 |
+
return_code = msg[1]
|
805 |
+
else:
|
806 |
+
log_output += msg
|
807 |
except Empty:
|
808 |
pass
|
809 |
|
810 |
now = time.time()
|
811 |
+
if now - last_push >= 1.0: # 1-second UI tick
|
812 |
current_preview = _maybe_new_preview()
|
813 |
yield (
|
814 |
log_output,
|
815 |
current_preview,
|
816 |
+
gr.update(), # placeholder for download widget
|
817 |
)
|
818 |
last_push = now
|
819 |
|
820 |
time.sleep(0.05) # keep CPU load low
|
821 |
|
|
|
822 |
if return_code != 0:
|
823 |
err = RuntimeError(f"Autoforge exited with code {return_code} \n {log_output}")
|
824 |
capture_exception(err) # send to Sentry
|
|
|
838 |
}
|
839 |
)
|
840 |
|
841 |
+
files_to_offer = [
|
842 |
+
p
|
843 |
+
for p in [
|
844 |
+
os.path.join(run_output_dir_val, "final_model.png"),
|
845 |
+
os.path.join(run_output_dir_val, "final_model.stl"),
|
846 |
+
os.path.join(run_output_dir_val, "swap_instructions.txt"),
|
847 |
+
os.path.join(run_output_dir_val, "project_file.hfp"),
|
848 |
+
]
|
849 |
+
if os.path.exists(p)
|
850 |
+
]
|
|
|
851 |
png_path = os.path.join(run_output_dir_val, "final_model.png")
|
|
|
|
|
|
|
|
|
852 |
out_png = png_path if os.path.exists(png_path) else None
|
853 |
|
854 |
if out_png is None:
|
|
|
856 |
|
857 |
yield (
|
858 |
log_output, # progress_output
|
859 |
+
out_png, # final_image_preview (same as before)
|
860 |
+
gr.update( # download_results
|
861 |
+
value=files_to_offer,
|
862 |
+
visible=True,
|
863 |
+
interactive=True,
|
864 |
+
),
|
865 |
)
|
866 |
|
867 |
run_inputs = [filament_df_state, input_image_component] + [
|
|
|
870 |
run_outputs = [
|
871 |
progress_output,
|
872 |
final_image_preview,
|
873 |
+
download_results, # ### ZIP PATCH: only three outputs now
|
874 |
]
|
875 |
|
876 |
run_button.click(execute_autoforge_script, inputs=run_inputs, outputs=run_outputs)
|