|
| 1 | +""" |
| 2 | +A PyVista based viewer/player for saved Sibernetic simulations |
| 3 | +
|
| 4 | +Loads in the generated position_buffer.txt file |
| 5 | +
|
| 6 | +""" |
| 7 | + |
| 8 | +import pyvista as pv |
| 9 | +import sys |
| 10 | +import os |
| 11 | +import time |
| 12 | +import json |
| 13 | +import numpy as np |
| 14 | +import matplotlib.pyplot as plt |
| 15 | + |
| 16 | + |
| 17 | +last_meshes = {} |
| 18 | + |
| 19 | +replay_speed = 0.05 # seconds between frames |
| 20 | +replaying = False |
| 21 | + |
| 22 | +all_points = [] |
| 23 | +all_point_types = [] |
| 24 | + |
| 25 | + |
| 26 | +plotter = None |
| 27 | +offset3d_ = (0, 0, 0) |
| 28 | +slider = None |
| 29 | + |
| 30 | +show_boundary = False |
| 31 | + |
| 32 | + |
| 33 | +def get_color_info_for_type(type_): |
| 34 | + """ |
| 35 | + Get color, info string and point size for a given point type |
| 36 | + returns: color, info, size |
| 37 | + """ |
| 38 | + |
| 39 | + if type_ == 1.1: |
| 40 | + return "#8BEBFC", "liquid 1", 5 |
| 41 | + elif type_ == 1.2: |
| 42 | + return "#3ACFF0", "liquid 2", 5 |
| 43 | + elif type_ == 2.1: |
| 44 | + return "yellow", "elastic 1", 5 |
| 45 | + elif type_ == 2.2: |
| 46 | + return "#FF0000", "elastic 2", 5 |
| 47 | + elif type_ > 2 and type_ < 3: |
| 48 | + return "#00cc00", "elastic variable", 5 |
| 49 | + elif type_ == 3: |
| 50 | + return "grey", "boundary 0", 3 |
| 51 | + elif type_ == 3.1: |
| 52 | + return "black", "boundary 1", 7 |
| 53 | + else: |
| 54 | + return "orange", "unknown", 5 |
| 55 | + |
| 56 | + |
| 57 | +def add_sibernetic_model( |
| 58 | + pl, |
| 59 | + position_file="Sibernetic/position_buffer.txt", |
| 60 | + report_file=None, |
| 61 | + swap_y_z=False, |
| 62 | + offset3d=(0, 0, 0), |
| 63 | + include_boundary=False, |
| 64 | +): |
| 65 | + global \ |
| 66 | + all_points, \ |
| 67 | + all_point_types, \ |
| 68 | + last_meshes, \ |
| 69 | + plotter, \ |
| 70 | + offset3d_, \ |
| 71 | + slider, \ |
| 72 | + show_boundary |
| 73 | + |
| 74 | + offset3d_ = offset3d |
| 75 | + plotter = pl |
| 76 | + show_boundary = include_boundary |
| 77 | + |
| 78 | + points = {} |
| 79 | + types = [] |
| 80 | + |
| 81 | + line_count = 0 |
| 82 | + pcount = 0 |
| 83 | + time_count = 0 |
| 84 | + logStep = None |
| 85 | + |
| 86 | + report_data = None |
| 87 | + count_point_types = {} |
| 88 | + |
| 89 | + if report_file is not None: |
| 90 | + sim_dir = os.path.dirname(os.path.abspath(report_file)) |
| 91 | + report_data = json.load(open(report_file, "r")) |
| 92 | + print(report_data) |
| 93 | + position_file = os.path.join(sim_dir, "position_buffer.txt") |
| 94 | + |
| 95 | + if "worm" in report_data["configuration"]: |
| 96 | + muscle_activation_file = os.path.join( |
| 97 | + sim_dir, "muscles_activity_buffer.txt" |
| 98 | + ) |
| 99 | + print("Loading muscle activation file from: %s" % muscle_activation_file) |
| 100 | + musc_dat = np.loadtxt(muscle_activation_file, delimiter="\t").T |
| 101 | + print(musc_dat) |
| 102 | + print(musc_dat.shape) |
| 103 | + # plt.imshow(musc_dat, interpolation="none", aspect="auto", cmap="YlOrRd") |
| 104 | + |
| 105 | + f, ax = plt.subplots(tight_layout=True) |
| 106 | + ax.imshow(musc_dat, interpolation="none", aspect="auto", cmap="YlOrRd") |
| 107 | + # ax.set_ylim([-1, 1]) |
| 108 | + ax.set_xlabel("Time (s)") |
| 109 | + _ = ax.set_ylabel("Muscle") |
| 110 | + |
| 111 | + h_chart = pv.ChartMPL(f, size=(0.35, 0.35), loc=(0.02, 0.06)) |
| 112 | + h_chart.title = None |
| 113 | + h_chart.border_color = "white" |
| 114 | + h_chart.show_title = False |
| 115 | + h_chart.background_color = (1.0, 1.0, 1.0, 0.4) |
| 116 | + pl.add_chart( |
| 117 | + h_chart, |
| 118 | + ) |
| 119 | + |
| 120 | + first_pass_complete = False |
| 121 | + |
| 122 | + for line in open(position_file): |
| 123 | + ws = line.split() |
| 124 | + # print(ws) |
| 125 | + if line_count == 6: |
| 126 | + numOfElasticP = int(ws[0]) |
| 127 | + if line_count == 7: |
| 128 | + numOfLiquidP = int(ws[0]) |
| 129 | + if line_count == 8: |
| 130 | + numOfBoundaryP = int(ws[0]) |
| 131 | + if line_count == 9: |
| 132 | + timeStep = float(ws[0]) # noqa: F841 |
| 133 | + if line_count == 10: |
| 134 | + logStep = int(ws[0]) |
| 135 | + |
| 136 | + if len(ws) == 4: |
| 137 | + type_ = float(ws[3]) |
| 138 | + if type_ not in points: |
| 139 | + points[type_] = [] |
| 140 | + |
| 141 | + if not first_pass_complete: |
| 142 | + if type_ not in count_point_types: |
| 143 | + count_point_types[type_] = 0 |
| 144 | + count_point_types[type_] += 1 |
| 145 | + |
| 146 | + if swap_y_z: |
| 147 | + points[type_].append([float(ws[1]), 1 * float(ws[0]), float(ws[2])]) |
| 148 | + else: |
| 149 | + points[type_].append([float(ws[0]), float(ws[1]), float(ws[2])]) |
| 150 | + |
| 151 | + types.append(type_) |
| 152 | + |
| 153 | + if logStep is not None: |
| 154 | + pcount += 1 |
| 155 | + |
| 156 | + if pcount == numOfBoundaryP + numOfElasticP + numOfLiquidP: |
| 157 | + first_pass_complete = True |
| 158 | + print( |
| 159 | + "End of one batch of %i total points (%i types), at line %i, time: %i" |
| 160 | + % (pcount, len(points), line_count, time_count) |
| 161 | + ) |
| 162 | + all_points.append(points) |
| 163 | + all_point_types.append(types) |
| 164 | + |
| 165 | + points = {} |
| 166 | + types = [] |
| 167 | + pcount = 0 |
| 168 | + numOfBoundaryP = 0 |
| 169 | + |
| 170 | + time_count += 1 |
| 171 | + |
| 172 | + line_count += 1 |
| 173 | + |
| 174 | + print( |
| 175 | + "Loaded positions with %i elastic, %i liquid and %i boundary points (%i total), over %i lines" |
| 176 | + % ( |
| 177 | + numOfElasticP, |
| 178 | + numOfLiquidP, |
| 179 | + numOfBoundaryP, |
| 180 | + numOfElasticP + numOfLiquidP + numOfBoundaryP, |
| 181 | + line_count, |
| 182 | + ) |
| 183 | + ) |
| 184 | + |
| 185 | + print("Num of time points found: %i" % len(all_points)) |
| 186 | + print("Count of point types found: %s" % dict(sorted(count_point_types.items()))) |
| 187 | + |
| 188 | + create_mesh(0) |
| 189 | + |
| 190 | + max_time = len(all_points) - 1 |
| 191 | + |
| 192 | + slider = pl.add_slider_widget( |
| 193 | + create_mesh, rng=[0, max_time], value=0, title="Time point", style="modern" |
| 194 | + ) |
| 195 | + |
| 196 | + pl.add_checkbox_button_widget(play_animation, value=False) |
| 197 | + |
| 198 | + |
| 199 | +def play_animation(play): |
| 200 | + global plotter, last_meshes, all_points, all_point_types, replaying, slider |
| 201 | + print("Playing animation: %s" % play) |
| 202 | + |
| 203 | + if not play: |
| 204 | + replaying = False |
| 205 | + print("Animation stopped.") |
| 206 | + return |
| 207 | + else: |
| 208 | + replaying = True |
| 209 | + print("Animation started.") |
| 210 | + |
| 211 | + if last_meshes is None: |
| 212 | + print("No meshes to animate. Please load a model first.") |
| 213 | + return |
| 214 | + |
| 215 | + for i in range(len(all_points)): |
| 216 | + if not replaying: |
| 217 | + break |
| 218 | + curr_time = slider.GetSliderRepresentation().GetValue() |
| 219 | + |
| 220 | + print( |
| 221 | + " --- Animating step %i (curr_time: %s) of %i, %s" |
| 222 | + % (i, curr_time, len(all_points), play) |
| 223 | + ) |
| 224 | + next_time = curr_time + 1 |
| 225 | + slider.GetSliderRepresentation().SetValue(next_time) |
| 226 | + |
| 227 | + create_mesh(next_time) |
| 228 | + plotter.update() |
| 229 | + plotter.render() |
| 230 | + time.sleep(replay_speed) |
| 231 | + |
| 232 | + |
| 233 | +def create_mesh(step): |
| 234 | + step_count = step |
| 235 | + value = step_count |
| 236 | + global all_points, last_meshes, plotter, offset3d_, replaying, show_boundary |
| 237 | + |
| 238 | + index = int(value) |
| 239 | + if index >= len(all_points): |
| 240 | + print( |
| 241 | + "Index %i out of bounds for all_points with length %i" |
| 242 | + % (index, len(all_points)) |
| 243 | + ) |
| 244 | + replaying = False |
| 245 | + return |
| 246 | + |
| 247 | + print(" -- Creating new mesh at time point: %s (%s) " % (index, value)) |
| 248 | + curr_points_dict = all_points[index] |
| 249 | + |
| 250 | + print(" Plotting %i point types" % (len(curr_points_dict))) |
| 251 | + |
| 252 | + for type_, curr_points in curr_points_dict.items(): |
| 253 | + color, info, size = get_color_info_for_type(type_) |
| 254 | + is_boundary = "boundary" in info |
| 255 | + if show_boundary is False and is_boundary: |
| 256 | + continue |
| 257 | + |
| 258 | + print( |
| 259 | + " - Plotting %i points of type '%s' (%s), color: %s, size: %i" |
| 260 | + % (len(curr_points), type_, info, color, size) |
| 261 | + ) |
| 262 | + |
| 263 | + if len(curr_points) == 0: |
| 264 | + continue |
| 265 | + if type_ not in last_meshes: |
| 266 | + last_meshes[type_] = pv.PolyData(curr_points) |
| 267 | + last_meshes[type_].translate(offset3d_, inplace=True) |
| 268 | + |
| 269 | + # last_actor = |
| 270 | + plotter.add_mesh( |
| 271 | + last_meshes[type_], |
| 272 | + render_points_as_spheres=True, |
| 273 | + point_size=size, |
| 274 | + color=color, |
| 275 | + ) |
| 276 | + else: |
| 277 | + if not is_boundary: |
| 278 | + last_meshes[type_].points = curr_points |
| 279 | + last_meshes[type_].translate( |
| 280 | + (offset3d_[0], offset3d_[1], offset3d_[2]), inplace=True |
| 281 | + ) |
| 282 | + else: |
| 283 | + print("Boundary points not translated") |
| 284 | + |
| 285 | + plotter.render() |
| 286 | + # time.sleep(0.1) |
| 287 | + |
| 288 | + return |
| 289 | + |
| 290 | + |
| 291 | +if __name__ == "__main__": |
| 292 | + plotter = pv.Plotter() |
| 293 | + |
| 294 | + position_file = "buffers/position_buffer.txt" # can be overwritten by arg |
| 295 | + report_file = None |
| 296 | + |
| 297 | + if not os.path.isfile(position_file): |
| 298 | + position_file = ( |
| 299 | + "Sibernetic/position_buffer.txt" # example location in Worm3DViewer repo |
| 300 | + ) |
| 301 | + |
| 302 | + include_boundary = False |
| 303 | + |
| 304 | + if "-b" in sys.argv: |
| 305 | + include_boundary = True |
| 306 | + else: |
| 307 | + print("Run with -b to display boundary box") |
| 308 | + |
| 309 | + if len(sys.argv) > 1 and os.path.isfile(sys.argv[1]): |
| 310 | + if "json" in sys.argv[1]: |
| 311 | + position_file = None |
| 312 | + report_file = sys.argv[1] |
| 313 | + else: |
| 314 | + position_file = sys.argv[1] |
| 315 | + |
| 316 | + add_sibernetic_model( |
| 317 | + plotter, |
| 318 | + position_file, |
| 319 | + report_file, |
| 320 | + swap_y_z=True, |
| 321 | + include_boundary=include_boundary, |
| 322 | + ) |
| 323 | + plotter.set_background("white") |
| 324 | + plotter.add_axes() |
| 325 | + plotter.camera_position = "zx" |
| 326 | + plotter.camera.roll = 90 |
| 327 | + plotter.camera.elevation = 45 |
| 328 | + print(plotter.camera_position) |
| 329 | + |
| 330 | + if "-nogui" not in sys.argv: |
| 331 | + plotter.show() |
0 commit comments