-
Notifications
You must be signed in to change notification settings - Fork 12
/
object_surface_regions.py
930 lines (728 loc) · 32.7 KB
/
object_surface_regions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
"""
This file contains the classes for Object Surface Regions.
"""
# blender imports
import bpy
from bpy.props import BoolProperty, CollectionProperty, EnumProperty, \
FloatProperty, FloatVectorProperty, IntProperty, \
IntVectorProperty, PointerProperty, StringProperty
from bpy.app.handlers import persistent
import mathutils
# python imports
import re
import cellblender
# from . import ParameterSpace
# Object Surface Region Operators:
class MCELL_OT_region_add(bpy.types.Operator):
bl_idname = "mcell.region_add"
bl_label = "Add New Surface Region"
bl_description = "Add a new surface region to an object"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
context.object.mcell.regions.add_region(context)
return {'FINISHED'}
class MCELL_OT_region_remove(bpy.types.Operator):
bl_idname = "mcell.region_remove"
bl_label = "Remove Surface Region"
bl_description = "Remove selected surface region from object"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
context.object.mcell.regions.remove_region(context)
return {'FINISHED'}
class MCELL_OT_region_remove_all(bpy.types.Operator):
bl_idname = "mcell.region_remove_all"
bl_label = "Remove All Surface Regions"
bl_description = "Remove all surface regions from object"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
context.object.mcell.regions.remove_all_regions(context)
return {'FINISHED'}
class MCELL_OT_region_faces_assign(bpy.types.Operator):
bl_idname = "mcell.region_faces_assign"
bl_label = "Assign Selected Faces To Surface Region"
bl_description = "Assign selected faces to surface region"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
reg = context.object.mcell.regions.get_active_region()
if reg:
reg.assign_region_faces(context)
return {'FINISHED'}
class MCELL_OT_region_faces_remove(bpy.types.Operator):
bl_idname = "mcell.region_faces_remove"
bl_label = "Remove Selected Faces From Surface Region"
bl_description = "Remove selected faces from surface region"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
reg = context.object.mcell.regions.get_active_region()
if reg:
reg.remove_region_faces(context)
return {'FINISHED'}
class MCELL_OT_region_faces_select(bpy.types.Operator):
bl_idname = "mcell.region_faces_select"
bl_label = "Select Faces of Selected Surface Region"
bl_description = "Select faces of selected surface region"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
reg = context.object.mcell.regions.get_active_region()
if reg:
reg.select_region_faces(context)
return {'FINISHED'}
class MCELL_OT_region_faces_select_all(bpy.types.Operator):
bl_idname = "mcell.region_faces_select_all"
bl_label = "Select Faces of All Surface Regions"
bl_description = "Select faces of all surface regions"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
for reg in context.object.mcell.regions.region_list:
reg.select_region_faces(context)
return {'FINISHED'}
class MCELL_OT_region_faces_deselect(bpy.types.Operator):
bl_idname = "mcell.region_faces_deselect"
bl_label = "Deselect Faces of Selected Surface Region"
bl_description = "Deselect faces of selected surface region"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
reg = context.object.mcell.regions.get_active_region()
if reg:
reg.deselect_region_faces(context)
return {'FINISHED'}
class MCELL_OT_eliminate_overlapping_faces(bpy.types.Operator):
bl_idname = "mcell.eliminate_overlapping_faces"
bl_label = "Remove Faces Of This Region From All Other Regions"
bl_description = "Remove faces of this region from all other regions"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
reg = context.object.mcell.regions.get_active_region()
if reg:
reg.eliminate_overlapping_faces(context)
return{'FINISHED'}
class MCELL_OT_eliminate_all_overlaps(bpy.types.Operator):
bl_idname = "mcell.eliminate_all_overlaps"
bl_label = "Eliminate All Overlaps Of This Object"
bl_description = "Eliminate all overlaps og this object"
bl_options = {'REGISTER', 'UNDO'}
def execute(slef, context):
for reg in bpy.data.objects[context.active_object.name].mcell.regions.region_list:
reg.eliminate_overlapping_faces(context)
print('Eliminating overlaps in', reg.name)
return{'FINISHED'}
class MCELL_OT_face_get_regions(bpy.types.Operator):
bl_idname = "mcell.face_get_regions"
bl_label = "Get Region Membership of Selected Face"
bl_description = "Get region membership of selected face"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
context.object.mcell.regions.face_get_regions(context)
return {'FINISHED'}
class MCELL_OT_faces_get_regions(bpy.types.Operator):
bl_idname = "mcell.faces_get_regions"
bl_label = "Get Region Membership of Selected Faces"
bl_description = "Get region membership of selected faces"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
context.object.mcell.regions.faces_get_regions(context)
return {'FINISHED'}
# Object Surface Region Panel:
class MCELL_UL_check_region(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data,
active_propname, index):
if item.status:
layout.label(text=item.status, icon='ERROR')
else:
layout.label(text=item.name, icon='CHECKMARK')
# Region Callbacks:
def region_update(self, context):
context.object.mcell.regions.region_update()
return
# CellBlender Properties Classes for Surface Regions:
class MCellSurfaceRegionProperty(bpy.types.PropertyGroup):
id: IntProperty(name="Unique ID of This Region",default=-1)
name: StringProperty(
name="Region Name", default="Region", update=region_update)
status: StringProperty(name="Status")
def check_region_name(self, reg_name_list):
"""Checks for duplicate or illegal region name"""
status = ""
# Check for duplicate region name
if reg_name_list.count(self.name) > 1:
status = "Duplicate region: %s" % (self.name)
# Check for illegal names (Starts with a letter. No special characters)
reg_filter = r"(^[A-Za-z]+[0-9A-Za-z_.]*$)"
m = re.match(reg_filter, self.name)
if m is None:
status = "Region name error: %s" % (self.name)
self.status = status
return
def assign_region_faces(self, context):
mesh = context.active_object.data
if (mesh.total_face_sel > 0):
face_set = self.get_region_faces(mesh)
bpy.ops.object.mode_set(mode='OBJECT')
for f in mesh.polygons:
if f.select:
face_set.add(f.index)
bpy.ops.object.mode_set(mode='EDIT')
self.set_region_faces(mesh,face_set)
return {'FINISHED'}
def remove_region_faces(self, context):
mesh = context.active_object.data
if (mesh.total_face_sel > 0):
face_set = self.get_region_faces(mesh)
bpy.ops.object.mode_set(mode='OBJECT')
for f in mesh.polygons:
if f.select:
if f.index in face_set:
face_set.remove(f.index)
bpy.ops.object.mode_set(mode='EDIT')
self.set_region_faces(mesh,face_set)
return {'FINISHED'}
def select_region_faces(self, context):
mesh = context.active_object.data
face_set = self.get_region_faces(mesh)
msm = context.scene.tool_settings.mesh_select_mode[0:3]
context.scene.tool_settings.mesh_select_mode = (False, False, True)
bpy.ops.object.mode_set(mode='OBJECT')
for f in face_set:
mesh.polygons[f].select = True
bpy.ops.object.mode_set(mode='EDIT')
context.scene.tool_settings.mesh_select_mode = msm
return {'FINISHED'}
def deselect_region_faces(self, context):
mesh = context.active_object.data
face_set = self.get_region_faces(mesh)
msm = context.scene.tool_settings.mesh_select_mode[0:3]
context.scene.tool_settings.mesh_select_mode = (False, False, True)
bpy.ops.object.mode_set(mode='OBJECT')
for f in face_set:
mesh.polygons[f].select = False
bpy.ops.object.mode_set(mode='EDIT')
context.scene.tool_settings.mesh_select_mode = msm
return {'FINISHED'}
def eliminate_overlapping_faces(self, context):
bpy.ops.mesh.select_all(action = 'DESELECT')
#mesh = context.active_object.data
self.select_region_faces(context)
reg_names = context.object.mcell.regions.faces_get_regions(context)
for reg_name in reg_names:
if reg_name != self.name:
reg = bpy.data.objects[context.active_object.name].mcell.regions.region_list[reg_name]
reg.remove_region_faces(context)
#bpy.data.objects[context.active_object.name].mcell.regions.active_reg_index = bpy.data.objects[context.active_object.name].mcell.regions.region_list.find(reg)
#bpy.ops.mcell.region_faces_remove()
#bpy.data.objects[context.active_object.name].mcell.regions.active_reg_index = bpy.data.objects[context.active_object.name].mcell.regions.region_list.find(self.name)
def destroy_region(self, context):
"""Remove all region data from mesh"""
id = str(self.id)
# POSSIBLE FIXME? GET PARENT BLEND OBJECT OF THIS REGION_LIST
obj = context.active_object
mesh = obj.data
if mesh.get('mcell'):
if mesh['mcell'].get('regions'):
if mesh['mcell']['regions'].get(id):
for seg_id in mesh["mcell"]["regions"][id].keys():
mesh["mcell"]["regions"][id][seg_id] = []
mesh["mcell"]["regions"][id].clear()
mesh["mcell"]["regions"].pop(id)
def face_in_region(self, context, face_index):
"""Return True if face is in this region"""
mesh = context.active_object.data
reg_faces = self.get_region_faces(mesh)
return(face_index in reg_faces)
def init_region(self, context, id):
self.id = id
str_id = str(self.id)
mesh = context.active_object.data
if not mesh.get("mcell"):
mesh["mcell"] = {}
if not mesh["mcell"].get("regions"):
mesh["mcell"]["regions"] = {}
if not mesh["mcell"]["regions"].get(str_id):
mesh["mcell"]["regions"][str_id] = {}
def reset_region(self, mesh):
id = str(self.id)
if not mesh.get("mcell"):
mesh["mcell"] = {}
if not mesh["mcell"].get("regions"):
mesh["mcell"]["regions"] = {}
if not mesh["mcell"]["regions"].get(id):
mesh["mcell"]["regions"][id] = {}
for seg_id in mesh["mcell"]["regions"][id].keys():
mesh["mcell"]["regions"][id][seg_id] = []
mesh["mcell"]["regions"][id].clear()
def get_region_faces(self, mesh):
"""Given a mesh and a region id, return the set of region face indices"""
id = str(self.id)
face_rle = []
if mesh.get('mcell'):
if mesh['mcell'].get('regions'):
if mesh['mcell']['regions'].get(id):
for seg_id in mesh["mcell"]["regions"][id].keys():
face_rle.extend(mesh["mcell"]["regions"][id][seg_id].to_list())
if (len(face_rle) > 0):
face_set = set(self.rl_decode(face_rle))
else:
face_set = set([])
return(face_set)
def set_region_faces(self, mesh, face_set):
"""Set the faces of a given region id on a mesh, given a set of faces """
id = str(self.id)
face_list = list(face_set)
face_list.sort()
face_rle = self.rl_encode(face_list)
# Clear existing faces from this region id
self.reset_region(mesh)
# segment face_rle into pieces <= max_len (i.e. <= 32767)
# and assign these segments to the region id
max_len = 32767
seg_rle = face_rle
len_rle = len(seg_rle)
seg_idx = 0
while len_rle > 0:
if len_rle <= 32767:
mesh["mcell"]["regions"][id][str(seg_idx)] = seg_rle
len_rle = 0
else:
mesh["mcell"]["regions"][id][str(seg_idx)] = seg_rle[0:max_len]
tmp_rle = seg_rle[max_len:]
seg_rle = tmp_rle
len_rle = len(seg_rle)
seg_idx += 1
def rl_encode(self, l):
"""Run-length encode an array of face indices"""
runlen = 0
runstart = 0
rle = []
i = 0
while (i < len(l)):
if (runlen == 0):
rle.append(l[i])
runstart = l[i]
runlen = 1
i+=1
elif (l[i] == (runstart+runlen)):
if (i < (len(l)-1)):
runlen += 1
else:
if (runlen == 1):
rle.append(runstart+1)
else:
rle.append(-runlen)
i+=1
elif (runlen == 1):
runlen = 0
elif (runlen == 2):
rle.append(runstart+1)
runlen = 0
else:
rle.append(-(runlen-1))
runlen = 0
return(rle)
def rl_decode(self, l):
"""Decode a run-length encoded array of face indices"""
runlen = 0
runstart = 0
rld = []
i = 0
while (i < len(l)):
if (runlen == 0):
rld.append(l[i])
runstart = l[i]
runlen = 1
i+=1
elif (l[i] > 0):
runlen = 0
else:
for j in range(1,-l[i]+1):
rld.append(runstart+j)
runlen = 0
i+=1
return(rld)
class MCellSurfaceRegionListProperty(bpy.types.PropertyGroup):
region_list: CollectionProperty(
type=MCellSurfaceRegionProperty, name="Surface Region List")
active_reg_index: IntProperty(name="Active Region Index", default=0)
id_counter: IntProperty(name="Counter for Unique Region IDs", default=0)
get_region_info: BoolProperty(
name="Toggle to enable/disable region_info", default=False)
def get_active_region(self):
reg = None
if (len(self.region_list) > 0):
reg = self.region_list[self.active_reg_index]
return(reg)
def allocate_id(self):
""" Return a unique region ID for a new region """
if (len(self.region_list) <= 0):
# Reset the ID to 0 when there are no more regions
self.id_counter = 0
id = self.id_counter
self.id_counter += 1
return(id)
def face_get_regions(self,context):
""" Return the list of region IDs associated with one selected face """
reg_list = ""
mesh = context.active_object.data
if (mesh.total_face_sel == 1):
bpy.ops.object.mode_set(mode='OBJECT')
face_index = [f.index for f in mesh.polygons if f.select][0]
bpy.ops.object.mode_set(mode='EDIT')
for reg in self.region_list:
if reg.face_in_region(context,face_index):
reg_list = reg_list + " " + reg.name
return(reg_list)
def faces_get_regions(self,context):
""" Return list of region names associated with the selected faces """
reg_info = []
mesh = context.active_object.data
if (mesh.total_face_sel > 0):
bpy.ops.object.mode_set(mode='OBJECT')
selface_set = set([f.index for f in mesh.polygons if f.select])
bpy.ops.object.mode_set(mode='EDIT')
for reg in self.region_list:
reg_faces = reg.get_region_faces(mesh)
if not selface_set.isdisjoint(reg_faces):
reg_info.append(reg.name)
return(reg_info)
def add_region(self, context):
""" Add a new region to the list of regions and set as the active region """
id = self.allocate_id()
new_reg=self.region_list.add() # Note that this invalidates any handles that had been obtained from this list
new_reg.init_region(context, id)
# FIXME: CHECK FOR NAME COLLISION HERE: FIX BY ALLOCATING NEXT ID...
reg_name = "Region_%d" % (new_reg.id)
new_reg.name = reg_name # This will cause sorting via the callback to region_update on the region name
idx = self.region_list.find(reg_name)
self.active_reg_index = idx
def add_region_by_name(self, context, reg_name):
""" Add a new region to the list of regions and set as the active region """
curr_reg = self.get_active_region()
curr_reg_name = ""
if curr_reg:
curr_reg_name = curr_reg.name
id = self.allocate_id()
new_reg=self.region_list.add() # Note that this invalidates any handles that had been obtained from this list
new_reg.init_region(context, id)
# FIXME: CHECK FOR NAME COLLISION HERE: FIX BY ALLOCATING NEXT ID...
new_reg.name = reg_name # This will cause sorting via the callback to region_update on the region name
if curr_reg_name == "":
curr_reg_name = reg_name
idx = self.region_list.find(curr_reg_name)
self.active_reg_index = idx
def remove_all_regions(self, context):
for i in range(len(self.region_list)):
# First remove region data from mesh:
reg = self.region_list[0]
reg.destroy_region(context)
# Now remove the region from the object
self.region_list.remove(0)
self.active_reg_index = 0
def remove_region(self, context):
# First remove region data from mesh:
reg = self.get_active_region()
if reg:
reg.destroy_region(context)
# Now remove the region from the object
self.region_list.remove(self.active_reg_index)
self.active_reg_index -= 1
if (self.active_reg_index < 0):
self.active_reg_index = 0
def region_update(self):
"""Performs checks and sorts region list after update of region names"""
if self.region_list:
reg = self.get_active_region()
reg.check_region_name(self.region_list.keys())
self.sort_region_list()
return
def sort_region_list(self):
"""Sorts region list"""
act_reg = self.get_active_region()
act_reg_name = act_reg.name
# Sort the region list
self.inplace_quicksort(self.region_list, 0, len(self.region_list)-1)
act_i = self.region_list.find(act_reg_name)
self.active_reg_index = act_i
return
def inplace_quicksort(self, v, beg, end): # collection array, int, int
"""
Sorts a collection array, v, in place.
Sorts according values in v[i].name
"""
if ((end - beg) > 0): # only perform quicksort if we are dealing with > 1 values
pivot = v[beg].name # we set the first item as our initial pivot
i, j = beg, end
while (j > i):
while ((v[i].name <= pivot) and (j > i)):
i += 1
while ((v[j].name > pivot) and (j >= i)):
j -= 1
if (j > i):
v.move(i, j)
v.move(j-1, i)
if (not beg == j):
v.move(beg, j)
v.move(j-1, beg)
self.inplace_quicksort(v, beg, j-1)
self.inplace_quicksort(v, j+1, end)
return
def draw_panel(self, context, panel):
layout = panel.layout
self.draw_layout ( context, layout )
def draw_layout(self, context, layout):
active_obj = context.active_object
if (not (active_obj is None)) and (active_obj.type == 'MESH'):
layout.box() # Use as a separator
# Only draw the surface addition panel if this is an mcell object
if active_obj.mcell.include:
row = layout.row()
# row.label(text="Defined Regions:", icon='FORCE_LENNARDJONES')
row.label(text="Defined Surface Regions for %s:" % (active_obj.name), icon='FACE_MAPS')
#row = layout.row()
#row.prop ( active_obj, "name", text="Active Object" )
row = layout.row()
col = row.column()
col.template_list("MCELL_UL_check_region", "define_surf_regions",
self, "region_list",
self, "active_reg_index",
rows=2)
col = row.column(align=True)
col.operator("mcell.region_add", icon='ADD', text="")
col.operator("mcell.region_remove", icon='REMOVE', text="")
col.operator("mcell.region_remove_all", icon='X', text="")
# Could have region item draw itself in new row here:
row = layout.row()
if len(self.region_list) > 0:
layout.prop(self.get_active_region(), "name")
if active_obj.mode == 'EDIT' and (len(self.region_list)>0):
row = layout.row()
sub = row.row(align=True)
sub.operator("mcell.region_faces_assign", text="Assign")
sub.operator("mcell.region_faces_remove", text="Remove")
sub = row.row(align=True)
sub.operator("mcell.region_faces_select", text="Select")
sub.operator("mcell.region_faces_deselect", text="Deselect")
sub.operator("mcell.region_faces_select_all", text="Select All")
row1 = layout.row()
sub = row1.row(align = True)
sub.operator("mcell.eliminate_overlapping_faces", text = "Eliminate Overlaps")
sub.operator("mcell.eliminate_all_overlaps", text = "Eliminate All Overlaps")
# Option to Get Region Info
box = layout.box()
row = box.row(align=True)
row.alignment = 'LEFT'
if self.get_region_info:
row.prop(self, "get_region_info", icon='TRIA_DOWN',
text="Region Info for Selected Faces",
emboss=False)
reg_info = self.faces_get_regions(context)
for reg_name in reg_info:
row = box.row()
row.label(text=reg_name)
else:
row.prop(self, "get_region_info", icon='TRIA_RIGHT',
text="Region Info for Selected Faces",
emboss=False)
def format_update(self, obj):
"""
Update format of object regions.
This is required to update regions from pre v1.0 format to new v1.0 format
"""
mesh = obj.data
if len(self.region_list) > 0:
# We have object regions so check for pre v1.0 region format
# We'll do that by checking the region name on the mesh.
# If the first region on mesh is old then they're all old
self.sort_region_list()
# Check that the mesh has regions
if mesh.get("mcell"):
if mesh["mcell"].get("regions"):
mregs = mesh["mcell"]["regions"]
if len(mregs.keys()) > 0:
# if reg_name is alpha followed by alphanumeric
# then we've got an old format region
reg_name = mregs.keys()[0]
reg_filter = r"(^[A-Za-z]+[0-9A-Za-z_.]*$)"
m = re.match(reg_filter, reg_name)
if m is not None:
# We have old region format
# Make copies of all old regions
mreg_tmp = {}
for reg in self.region_list:
mreg_tmp[reg.name] = set(
mregs[reg.name].to_list())
# Clear old regions from mesh
for key in mregs.keys():
mregs[key] = []
mregs.clear()
# Convert old regions to new format
self.id_counter = 0
for reg in self.region_list:
reg.id = self.allocate_id()
id = str(reg.id)
mregs[id] = {}
reg.set_region_faces(mesh,mreg_tmp[reg.name])
else:
# The mesh did not have regions so the object regions are
# empty. But if id_counter is 0 then we have old object regions
# so we need to generate id's for the empty regions
if self.id_counter == 0:
for reg in self.region_list:
# Update the object region id's
reg.id = self.allocate_id()
else:
# There are no object regions but there might be mesh cruft
# Remove any region cruft we find attached to mesh["mcell"]
if mesh.get("mcell"):
if mesh["mcell"].get("regions"):
mregs = mesh["mcell"]["regions"]
for key in mregs.keys():
mregs[key] = []
mregs.clear()
mesh["mcell"].pop("regions")
# Properties Class for CellBlender Metadata on Blender Objects
class MCellObjectPropertyGroup(bpy.types.PropertyGroup):
regions: PointerProperty(
type=MCellSurfaceRegionListProperty, name="Defined Surface Regions")
include: BoolProperty(name="Include Object in Model", default=False)
def get_regions_dictionary (self, obj):
""" Return a dictionary with region names """
reg_dict = {}
obj_regs = self.regions.region_list
for reg in obj_regs:
id = str(reg.id)
mesh = obj.data
#reg_faces = list(object_surface_regions.get_region_faces(mesh,id))
reg_faces = list(reg.get_region_faces(mesh))
reg_faces.sort()
reg_dict[reg.name] = reg_faces
return reg_dict
def get_face_regions_dictionary (self, obj):
""" Return a dictionary with face id keys, and list of region names as a value"""
face_reg_dict = {}
obj_regs = self.regions.region_list
for reg in obj_regs:
mesh = obj.data
reg_faces = list(reg.get_region_faces(mesh))
for face in reg_faces:
if not face_reg_dict.get(face):
face_reg_dict[face] = []
face_reg_dict[face].append(reg.name)
return(face_reg_dict)
# Update format of object regions
# This is required to update regions from pre v1.0 format to new v1.0 format
# Note: This function is registered as a load_post handler
@persistent
def object_regions_format_update(context):
if not context:
context = bpy.context
scn_objs = context.scene.collection.children[0].objects
objs = [obj for obj in scn_objs if obj.type == 'MESH']
for obj in objs:
obj.mcell.regions.format_update(obj)
return
# Legacy code from when we used to store regions as vertex groups
# Useful to run from Blender's python console on some older models
# We'll eliminate this when our face regions have query features akin
# to those available with vertex groups.
class MCELL_OT_vertex_groups_to_regions(bpy.types.Operator):
bl_idname = "mcell.vertex_groups_to_regions"
bl_label = "Convert Vertex Groups of Selected Objects to Regions"
bl_description = "Convert Vertex Groups to Regions"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
scn = context.scene
select_objs = context.selected_objects
# For each selected object:
for obj in select_objs:
print(obj.name)
context.view_layer.objects.active = obj
obj.select_set(True)
obj_regs = obj.mcell.regions
vert_groups = obj.vertex_groups
# If there are vertex groups to convert:
if vert_groups:
mesh = obj.data
# For each vertex group:
for vg in vert_groups:
# Deselect the whole mesh:
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
# Select the vertex group:
print(vg.name)
bpy.ops.object.vertex_group_set_active(group=vg.name)
bpy.ops.object.vertex_group_select()
# If there are selected faces:
if (mesh.total_face_sel > 0):
print(" vg faces: %d" % (mesh.total_face_sel))
# Setup mesh regions IDProp if necessary:
if not mesh.get("mcell"):
mesh["mcell"] = {}
if not mesh["mcell"].get("regions"):
mesh["mcell"]["regions"] = {}
# look for vg.name in region_list and add if not found:
# method 1:
if (obj_regs.region_list.find(vg.name) == -1):
bpy.ops.mcell.region_add()
reg = obj_regs.region_list[
obj_regs.active_reg_index]
reg.name = vg.name
reg = obj_regs.region_list[vg.name]
# append faces in vertex group to faces in region:
# retreive or create region on mesh:
if not mesh["mcell"]["regions"].get(vg.name):
mesh["mcell"]["regions"][reg.name] = []
face_set = set([])
for f in mesh["mcell"]["regions"][reg.name]:
face_set.add(f)
print(" reg faces 0: %d" % (len(face_set)))
bpy.ops.object.mode_set(mode='OBJECT')
for f in mesh.polygons:
if f.select:
face_set.add(f.index)
bpy.ops.object.mode_set(mode='EDIT')
reg_faces = list(face_set)
reg_faces.sort()
print(" reg faces 1: %d" % (len(reg_faces)))
mesh["mcell"]["regions"][reg.name] = reg_faces
bpy.ops.object.mode_set(mode='OBJECT')
return {'FINISHED'}
classes = (
MCELL_OT_region_add,
MCELL_OT_region_remove,
MCELL_OT_region_remove_all,
MCELL_OT_region_faces_assign,
MCELL_OT_region_faces_remove,
MCELL_OT_region_faces_select,
MCELL_OT_region_faces_select_all,
MCELL_OT_region_faces_deselect,
MCELL_OT_eliminate_overlapping_faces,
MCELL_OT_eliminate_all_overlaps,
MCELL_OT_face_get_regions,
MCELL_OT_faces_get_regions,
MCELL_UL_check_region,
MCellSurfaceRegionProperty,
MCellSurfaceRegionListProperty,
MCellObjectPropertyGroup,
MCELL_OT_vertex_groups_to_regions,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)