@@ -622,15 +622,18 @@ def component(self, value):
622622 self .uniform .update_buffer ()
623623
624624
625- def _complex_phase_export_interactions (uniforms_with_mode , registry , label = "Complex Animation " ):
626- """Build a generic gui ExportInteraction for complex phase animation .
625+ def _complex_phase_export_interactions (uniforms_with_mode , registry , label = "Complex" ):
626+ """Build a generic gui ExportInteraction for complex visualization .
627627
628628 uniforms_with_mode: iterable of (uniform_obj, has_mode_field).
629- has_mode_field=True for ComplexUniform (mode at offset 0, phase at offset 4),
630- False for ShapeComplexUniform (phase at offset 0, no mode).
629+ has_mode_field=True for ComplexUniform (mode @ offset 0, phase @ 4),
630+ False for ShapeComplexUniform (phase @ 0, no mode).
631+
632+ Emits: Mode dropdown (if any mode-carrying uniform), Phase slider,
633+ Animate checkbox, Speed slider.
631634 """
632635 from webgpu .export .gui import (
633- gui_interaction , Checkbox , Slider , Write , Target ,
636+ gui_interaction , Checkbox , Slider , Dropdown , Write , Target ,
634637 )
635638
636639 phase_targets = []
@@ -651,28 +654,99 @@ def _complex_phase_export_interactions(uniforms_with_mode, registry, label="Comp
651654 if not phase_targets :
652655 return []
653656
654- controls = [
655- Checkbox (var = "animate" , name = "Animate" , default = False ),
656- Slider (var = "speed" , name = "Speed" , default = 1.0 , min = 0.1 , max = 5.0 , step = 0.1 ),
657- ]
658- writes = [
659- Write (
660- targets = phase_targets ,
661- expr = "(t*speed*2*Math.PI) % (2*Math.PI)" ,
662- when = "animate" ,
663- ),
664- ]
657+ controls = []
658+ writes = []
665659 if mode_targets :
666- # Force PHASE_ROTATE mode whenever animation is toggled on.
660+ # 0=Real (PHASE_ROTATE,phase=0), 1=Imag (PHASE_ROTATE,phase=-pi/2),
661+ # 2=Abs (mode=ABS), 3=Arg (mode=ARG).
662+ controls .append (Dropdown (
663+ var = "cmode" , name = "Mode" ,
664+ options = {"Real" : 0 , "Imag" : 1 , "Abs" : 2 , "Arg" : 3 },
665+ default = 0 ,
666+ ))
667+ # Map cmode -> shader mode: 0,1 -> 0; 2 -> 1; 3 -> 2.
667668 writes .append (Write (
668669 targets = mode_targets ,
669- value = 0 ,
670- when = "animate" ,
671- trigger = "animate" ,
672- ))
670+ expr = "cmode <= 1 ? 0 : (cmode - 1)" ,
671+ trigger = "cmode" ,
672+ ))
673+ # Real/Imag set fixed phase (only when not animating).
674+ writes .append (Write (
675+ targets = phase_targets ,
676+ expr = "cmode == 1 ? -Math.PI/2 : 0" ,
677+ when = "cmode <= 1 && !animate" ,
678+ trigger = "cmode" ,
679+ ))
680+
681+ controls .append (Slider (var = "phase" , name = "Phase" ,
682+ default = 0.0 , min = 0.0 , max = 6.283 , step = 0.01 ))
683+ writes .append (Write (
684+ targets = phase_targets , expr = "phase" ,
685+ when = "!animate" , trigger = "phase" ,
686+ ))
687+
688+ controls .append (Checkbox (var = "animate" , name = "Animate" , default = False ))
689+ controls .append (Slider (var = "speed" , name = "Speed" ,
690+ default = 1.0 , min = 0.1 , max = 5.0 , step = 0.1 ))
691+ writes .append (Write (
692+ targets = phase_targets ,
693+ expr = "(t*speed*2*Math.PI) % (2*Math.PI)" ,
694+ when = "animate" ,
695+ ))
696+ if mode_targets :
697+ # Force PHASE_ROTATE mode while animating.
698+ writes .append (Write (
699+ targets = mode_targets , value = 0 ,
700+ when = "animate" , trigger = "animate" ,
701+ ))
673702 return [gui_interaction (label , controls , writes )]
674703
675704
705+ def _component_export_interactions (uniform , dim , registry , label = "Function" ):
706+ """Dropdown to select the active component (i32 at offset 0)."""
707+ from webgpu .export .gui import gui_interaction , Dropdown , Write , Target
708+
709+ if uniform is None or dim <= 1 :
710+ return []
711+ buf = getattr (uniform , "_buffer" , None )
712+ if buf is None :
713+ return []
714+ key = id (buf )
715+ if key not in registry ._buffers :
716+ return []
717+ buf_id = registry ._buffers [key ][0 ]
718+ options = {"Norm" : - 1 }
719+ for d in range (dim ):
720+ options [str (d )] = d
721+ return [gui_interaction (
722+ label ,
723+ [Dropdown (var = "component" , name = "Component" , options = options , default = - 1 )],
724+ [Write (targets = [Target (buf_id , offset = 0 , dtype = "i32" )],
725+ expr = "component" , trigger = "component" )],
726+ )]
727+
728+
729+ def _shrink_export_interactions (uniform , registry , label = "3D Elements" , offset = 4 ):
730+ """Slider for the shrink field (f32) of a uniform buffer."""
731+ from webgpu .export .gui import gui_interaction , Slider , Write , Target
732+
733+ if uniform is None :
734+ return []
735+ buf = getattr (uniform , "_buffer" , None )
736+ if buf is None :
737+ return []
738+ key = id (buf )
739+ if key not in registry ._buffers :
740+ return []
741+ buf_id = registry ._buffers [key ][0 ]
742+ return [gui_interaction (
743+ label ,
744+ [Slider (var = "shrink" , name = "Shrink" , default = 1.0 , min = 0.0 , max = 1.0 , step = 0.01 )],
745+ [Write (targets = [Target (buf_id , offset = offset , dtype = "f32" )],
746+ expr = "shrink" , trigger = "shrink" )],
747+ )]
748+
749+
676750class ComplexUniform (UniformBase ):
677751 _binding = Binding .COMPLEX_SETTINGS
678752 _fields_ = [
@@ -818,7 +892,10 @@ def on_component_change(self, callback):
818892 self ._on_component_change .append (callback )
819893
820894 def get_bounding_box (self ):
821- return self .data .get_bounding_box ()
895+ bbox = self .data .get_bounding_box ()
896+ if self .symmetry :
897+ bbox = self .symmetry .expand_bbox (bbox )
898+ return bbox
822899
823900 def add_options_to_gui (self , gui ):
824901 if gui is None :
@@ -914,6 +991,9 @@ def get_bindings(self):
914991
915992 def get_export_interactions (self , options , buffer_registry ):
916993 out = list (super ().get_export_interactions (options , buffer_registry ))
994+ out += _component_export_interactions (
995+ self .gpu_objects .settings .uniform , self .data .cf .dim , buffer_registry ,
996+ )
917997 if self .data .cf .is_complex :
918998 out += _complex_phase_export_interactions (
919999 [(self .gpu_objects .complex_settings .uniform , True )],
0 commit comments