euler314 commited on
Commit
4454015
·
verified ·
1 Parent(s): 903f458

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +327 -28
app.py CHANGED
@@ -244,6 +244,10 @@ light.data.energy = 2.0
244
  bpy.context.scene.render.engine = 'CYCLES'
245
  bpy.context.scene.cycles.device = 'CPU'
246
  bpy.context.scene.render.film_transparent = True
 
 
 
 
247
  """
248
 
249
  # Sidebar: Blender script examples
@@ -341,11 +345,10 @@ sun_light.data.energy = 5.0
341
  bpy.context.scene.render.engine = 'CYCLES'
342
  bpy.context.scene.cycles.device = 'CPU'
343
 
344
- print('Solar system animation setup complete.')"""
345
- }
346
 
347
- # Add a simple script example that's lightweight for testing
348
- script_examples["Simple Cube (Test)"] = """import bpy
349
 
350
  # Clear the scene
351
  bpy.ops.object.select_all(action='SELECT')
@@ -370,6 +373,169 @@ bpy.context.scene.camera = camera
370
 
371
  # Setup lighting
372
  bpy.ops.object.light_add(type='SUN', location=(5, -5, 5))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  """
374
 
375
  # Add example selector
@@ -607,11 +773,11 @@ print(f"Save operation completed, checking if file exists: {{os.path.exists(blen
607
  logger.error(f"Alternative approach error: {str(e)}")
608
  st.stop()
609
 
610
- # 6) Export GLB with animation - enhanced error handling
611
  glb_path = os.path.join(tmp_dir, "animation.glb")
612
 
613
- with st.status("Exporting to GLB format...") as status:
614
- # Create a separate Python script for GLB export to avoid command line issues
615
  export_script = os.path.join(tmp_dir, "export_glb.py")
616
  with open(export_script, "w") as f:
617
  f.write(f"""
@@ -631,7 +797,14 @@ try:
631
  bpy.ops.wm.open_mainfile(filepath=r'{blend_path}')
632
  print("Blend file loaded successfully")
633
 
634
- # Export to GLB
 
 
 
 
 
 
 
635
  glb_path = r'{glb_path}'
636
  print(f"Exporting to GLB: {{glb_path}}")
637
 
@@ -640,7 +813,12 @@ try:
640
  bpy.ops.export_scene.gltf(
641
  filepath=glb_path,
642
  export_format='GLB',
643
- export_animations=True
 
 
 
 
 
644
  )
645
  print(f"GLB export completed, file exists: {{os.path.exists(glb_path)}}")
646
  else:
@@ -654,7 +832,12 @@ try:
654
  bpy.ops.export_scene.gltf(
655
  filepath=glb_path,
656
  export_format='GLB',
657
- export_animations=True
 
 
 
 
 
658
  )
659
  print(f"GLB export completed after enabling add-on")
660
  else:
@@ -678,7 +861,7 @@ except Exception as e:
678
  text=True,
679
  timeout=180 # 3 minute timeout
680
  )
681
- status.update(label="GLB export completed successfully", state="complete")
682
  st.text_area("Export output", r2.stdout + r2.stderr, height=100)
683
  logger.info("GLB export completed successfully")
684
  except Exception as e:
@@ -686,13 +869,13 @@ except Exception as e:
686
  logger.error(f"GLB export error: {str(e)}")
687
  st.stop()
688
 
689
- # 7) Embed GLB inline if it exists
690
  if os.path.exists(glb_path):
691
  with open(glb_path, 'rb') as f:
692
  data = f.read()
693
 
694
  file_size = len(data) / (1024 * 1024) # Size in MB
695
- st.success(f"Successfully created GLB file ({file_size:.1f} MB)")
696
  logger.info(f"Created GLB file, size: {file_size:.1f} MB")
697
 
698
  # Check if file isn't too large for embedding
@@ -702,25 +885,110 @@ except Exception as e:
702
 
703
  b64 = base64.b64encode(data).decode()
704
 
705
- # Display the 3D model viewer
706
- st.subheader("3D Model Viewer")
707
  html = f"""
708
- <script type="module" src="https://unpkg.com/@google/model-viewer@latest/dist/model-viewer.min.js"></script>
709
- <model-viewer
710
- src="data:model/gltf-binary;base64,{b64}"
711
- alt="3D Model"
712
- auto-rotate
713
- camera-controls
714
- ar
715
- shadow-intensity="1"
716
- style="width:100%; height:600px; background-color: #f0f0f0;">
717
- </model-viewer>
718
- """
719
- st.components.v1.html(html, height=650)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
720
 
721
  # Add download button
722
  st.download_button(
723
- "⬇️ Download GLB File",
724
  data,
725
  file_name="animation.glb",
726
  mime="model/gltf-binary"
@@ -797,6 +1065,37 @@ with st.expander("Script Tips"):
797
  - Try the "Simple Cube (Test)" example first to verify Blender is working
798
  """)
799
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
800
  # Add a section at the bottom for Hugging Face Spaces setup
801
  with st.expander("Hugging Face Spaces Setup"):
802
  st.markdown("""
 
244
  bpy.context.scene.render.engine = 'CYCLES'
245
  bpy.context.scene.cycles.device = 'CPU'
246
  bpy.context.scene.render.film_transparent = True
247
+
248
+ # Set frame range for animation
249
+ bpy.context.scene.frame_start = 1
250
+ bpy.context.scene.frame_end = 250
251
  """
252
 
253
  # Sidebar: Blender script examples
 
345
  bpy.context.scene.render.engine = 'CYCLES'
346
  bpy.context.scene.cycles.device = 'CPU'
347
 
348
+ print('Solar system animation setup complete.')""",
 
349
 
350
+ "Simple Cube (Test)": """import bpy
351
+ import math
352
 
353
  # Clear the scene
354
  bpy.ops.object.select_all(action='SELECT')
 
373
 
374
  # Setup lighting
375
  bpy.ops.object.light_add(type='SUN', location=(5, -5, 5))
376
+
377
+ # Set frame range
378
+ bpy.context.scene.frame_start = 1
379
+ bpy.context.scene.frame_end = 100
380
+ """
381
+ }
382
+
383
+ # Add a more animation-focused example
384
+ script_examples["Animated Character"] = """import bpy
385
+ import math
386
+
387
+ # Clear existing objects
388
+ bpy.ops.object.select_all(action='SELECT')
389
+ bpy.ops.object.delete(use_global=False)
390
+
391
+ # Create simple character with basic armature
392
+ def create_simple_character():
393
+ # Create a body (cylinder)
394
+ bpy.ops.mesh.primitive_cylinder_add(radius=0.5, depth=2, location=(0, 0, 1))
395
+ body = bpy.context.active_object
396
+ body.name = "Body"
397
+
398
+ # Create a head (sphere)
399
+ bpy.ops.mesh.primitive_uv_sphere_add(radius=0.5, location=(0, 0, 2.5))
400
+ head = bpy.context.active_object
401
+ head.name = "Head"
402
+
403
+ # Create arms (cylinders)
404
+ bpy.ops.mesh.primitive_cylinder_add(radius=0.2, depth=1.5, location=(0.8, 0, 1.5))
405
+ arm_right = bpy.context.active_object
406
+ arm_right.name = "ArmRight"
407
+ arm_right.rotation_euler = (0, math.radians(90), 0)
408
+
409
+ bpy.ops.mesh.primitive_cylinder_add(radius=0.2, depth=1.5, location=(-0.8, 0, 1.5))
410
+ arm_left = bpy.context.active_object
411
+ arm_left.name = "ArmLeft"
412
+ arm_left.rotation_euler = (0, math.radians(90), 0)
413
+
414
+ # Create legs (cylinders)
415
+ bpy.ops.mesh.primitive_cylinder_add(radius=0.25, depth=1, location=(0.3, 0, 0))
416
+ leg_right = bpy.context.active_object
417
+ leg_right.name = "LegRight"
418
+
419
+ bpy.ops.mesh.primitive_cylinder_add(radius=0.25, depth=1, location=(-0.3, 0, 0))
420
+ leg_left = bpy.context.active_object
421
+ leg_left.name = "LegLeft"
422
+
423
+ return {
424
+ "body": body,
425
+ "head": head,
426
+ "arm_right": arm_right,
427
+ "arm_left": arm_left,
428
+ "leg_right": leg_right,
429
+ "leg_left": leg_left
430
+ }
431
+
432
+ # Create character
433
+ character = create_simple_character()
434
+
435
+ # Create dance animation
436
+ def create_dance_animation():
437
+ # Set frame range
438
+ bpy.context.scene.frame_start = 1
439
+ bpy.context.scene.frame_end = 60
440
+
441
+ # Arm waving
442
+ # Frame 1
443
+ bpy.context.scene.frame_set(1)
444
+ character["arm_right"].rotation_euler = (0, math.radians(90), 0)
445
+ character["arm_left"].rotation_euler = (0, math.radians(90), 0)
446
+ character["arm_right"].keyframe_insert(data_path="rotation_euler")
447
+ character["arm_left"].keyframe_insert(data_path="rotation_euler")
448
+
449
+ # Frame 15
450
+ bpy.context.scene.frame_set(15)
451
+ character["arm_right"].rotation_euler = (0, math.radians(90), math.radians(45))
452
+ character["arm_left"].rotation_euler = (0, math.radians(90), math.radians(-45))
453
+ character["arm_right"].keyframe_insert(data_path="rotation_euler")
454
+ character["arm_left"].keyframe_insert(data_path="rotation_euler")
455
+
456
+ # Frame 30
457
+ bpy.context.scene.frame_set(30)
458
+ character["arm_right"].rotation_euler = (0, math.radians(90), math.radians(-45))
459
+ character["arm_left"].rotation_euler = (0, math.radians(90), math.radians(45))
460
+ character["arm_right"].keyframe_insert(data_path="rotation_euler")
461
+ character["arm_left"].keyframe_insert(data_path="rotation_euler")
462
+
463
+ # Frame 45
464
+ bpy.context.scene.frame_set(45)
465
+ character["arm_right"].rotation_euler = (0, math.radians(90), math.radians(45))
466
+ character["arm_left"].rotation_euler = (0, math.radians(90), math.radians(-45))
467
+ character["arm_right"].keyframe_insert(data_path="rotation_euler")
468
+ character["arm_left"].keyframe_insert(data_path="rotation_euler")
469
+
470
+ # Frame 60
471
+ bpy.context.scene.frame_set(60)
472
+ character["arm_right"].rotation_euler = (0, math.radians(90), 0)
473
+ character["arm_left"].rotation_euler = (0, math.radians(90), 0)
474
+ character["arm_right"].keyframe_insert(data_path="rotation_euler")
475
+ character["arm_left"].keyframe_insert(data_path="rotation_euler")
476
+
477
+ # Body bounce
478
+ # Frame 1
479
+ bpy.context.scene.frame_set(1)
480
+ character["body"].location = (0, 0, 1)
481
+ character["body"].keyframe_insert(data_path="location")
482
+ character["head"].location = (0, 0, 2.5)
483
+ character["head"].keyframe_insert(data_path="location")
484
+
485
+ # Frame 15
486
+ bpy.context.scene.frame_set(15)
487
+ character["body"].location = (0, 0, 1.2)
488
+ character["body"].keyframe_insert(data_path="location")
489
+ character["head"].location = (0, 0, 2.7)
490
+ character["head"].keyframe_insert(data_path="location")
491
+
492
+ # Frame 30
493
+ bpy.context.scene.frame_set(30)
494
+ character["body"].location = (0, 0, 1)
495
+ character["body"].keyframe_insert(data_path="location")
496
+ character["head"].location = (0, 0, 2.5)
497
+ character["head"].keyframe_insert(data_path="location")
498
+
499
+ # Frame 45
500
+ bpy.context.scene.frame_set(45)
501
+ character["body"].location = (0, 0, 1.2)
502
+ character["body"].keyframe_insert(data_path="location")
503
+ character["head"].location = (0, 0, 2.7)
504
+ character["head"].keyframe_insert(data_path="location")
505
+
506
+ # Frame 60
507
+ bpy.context.scene.frame_set(60)
508
+ character["body"].location = (0, 0, 1)
509
+ character["body"].keyframe_insert(data_path="location")
510
+ character["head"].location = (0, 0, 2.5)
511
+ character["head"].keyframe_insert(data_path="location")
512
+
513
+ # Set interpolation
514
+ for obj in character.values():
515
+ if obj.animation_data and obj.animation_data.action:
516
+ for fc in obj.animation_data.action.fcurves:
517
+ for kp in fc.keyframe_points:
518
+ kp.interpolation = 'BEZIER'
519
+
520
+ # Create the animation
521
+ create_dance_animation()
522
+
523
+ # Set up camera and lighting
524
+ bpy.ops.object.camera_add(location=(0, -5, 2))
525
+ camera = bpy.context.active_object
526
+ camera.rotation_euler = (math.radians(80), 0, 0)
527
+ bpy.context.scene.camera = camera
528
+
529
+ # Add light
530
+ bpy.ops.object.light_add(type='SUN', location=(2, -3, 5))
531
+ sun = bpy.context.active_object
532
+ sun.data.energy = 3.0
533
+
534
+ # Set render settings
535
+ bpy.context.scene.render.engine = 'CYCLES'
536
+ bpy.context.scene.cycles.device = 'CPU'
537
+
538
+ print('Character animation setup complete')
539
  """
540
 
541
  # Add example selector
 
773
  logger.error(f"Alternative approach error: {str(e)}")
774
  st.stop()
775
 
776
+ # 6) Export GLB with animation - enhanced error handling and animation support
777
  glb_path = os.path.join(tmp_dir, "animation.glb")
778
 
779
+ with st.status("Exporting to GLB format with animations...") as status:
780
+ # Create a separate Python script for enhanced GLB export with better animation support
781
  export_script = os.path.join(tmp_dir, "export_glb.py")
782
  with open(export_script, "w") as f:
783
  f.write(f"""
 
797
  bpy.ops.wm.open_mainfile(filepath=r'{blend_path}')
798
  print("Blend file loaded successfully")
799
 
800
+ # Get animation info
801
+ scene = bpy.context.scene
802
+ frame_start = scene.frame_start
803
+ frame_end = scene.frame_end
804
+ fps = scene.render.fps
805
+ print(f"Animation frames: {{frame_start}}-{{frame_end}} at {{fps}} fps")
806
+
807
+ # Export to GLB with enhanced animation options
808
  glb_path = r'{glb_path}'
809
  print(f"Exporting to GLB: {{glb_path}}")
810
 
 
813
  bpy.ops.export_scene.gltf(
814
  filepath=glb_path,
815
  export_format='GLB',
816
+ export_animations=True,
817
+ export_frame_range=True,
818
+ export_frame_step=1,
819
+ export_anim_single_armature=False, # Export all animations
820
+ export_current_frame=False,
821
+ export_apply=False # Keep animations intact
822
  )
823
  print(f"GLB export completed, file exists: {{os.path.exists(glb_path)}}")
824
  else:
 
832
  bpy.ops.export_scene.gltf(
833
  filepath=glb_path,
834
  export_format='GLB',
835
+ export_animations=True,
836
+ export_frame_range=True,
837
+ export_frame_step=1,
838
+ export_anim_single_armature=False,
839
+ export_current_frame=False,
840
+ export_apply=False
841
  )
842
  print(f"GLB export completed after enabling add-on")
843
  else:
 
861
  text=True,
862
  timeout=180 # 3 minute timeout
863
  )
864
+ status.update(label="GLB export with animations completed successfully", state="complete")
865
  st.text_area("Export output", r2.stdout + r2.stderr, height=100)
866
  logger.info("GLB export completed successfully")
867
  except Exception as e:
 
869
  logger.error(f"GLB export error: {str(e)}")
870
  st.stop()
871
 
872
+ # 7) Embed GLB inline with enhanced animation viewer
873
  if os.path.exists(glb_path):
874
  with open(glb_path, 'rb') as f:
875
  data = f.read()
876
 
877
  file_size = len(data) / (1024 * 1024) # Size in MB
878
+ st.success(f"Successfully created GLB file with animations ({file_size:.1f} MB)")
879
  logger.info(f"Created GLB file, size: {file_size:.1f} MB")
880
 
881
  # Check if file isn't too large for embedding
 
885
 
886
  b64 = base64.b64encode(data).decode()
887
 
888
+ # Display the 3D model viewer with animation controls
889
+ st.subheader("3D Model Viewer with Animation Controls")
890
  html = f"""
891
+ <script type="module" src="https://unpkg.com/@google/model-viewer@latest/dist/model-viewer.min.js"></script>
892
+ <style>
893
+ .animation-controls {{
894
+ display: flex;
895
+ justify-content: center;
896
+ margin-top: 10px;
897
+ gap: 8px;
898
+ }}
899
+ .animation-btn {{
900
+ background-color: #4CAF50;
901
+ border: none;
902
+ color: white;
903
+ padding: 8px 16px;
904
+ text-align: center;
905
+ text-decoration: none;
906
+ display: inline-block;
907
+ font-size: 14px;
908
+ margin: 4px 2px;
909
+ cursor: pointer;
910
+ border-radius: 4px;
911
+ }}
912
+ #animation-name {{
913
+ font-weight: bold;
914
+ margin-right: 10px;
915
+ }}
916
+ </style>
917
+
918
+ <model-viewer id="model-viewer"
919
+ src="data:model/gltf-binary;base64,{b64}"
920
+ alt="3D Model with Animation"
921
+ camera-controls
922
+ auto-rotate
923
+ ar
924
+ shadow-intensity="1"
925
+ animation-name=""
926
+ autoplay
927
+ style="width:100%; height:500px; background-color: #f0f0f0;">
928
+ <div class="progress-bar hide" slot="progress-bar">
929
+ <div class="update-bar"></div>
930
+ </div>
931
+ </model-viewer>
932
+
933
+ <div class="animation-controls">
934
+ <span id="animation-name">No animation playing</span>
935
+ <button id="play-btn" class="animation-btn">Play</button>
936
+ <button id="pause-btn" class="animation-btn">Pause</button>
937
+ <button id="stop-btn" class="animation-btn">Stop</button>
938
+ </div>
939
+
940
+ <script>
941
+ const modelViewer = document.getElementById('model-viewer');
942
+ const playBtn = document.getElementById('play-btn');
943
+ const pauseBtn = document.getElementById('pause-btn');
944
+ const stopBtn = document.getElementById('stop-btn');
945
+ const animNameDisplay = document.getElementById('animation-name');
946
+
947
+ // Wait for the model to load
948
+ modelViewer.addEventListener('load', () => {{
949
+ // Get available animations
950
+ const animationNames = modelViewer.availableAnimations;
951
+ console.log('Available animations:', animationNames);
952
+
953
+ if (animationNames && animationNames.length > 0) {{
954
+ // Set first animation as default
955
+ modelViewer.animationName = animationNames[0];
956
+ animNameDisplay.textContent = 'Playing: ' + animationNames[0];
957
+ modelViewer.play();
958
+ }} else {{
959
+ animNameDisplay.textContent = 'No animations available';
960
+ }}
961
+ }});
962
+
963
+ // Animation controls
964
+ playBtn.addEventListener('click', () => {{
965
+ modelViewer.play();
966
+ if (modelViewer.animationName) {{
967
+ animNameDisplay.textContent = 'Playing: ' + modelViewer.animationName;
968
+ }}
969
+ }});
970
+
971
+ pauseBtn.addEventListener('click', () => {{
972
+ modelViewer.pause();
973
+ if (modelViewer.animationName) {{
974
+ animNameDisplay.textContent = 'Paused: ' + modelViewer.animationName;
975
+ }}
976
+ }});
977
+
978
+ stopBtn.addEventListener('click', () => {{
979
+ modelViewer.pause();
980
+ modelViewer.currentTime = 0;
981
+ if (modelViewer.animationName) {{
982
+ animNameDisplay.textContent = 'Stopped: ' + modelViewer.animationName;
983
+ }}
984
+ }});
985
+ </script>
986
+ """
987
+ st.components.v1.html(html, height=600)
988
 
989
  # Add download button
990
  st.download_button(
991
+ "⬇️ Download GLB File with Animation",
992
  data,
993
  file_name="animation.glb",
994
  mime="model/gltf-binary"
 
1065
  - Try the "Simple Cube (Test)" example first to verify Blender is working
1066
  """)
1067
 
1068
+ with st.expander("Animation Tips"):
1069
+ st.markdown("""
1070
+ ### Tips for creating animations that show properly in GLB
1071
+
1072
+ 1. **Set frame range properly**: Always define your animation frame range
1073
+ ```python
1074
+ bpy.context.scene.frame_start = 1
1075
+ bpy.context.scene.frame_end = 60 # End frame of your animation
1076
+ ```
1077
+
1078
+ 2. **Use keyframes properly**: Add keyframes at specific frames
1079
+ ```python
1080
+ # Set current frame
1081
+ bpy.context.scene.frame_set(1)
1082
+
1083
+ # Set object properties
1084
+ obj.location = (0, 0, 0)
1085
+
1086
+ # Insert keyframe for that property
1087
+ obj.keyframe_insert(data_path="location")
1088
+ ```
1089
+
1090
+ 3. **Animation export settings**: The app automatically handles GLB export with proper animation settings
1091
+
1092
+ 4. **Animation playback**: Use the animation controls below the 3D viewer to play/pause/stop your animation
1093
+
1094
+ 5. **Multiple animations**: If your GLB has multiple animations, they will be detected and can be selected in the viewer
1095
+
1096
+ 6. **Looping animations**: By default, animations will loop in the viewer
1097
+ """)
1098
+
1099
  # Add a section at the bottom for Hugging Face Spaces setup
1100
  with st.expander("Hugging Face Spaces Setup"):
1101
  st.markdown("""