11import os
22import random
3- from mathutils import Vector
43import bpy
4+ from mathutils import Vector
55from scene_utils import ensure_collection , make_material , add_sphere , add_cylinder_edge , add_camera_and_light
66
7+
78def build_crdt_convergence (scene , anim_dir : str ):
9+
810 random .seed (3 )
11+
912 col = ensure_collection ("CRDT" )
1013
1114 add_camera_and_light (camera_loc = (12 , - 12 , 8 ), look_at = (6 , 0 , 1 ))
@@ -15,70 +18,129 @@ def build_crdt_convergence(scene, anim_dir: str):
1518 make_material ("rep1" , rgba = (0.7 , 1.0 , 0.7 , 1.0 ), emission = 0.25 ),
1619 make_material ("rep2" , rgba = (0.7 , 0.8 , 1.0 , 1.0 ), emission = 0.25 ),
1720 ]
21+
1822 mat_edge = make_material ("merge_edge" , rgba = (0.2 , 0.2 , 0.25 , 1.0 ), emission = 0.0 )
1923
2024 replicas = 3
2125 ops_total = 18
2226 steps = 90
2327
24- ops = list (range (ops_total ))
25- random .shuffle (ops )
28+ ops_pool = list (range (ops_total ))
29+ random .shuffle (ops_pool )
2630
2731 op_sets = [set () for _ in range (replicas )]
32+
2833 for r in range (replicas ):
2934 for _ in range (4 ):
30- op_sets [r ].add (ops .pop ())
35+ op_sets [r ].add (ops_pool .pop ())
36+
37+ op_objs = [[None ] * ops_total for _ in range (replicas )]
3138
32- op_objs = [[ None ] * ops_total for _ in range ( replicas )]
39+ def op_position ( replica , op_id ):
3340
34- def op_position (r , op_id ):
35- x = r * 3.0
36- y = (op_id - (ops_total - 1 )/ 2 ) * 0.22
41+ x = replica * 3.0
42+ y = (op_id - (ops_total - 1 ) / 2 ) * 0.22
3743 z = 0.0
44+
3845 return Vector ((x , y , z ))
3946
40- for r in range (replicas ):
41- for op_id in range (ops_total ):
42- p = op_position (r , op_id )
43- obj = add_sphere (p , radius = 0.11 , name = f"op_{ r } _{ op_id } " , material = mat_ops [r ], collection = col )
44- obj .hide_render = True
45- obj .hide_viewport = True
46- op_objs [r ][op_id ] = obj
47-
48- def reveal_ops (frame ):
47+ def create_operation_objects ():
48+
4949 for r in range (replicas ):
50+ for op_id in range (ops_total ):
51+
52+ pos = op_position (r , op_id )
53+
54+ obj = add_sphere (
55+ pos ,
56+ radius = 0.11 ,
57+ name = f"op_{ r } _{ op_id } " ,
58+ material = mat_ops [r ],
59+ collection = col ,
60+ )
61+
62+ obj .hide_render = True
63+ obj .hide_viewport = True
64+
65+ op_objs [r ][op_id ] = obj
66+
67+ def reveal_operations (frame ):
68+
69+ for r in range (replicas ):
70+
5071 for op_id in op_sets [r ]:
72+
5173 obj = op_objs [r ][op_id ]
74+
5275 obj .hide_render = False
5376 obj .hide_viewport = False
54- obj .keyframe_insert (data_path = "hide_render" , frame = frame )
55- obj .keyframe_insert (data_path = "hide_viewport" , frame = frame )
77+
78+ obj .keyframe_insert ("hide_render" , frame = frame )
79+ obj .keyframe_insert ("hide_viewport" , frame = frame )
80+
81+ def merge (a , b ):
82+
83+ before = set (op_sets [b ])
84+
85+ op_sets [b ] |= op_sets [a ]
86+
87+ return before != op_sets [b ]
88+
89+ def inject_operation ():
90+
91+ if not ops_pool :
92+ return
93+
94+ r = random .randrange (replicas )
95+ op_sets [r ].add (ops_pool .pop ())
96+
97+ def converged ():
98+
99+ base = op_sets [0 ]
100+
101+ for r in range (1 , replicas ):
102+ if op_sets [r ] != base :
103+ return False
104+
105+ return not ops_pool
106+
107+ create_operation_objects ()
56108
57109 scene .frame_start = 1
58110 scene .frame_end = steps
59111
60- reveal_ops (1 )
112+ reveal_operations (1 )
113+
114+ for frame in range (2 , steps + 1 ):
61115
62- for f in range (2 , steps + 1 ):
63116 if random .random () < 0.55 :
117+
64118 a , b = random .sample (range (replicas ), 2 )
65- before = set (op_sets [b ])
66- op_sets [b ] |= op_sets [a ]
67- if before != op_sets [b ]:
68- add_cylinder_edge ((a * 3.0 , 0 , 0.4 ), (b * 3.0 , 0 , 0.4 ), radius = 0.03 , material = mat_edge , collection = col )
69119
70- if ops and random .random () < 0.25 :
71- r = random .randrange (replicas )
72- op_sets [r ].add (ops .pop ())
120+ if merge (a , b ):
121+
122+ add_cylinder_edge (
123+ (a * 3.0 , 0 , 0.4 ),
124+ (b * 3.0 , 0 , 0.4 ),
125+ radius = 0.03 ,
126+ material = mat_edge ,
127+ collection = col ,
128+ )
73129
74- reveal_ops (f )
130+ if random .random () < 0.25 :
131+ inject_operation ()
75132
76- if all (op_sets [0 ] == op_sets [r ] for r in range (1 , replicas )) and not ops :
77- scene .frame_end = f
133+ reveal_operations (frame )
134+
135+ if converged ():
136+ scene .frame_end = frame
78137 break
79- if anim_dir :
80- os .makedirs (anim_dir , exist_ok = True )
81-
82- scene .render .image_settings .file_format = "PNG"
83- scene .render .filepath = os .path .join (anim_dir , "frame_" )
84- bpy .ops .render .render (animation = True , write_still = True )
138+
139+ if anim_dir :
140+
141+ os .makedirs (anim_dir , exist_ok = True )
142+
143+ scene .render .image_settings .file_format = "PNG"
144+ scene .render .filepath = os .path .join (anim_dir , "frame_" )
145+
146+ bpy .ops .render .render (animation = True , write_still = True )
0 commit comments