Example #1
0
def import_obj(mdlobj, obj_mat, reg_mat, add_to_model_objects=True):
    """ Create a new Blender mesh object.

    Currently, this is used by both the swig and pyparsing based importers. In
    addition to creating a new mesh object, a red material is applied to all
    surface regions and a gray material is applied to the rest of the mesh.
    """

    meshes = bpy.data.meshes
    objs = bpy.data.objects
    scn = bpy.context.scene
    scn_objs = scn.objects

    objname = mdlobj.name
    print(objname, len(mdlobj.vertices), len(mdlobj.faces))
    obj = objs.get(objname)

    # Overwrite existing object if it has the same name as the object we are
    # trying to import
    if obj:
        scn.objects.unlink(obj)
        objs.remove(obj)

    mesh = meshes.new(objname)
    mesh.from_pydata(mdlobj.vertices, [], mdlobj.faces)

    mesh.materials.append(obj_mat)
    mesh.materials.append(reg_mat)

    obj = objs.new(objname, mesh)
    scn_objs.link(obj)

    # Object needs to be active to add a region to it because of how
    # init_region (called by add_region_by_name) works.
    scn.objects.active = obj

    all_regions = []
    for reg_name, regions in mdlobj.regions.items():
        obj.mcell.regions.add_region_by_name(bpy.context, reg_name)
        reg = obj.mcell.regions.region_list[reg_name]
        reg.set_region_faces(obj.data, regions.faces)
        all_regions.extend(regions.faces)

    # Get list of face indices
    face_list = [0] * len(mesh.polygons)
    mesh.polygons.foreach_get('index', face_list)

    # Create list of 1s and 0s. 1 if corresponding face index is in a region,
    # 0 otherwise.
    all_regions_set = set(all_regions)
    material_list = [1 if f in all_regions_set else 0 for f in face_list]

    # Faces that belong to a region are colored red
    mesh.polygons.foreach_set("material_index", material_list)

    if add_to_model_objects:
        preserve_selection_use_operator(bpy.ops.mcell.model_objects_add, obj)

    mesh.validate(verbose=True)
    mesh.update()
Example #2
0
def f_compartmentize_sc_only(context, swc_filepath):

	print("> Running: f_compartmentize_sc_only")

	# Dictionary of sections
	global mn_section_dict
	mn_section_dict = {}

	# Time
	t_st = []
	t_st.append(time.time())

	# Get data from the SWC file
	zero_pad, zero_cmmnd, sc_name_length, sg_name_length, sc_id1_1, sc_id1_2, sc_id2_1, sc_id2_2, sg_id_1, sg_id_2 = get_connections(swc_filepath)

	# Get the active object
	ob_list = context.selected_objects

	if len(ob_list) != 1:
		raise SystemError("Please select one (and only one) object")
	else:
		ob = ob_list[0]

	# Make sure everything is de-selected before we start
	bpy.ops.object.mode_set(mode='EDIT')
	bpy.ops.mesh.select_all(action='DESELECT')
	bpy.ops.object.mode_set(mode='OBJECT')

	# Get the regions of the cube object
	reg_list = ob.mcell.regions.region_list

	############################
	############################
	# Get all region's boundaries
	############################
	############################

	print("> Retrieving border edges for each section...")

	# Go over all sections, get the border edges
	for sec in reg_list:
		# Get the name
		sec_name = sec.name
		
		#if sec_name != "sc_38_46" and sec_name != "sc_31_38":
		#	continue

		# Check that it is a section
		if len(sec_name) == sc_name_length and sec_name[0:3] == 'sc_':
			
			# Get the vert indeces
			pt1 = int(sec_name[sc_id1_1:sc_id1_2])
			pt2 = int(sec_name[sc_id2_1:sc_id2_2])
			pt_min = min(pt1,pt2)
			pt_max = max(pt1,pt2)

			print("Section: " + str(pt_min) + " to " + str(pt_max))
			
			# Select the faces
			bpy.ops.object.mode_set(mode='EDIT')
			sec.select_region_faces(context)

			# Get the edges
			bpy.ops.object.mode_set(mode='EDIT')
			bpy.ops.mesh.region_to_loop()
			bpy.ops.object.mode_set(mode='OBJECT')
			mn_section_dict[(pt_min,pt_max)].sc_edge_list = [i.index for i in ob.data.edges if i.select]

			# Deselect all
			bpy.ops.object.mode_set(mode='EDIT')
			bpy.ops.mesh.select_all(action='DESELECT')
			bpy.ops.object.mode_set(mode='OBJECT')

	# Make sure the main guy is not selected
	ob.select = False
	context.scene.objects.active = ob

	# Time
	t_st.append(time.time())
	print("> Retrieved border edges: time: " + str(t_st[-1]-t_st[-2]))

	############################
	############################
	# Make SECTION boundaries
	############################
	############################

	print("> Making section boundaries...")

	# Go through all of the sections
	for this_sc_id,this_sc in mn_section_dict.items():
		
		# Border
		this_sc_edge = this_sc.sc_edge_list
		
		# Go through both of end verts
		for i_end,nghbr_sc_ids in enumerate(this_sc.nghbr_sc_ids):
			
			# Go through all of the neighboring sections
			for nghbr_sc_id in nghbr_sc_ids:
				
				# Don't double count
				if nghbr_sc_id > this_sc_id:
					
					# Border
					nghbr_sc_edge = mn_section_dict[nghbr_sc_id].sc_edge_list

					# Get the elements they share
					edge_share = list(set(this_sc_edge).intersection(set(nghbr_sc_edge)))

					if len(edge_share) > 0: # Do they share any?
						
						# Make a new plane

						# Get the center at this endpoint
						ctr_pt = this_sc.sc_pts[i_end]

						# Get the actual neighboring section
						nghbr_sc = mn_section_dict[nghbr_sc_id]

						# Convert the edge id's to actual vertex pairs
						edge_pair_list = [ob.data.edges[item].vertices for item in edge_share]
						edge_list = [[int(ids[0]),int(ids[1])] for ids in edge_pair_list]

						# Make the plane
						name_plane = this_sc.name + "_B_" + nghbr_sc.name
						plane = MN_plane(name=name_plane,sides_names=(this_sc.name,nghbr_sc.name), sides_ids=(this_sc_id,nghbr_sc_id),edge_list=edge_list,ctr_pt=ctr_pt)

						# Add the plane to both section boundaries
						this_sc.planes_sc_brdrs[i_end].append(plane)
						if nghbr_sc_id[0] == this_sc_id[i_end]:
							nghbr_sc.planes_sc_brdrs[0].append(plane)
						elif nghbr_sc_id[1] == this_sc_id[i_end]:
							nghbr_sc.planes_sc_brdrs[1].append(plane)
						else:
							print("Something went wrong!")

	# Time
	t_st.append(time.time())
	print("> Made section boundaries: time: " + str(t_st[-1]-t_st[-2]))

	############################
	############################
	# Make a segmenting plane object
	############################
	############################

	# Vertex, edge, face lists
	v_new_list = []
	v_id_dict = {}
	e_new_list = []
	f_new_list = []
	plane_f_dict = {}

	# Original vertex list
	ob_vert_list = [tuple(item.co) for item in ob.data.vertices]

	# Go through all the planes
	planes_done_list = []
	for sc_id, sc in mn_section_dict.items():
		for end_list in sc.planes_sc_brdrs: 
			for plane in end_list:
				if not plane.name in planes_done_list:
					
					# Add this to the lists
					
					# List of unique vertex ids
					v_all_list = [item for sublist in plane.edge_list for item in sublist]
					v_unique_list = list(set(v_all_list))

					# Add to the vertex list
					for v in v_unique_list:
						if not v in v_id_dict:
							v_new_list.append(tuple(ob_vert_list[v]))
							v_id_dict[v] = len(v_new_list)-1

					v_new_list.append(tuple(plane.ctr_pt))
					ctr_id = len(v_new_list) - 1

					# Add to the face list
					plane_f_dict[plane.name] = [] # Also, store what the face ids are in the new obj
					for e in plane.edge_list:
						f_new_list.append([v_id_dict[e[0]],v_id_dict[e[1]],ctr_id])
						plane_f_dict[plane.name].append(len(f_new_list)-1)

					# Add to the edge list
					e_new_list = []
					for e in plane.edge_list:
						e_new_list.append([v_id_dict[e[0]],v_id_dict[e[1]]])
					for v in v_unique_list:
						e_new_list.append([ctr_id,v_id_dict[v]])

					# Store as done
					planes_done_list.append(plane.name)

	# Make the object
	obj_name = ob.name + "_Segment"
	mesh_new = bpy.data.meshes.new(obj_name + "_mesh")
	mesh_new.from_pydata(v_new_list,e_new_list,f_new_list)
	# Validate and update
	mesh_new.validate(verbose=False) # Important! and i dont know why
	mesh_new.update()
	# Overwrite existing object
	obj_old = bpy.data.objects.get(obj_name)
	if obj_old:
		context.scene.objects.unlink(obj_old)
		bpy.data.objects.remove(obj_old)
	# New one
	obj_new = bpy.data.objects.new(obj_name,mesh_new)
	# Link
	context.scene.objects.link(obj_new)

	############################
	############################
	# Add to CellBlender
	############################
	############################

	# Set active
	context.scene.objects.active = obj_new
	obj_new.select = True

	# Add to MCell
	preserve_selection_use_operator(bpy.ops.mcell.model_objects_add, obj_new)

	# Add each of the planes as a region
	# Go through all the planes
	planes_done_list = []
	for sc_id, sc in mn_section_dict.items():
		for end_list in sc.planes_sc_brdrs: 
			for plane in end_list:
				if not plane.name in planes_done_list:

					# Region name
					reg_name = plane.name
					
					# Make region
					obj_new.mcell.regions.add_region_by_name(context,reg_name)
					# Get region
					new_reg = obj_new.mcell.regions.region_list[reg_name]

					# List of unique vertex ids in this plane
					v_all_list = [item for sublist in plane.edge_list for item in sublist]
					v_unique_list = list(set(v_all_list))

					# Assign faces
					new_reg.set_region_faces(obj_new.data, plane_f_dict[plane.name])

					# Prevent double counting
					planes_done_list.append(plane.name)

	# Update (but dont validate because who knows)
	obj_new.data.update()

	############################
	############################
	# Make a surface plane object
	############################
	############################

	bpy.ops.object.mode_set(mode='OBJECT')
	# Select the original surface object
	# Deselect all
	for ob_tmp in bpy.data.objects:
		ob_tmp.select = False
	ob.select = True
	context.scene.objects.active = ob

	# Duplicate
	bpy.ops.object.duplicate()

	# Add the duplicate to MCell
	obj_surf = bpy.data.objects[ob.name + ".001"]
	obj_surf.name = obj_surf.name[:-4] + "_Surface"

	# Select the new object
	# Deselect all
	for ob_tmp in bpy.data.objects:
		ob_tmp.select = False
	obj_surf.select = True
	context.scene.objects.active = obj_surf

	# Add to MCell
	preserve_selection_use_operator(bpy.ops.mcell.model_objects_add, obj_surf)

	# The regions are already there - yay! Just rename them
	reg_surf_list = obj_surf.mcell.regions.region_list
	for reg in reg_surf_list:
		reg.name += "_B_surf"

	# Update the object
	obj_surf.data.update()

	print("> Finished: f_compartmentize_sc_only")
	print("> Time: " + str(t_st[-1] - t_st[0]))


	'''
Example #3
0
def sbml2blender(inputFilePath, addObjects):
    print("loading .xml file... " + inputFilePath)
    #extrapolate object data from SBML file
    csgObjects = readSBMLFileCSGObject(inputFilePath)
    paramObjects = readSBMLFileParametricObject(inputFilePath)

    print("length of csgObjects: " + str(len(csgObjects)))
    print("length of paramObjects: " + str(len(paramObjects)))
    #generates sphere or bounding box in Blender

    #track average endosome size
    sum_size = 0.0  #sum of volumes
    n_size = 0.0  #number of endosomes

    #track average endosome surface area
    sum_surf = 0.0
    n_surf = 0.0

    csgObjectNames = []
    domainTypes = []
    name = []
    size = []
    location = []
    rotation = []
    domain = []
    ind = 0
    for csgobject in csgObjects:
        if (csgobject[1] == 'SOLID_SPHERE' or csgobject[1] == 'sphere'):
            name.append(csgobject[0])
            size.append([
                float(csgobject[2]),
                float(csgobject[3]),
                float(csgobject[4])
            ])
            location.append([
                float(csgobject[8]),
                float(csgobject[9]),
                float(csgobject[10])
            ])
            rotation.append([
                float(csgobject[5]),
                float(csgobject[6]),
                float(csgobject[7])
            ])
            domain.append(csgobject[11])
            if domain[ind] not in domainTypes:
                domainTypes.append(domain[ind])
            csgObjectNames.append(domain[ind])
            sum_size += (4.0 / 3.0) * (3.14) * (size[ind][0]) * (
                size[ind][1]) * (size[ind][2])
            n_size += 1
            sum_surf += surface_area_sphere(size[ind][0], size[ind][1],
                                            size[ind][2])
            n_surf += 1
            ind = ind + 1
    for domainType in domainTypes:
        ind = 0
        print(domainType)
        for csgObjectName in csgObjectNames:
            print(csgObjectName)
            if domain[ind] == domainType:
                print("match")
                obj = generateSphere(domainType + str(ind), size[ind],
                                     location[ind], rotation[ind])
            ind = ind + 1
        bpy.ops.object.select_pattern(pattern=domainType + '*')
        bpy.ops.object.join()
        obj.name = domainType
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.faces_shade_smooth()
        bpy.ops.object.mode_set(mode='OBJECT')
        if addObjects:
            preserve_selection_use_operator(bpy.ops.mcell.model_objects_add,
                                            obj)
        bpy.ops.object.select_all(action='DESELECT')

    namingPatterns = domainTypes

    print(namingPatterns)
    '''
    #bpy.ops.object.join()
    print("Here are the domains: ")
    print(csgObjectNames)
    #extract groups of strings with a lvenshtein distance less than 4
    csgObjectNames.sort()
    namingPatterns = []
        #   while len(csgObjectNames) > 0:
    for domainType in domainTypes:
        print(domainType)


    #namingPatterns.append([x for x in csgObjectNames if domainType==x])
    #csgObjectNames = [x for x in csgObjectNames if domainType==x]
    
    #extract common prefix for groups of strings
    namingPatterns = domainTypes
    print(namingPatterns)
    #namingPatterns = [common_prefix(x) for x in namingPatterns]

    #group objects by pattern - now domainType
    print(namingPatterns)
    for namingPattern in namingPatterns:
    #for domainType in domainTypes:
        print("Current domain: ")
        print(namingPattern)
        bpy.ops.object.select_name(name=format(namingPattern), extend=False)
        #bpy.ops.object.select_pattern(pattern='{0}*'.format(namingPattern), extend=False)
        #bpy.ops.object.select_pattern(pattern="devin2", extend=False)
        #bpy.ops.object.select_pattern(pattern="ivan1", extend=False)
        bpy.ops.object.join()
        obj = bpy.data.objects[bpy.context.active_object.name]
        #obj.name = namingPattern
        obj.name = namingPattern
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.faces_shade_smooth()
        bpy.ops.object.mode_set(mode='OBJECT')
        if addObjects:
            preserve_selection_use_operator(bpy.ops.mcell.model_objects_add, obj)
    
    #for name in csgObjectNames:
        #bpy.ops.object.select_by_type(type='MESH', extend=False)
        #bpy.ops.object.join()
    '''
    csgObjectNames = []
    for csgobject in csgObjects:
        if (csgobject[1] == 'SOLID_CUBE' or csgobject[1] == 'cube'):
            name = csgobject[0]
            location = [
                float(csgobject[2]),
                float(csgobject[3]),
                float(csgobject[4])
            ]
            size = [
                float(csgobject[2]),
                float(csgobject[3]),
                float(csgobject[4])
            ]
            location = [
                float(csgobject[8]),
                float(csgobject[9]),
                float(csgobject[10])
            ]
            obj = generateCube(name, size, location)
            csgObjectNames.append(name)

#            if addObjects:
#                preserve_selection_use_operator(bpy.ops.mcell.model_objects_add, obj)
#extract groups of strings with a lvenshtein distance less than 4
    csgObjectNames.sort()
    namingPatterns = []
    while len(csgObjectNames) > 0:
        namingPatterns.append([
            x for x in csgObjectNames if levenshtein(csgObjectNames[0], x) <= 4
        ])
        csgObjectNames = [
            x for x in csgObjectNames if levenshtein(csgObjectNames[0], x) > 4
        ]

    #extract common prefix for groups of strings
    namingPatterns = [common_prefix(x) for x in namingPatterns]

    #group objects by pattern
    for namingPattern in namingPatterns:
        bpy.ops.object.select_pattern(pattern='{0}*'.format(namingPattern),
                                      extend=False)
        bpy.ops.object.join()
        obj = bpy.data.objects[bpy.context.active_object.name]
        obj.name = namingPattern
        if addObjects:
            preserve_selection_use_operator(bpy.ops.mcell.model_objects_add,
                                            obj)

# print("The average endosome size is: " + str((sum_size/(n_size*1.0))))
# print("The average endosome surface area is " + str((sum_surf/(n_surf*1.0))))

    for paramObject in paramObjects:
        obj = generateMesh(paramObject)
        if addObjects:
            preserve_selection_use_operator(bpy.ops.mcell.model_objects_add,
                                            obj)
def f_compartmentize_cyl(context, swc_filepath, n_seg_plen):

    print("> Running: f_compartmentize_cyl")

    # Dictionary of sections
    global mn_section_dict
    mn_section_dict = {}

    # Time
    t_st = []
    t_st.append(time.time())

    # Get data from the SWC file
    get_connections(swc_filepath)

    # Get the active object
    ob_list = context.selected_objects

    if len(ob_list) != 1:
        raise SystemError("Please select one (and only one) object")
    else:
        ob = ob_list[0]

    # Make sure everything is de-selected before we start
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.select_all(action='DESELECT')
    bpy.ops.object.mode_set(mode='OBJECT')

    # Get the regions of the cube object
    reg_list = ob.mcell.regions.region_list

    ###
    # Get all region's boundaries
    ###

    print("> Retrieving border edges...")

    # Go over all sections, get the border edges
    for sec in reg_list:
        # Get the name
        sec_name = sec.name
        
        # Check that it is a section
        if len(sec_name) == 8 and sec_name[0:3] == 'sc_':
            
            # Get the vert indeces
            pt1 = int(sec_name[3:5])
            pt2 = int(sec_name[6:8])
            pt_min = min(pt1,pt2)
            pt_max = max(pt1,pt2)
            
            # Select the faces
            bpy.ops.object.mode_set(mode='EDIT')
            sec.select_region_faces(context)

            # Get the edges
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.region_to_loop()
            bpy.ops.object.mode_set(mode='OBJECT')
            mn_section_dict[(pt_min,pt_max)].sc_edge_list = [i.index for i in ob.data.edges if i.select]

            # Deselect all
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='DESELECT')
            bpy.ops.object.mode_set(mode='OBJECT')

    # Make sure the main guy is not selected
    ob.select = False
    context.scene.objects.active = ob

    # Time
    t_st.append(time.time())
    print("> Retrieved border edges: time: " + str(t_st[-1]-t_st[-2]))

    ###
    # Make SECTION boundaries
    ###

    print("> Making section boundaries...")

    # Go through all of the sections
    for this_sc_id,this_sc in mn_section_dict.items():
        
        # Border
        this_sc_edge = this_sc.sc_edge_list
        
        # Go through both of end verts
        for i_end,nghbr_sc_ids in enumerate(this_sc.nghbr_sc_ids):
            
            # Go through all of the neighboring sections
            for nghbr_sc_id in nghbr_sc_ids:
                
                # Don't double count
                if nghbr_sc_id > this_sc_id:
                    
                    # Border
                    nghbr_sc_edge = mn_section_dict[nghbr_sc_id].sc_edge_list

                    # Get the elements they share
                    edge_share = list(set(this_sc_edge).intersection(set(nghbr_sc_edge)))

                    if len(edge_share) > 0: # Do they share any?
                        
                        # Make a new plane

                        # Get the center at this endpoint
                        ctr_pt = this_sc.sc_pts[i_end]

                        # Get the actual neighboring section
                        nghbr_sc = mn_section_dict[nghbr_sc_id]

                        # Convert the edge id's to actual vertex pairs
                        edge_pair_list = [ob.data.edges[item].vertices for item in edge_share]
                        edge_list = [[int(ids[0]),int(ids[1])] for ids in edge_pair_list]

                        # Make the plane
                        name_plane = this_sc.name + "_B_" + nghbr_sc.name
                        plane = MN_plane(name=name_plane,sides_names=(this_sc.name,nghbr_sc.name), \
                            sides_ids=(this_sc_id,nghbr_sc_id),edge_list=edge_list,ctr_pt=ctr_pt)

                        # Add the plane to both section boundaries
                        this_sc.planes_sc_brdrs[i_end].append(plane)
                        if nghbr_sc_id[0] == this_sc_id[i_end]:
                            nghbr_sc.planes_sc_brdrs[0].append(plane)
                        elif nghbr_sc_id[1] == this_sc_id[i_end]:
                            nghbr_sc.planes_sc_brdrs[1].append(plane)
                        else:
                            print("Something went wrong!")

    # Time
    t_st.append(time.time())
    print("> Made section boundaries: time: " + str(t_st[-1]-t_st[-2]))

    ###
    # Make SEGMENT boundaries
    ###

    print("> Making segment boundaries...")

    # Edit mode
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.select_all(action='DESELECT')

    # Go through every section
    for sec in reg_list:
        # Get the name
        sec_name = sec.name
        
        print("Checking section: " + str(sec_name))

        # Check that it is a section
        if len(sec_name) == 8 and sec_name[0:3] == 'sc_':
            
            # Get the sc id
            pt1 = int(sec_name[3:5])
            pt2 = int(sec_name[6:8])
            pt_min = min(pt1,pt2)
            pt_max = max(pt1,pt2)
            this_sc_id = (pt_min,pt_max)

            # Get the sc
            this_sc = mn_section_dict[this_sc_id]

            # Determine the number of segments to make
            sc_unit_vec = this_sc.sc_pts[1] - this_sc.sc_pts[0]
            sc_unit_vec.normalize()
            sc_length = (this_sc.sc_pts[1] - this_sc.sc_pts[0]).length
            n_seg = int(sc_length * n_seg_plen) + 1

            print("Number of segments: " + str(n_seg))

            # Select the faces in this segment
            sec.select_region_faces(context)

            if n_seg != 1:
                    
                # List of dividing lengths from sc_pts[0]
                sc_dividing_len_list = [0.0]
                for i in range(1,n_seg):
                    sc_dividing_len_list.append(i * sc_length / n_seg)
                sc_dividing_len_list.append(2.0*sc_length) # factor 2 = some buffer

                # Make a list of all the faces in this region's projected distances and their compartments
                proj_face_list = []
                for f in ob.data.polygons:
                    if f.select == True:
                        proj_pt = project_pt_line(this_sc.sc_pts[0],this_sc.sc_pts[1],Vector(f.center))
                        proj_dist = (proj_pt - this_sc.sc_pts[0]).length
                        i_sg = bisect.bisect(sc_dividing_len_list, proj_dist) - 1

                        proj_face_list.append((f.index,proj_dist,i_sg))

                # Assign each face to a segment
                
                # List of face id's in each segment
                sg_f_list = []
                for i in range(0,n_seg):
                    sg_f_list.append([])

                # Go through all faces
                for f_index, proj_dist, i_sg in proj_face_list:
                    sg_f_list[i_sg].append(f_index)

                # Prune empty face lists....
                del_list = []
                for i_sg, f_list in enumerate(sg_f_list):
                    if len(f_list) == 0:
                        del_list.append(i_sg)
                del_list.reverse()
                for i in del_list:
                    del sg_f_list[i]

                # Store for this section
                this_sc.surf_sg_f_list = sg_f_list

                # The revised number of segments
                n_seg_rev = len(sg_f_list)

                if n_seg_rev != n_seg:
                    print("Revised number of segments to: " + str(n_seg_rev) + " after assigning faces")

                # Check that there is more than one segment
                if n_seg_rev > 1:

                    # Go through each segment, get the boundary edge
                    sg_bdry_e_list = []
                    for i in range(0,n_seg_rev):
                        sg_bdry_e_list.append([])
                    for i_sg, f_list in enumerate(sg_f_list):

                        # Deselect all faces
                        bpy.ops.object.mode_set(mode='EDIT') # Must be in edit mode
                        bpy.ops.mesh.select_all(action='DESELECT')

                        # Need to be in object mode to select faces
                        bpy.ops.object.mode_set(mode='OBJECT')

                        # Select all faces in this segment
                        for f in f_list:
                            ob.data.polygons[f].select = True

                        # Get the border loop                    
                        bpy.ops.object.mode_set(mode='EDIT') # Must be in edit mode
                        bpy.ops.mesh.region_to_loop()

                        # Need to be in object mode to select edges
                        bpy.ops.object.mode_set(mode='OBJECT')

                        for e in ob.data.edges:
                            if e.select == True:
                                sg_bdry_e_list[i_sg].append(e.index)

                    ###
                    # Determine boundaries and make bounding planes
                    ###
                    for i_sg in range(0,n_seg_rev-1):
                        bdry_list_1 = sg_bdry_e_list[i_sg]
                        bdry_list_2 = sg_bdry_e_list[i_sg+1]

                        # Check for overlap
                        bdry_overlap = list(set(bdry_list_1).intersection(set(bdry_list_2)))

                        if len(bdry_overlap) > 0:

                            # Convert to edge pair list
                            bdry_e_list_tmp = [ob.data.edges[item].vertices for item in bdry_overlap]
                            bdry_e_list = [[int(ids[0]),int(ids[1])] for ids in bdry_e_list_tmp]

                            ###
                            # METHOD 1 for placing center points
                            ###
                            '''
                            # Just place them evenly spaced along the cable model
                            plane_ctr_pt = this_sc.sc_pts[0] + (i_sg+1) * (sc_length/n_seg_rev) * sc_unit_vec
                            '''

                            ###
                            # METHOD 2 for placing center points
                            ###

                            # Get the edge vertices, average them, then project onto the cable model
                            edge_vert_all_list = [item for sublist in bdry_e_list for item in sublist]
                            edge_vert_uniq_list = list(set(edge_vert_all_list))
                            edge_vert_co_list = [ob.data.vertices[item].co for item in edge_vert_uniq_list]
                            ave_co = Vector([0.0,0.0,0.0])
                            for item in edge_vert_co_list:
                                ave_co += item
                            ave_co /= len(edge_vert_co_list)
                            plane_ctr_pt = project_pt_line(this_sc.sc_pts[0],this_sc.sc_pts[1],ave_co)

                            # Make plane
                            pname = "sc_%02d_%02d_sg_%02d_B_sc_%02d_%02d_sg_%02d" % (pt_min,pt_max,i_sg+1,pt_min,pt_max,i_sg+2)
                            plane = MN_plane(name=pname, sides_ids=((pt_min,pt_max,i_sg+1),(pt_min,pt_max,i_sg+2)), edge_list=bdry_e_list, ctr_pt=plane_ctr_pt)

                            # Store the plane
                            this_sc.planes_sg_brdrs.append(plane)

                            # Store the number of segments
                            this_sc.n_seg = n_seg_rev

                else:

                    print("Number of segments: 1 (post revision)")

                    # Store the number of segments
                    this_sc.n_seg = 1

                    # Surface faces have already been stored for this section

            else:

                print("Number of segments: 1 (pre revision)")

                # Store the number of segments
                this_sc.n_seg = 1

                # Store the surface faces for this section
                surf_sg_f_list = [[]]
                for f in ob.data.polygons:
                    if f.select == True:
                        surf_sg_f_list[0].append(f.index)

                this_sc.surf_sg_f_list = surf_sg_f_list

            # Deselect all faces
            bpy.ops.object.mode_set(mode='EDIT') # Must be in edit mode
            bpy.ops.mesh.select_all(action='DESELECT')


    ###
    # Fix the names of the section planes
    ###

    # Make planes
    planes_done_list = []
    for sc_1 in mn_section_dict.values():
        for edge_list in sc_1.planes_sc_brdrs: 
            for plane in edge_list:
                if not plane.name in planes_done_list:

                    # Get the two sections on the plane's sides
                    ids_side_1 = sc_1.sc_id
                    side_ids = plane.sides_ids
                    if side_ids[0] == ids_side_1:
                        ids_side_2 = side_ids[1]
                    else:
                        ids_side_2 = side_ids[0]

                    # Get the other section
                    for sc in mn_section_dict.values():
                        if sc.sc_id == ids_side_2:
                            sc_2 = sc
                            break

                    # Which vertex are we at?
                    if ids_side_1[0] in ids_side_2:
                        vert_id = ids_side_1[0]
                    else:
                        vert_id = ids_side_1[1]

                    # For each section:
                    # if we are at the lower of the two vertices => segment 1
                    # if we are at the higher of the two => segment n_seg_#
                    if ids_side_1[0] == vert_id:
                        trip_1 = (ids_side_1[0],ids_side_1[1],1)
                    else:
                        trip_1 = (ids_side_1[0],ids_side_1[1],sc_1.n_seg)
                    if ids_side_2[0] == vert_id:
                        trip_2 = (ids_side_2[0],ids_side_2[1],1)
                    else:
                        trip_2 = (ids_side_2[0],ids_side_2[1],sc_2.n_seg)

                    # The new name of this plane
                    new_plane_name = "sc_%02d_%02d_sg_%02d" % min(trip_1,trip_2) + "_B_" + "sc_%02d_%02d_sg_%02d" % max(trip_1,trip_2)
                    print("Fixing name: " + str(plane.name) + " to: " + str(new_plane_name))
                    plane.name = new_plane_name

                    # Don't repeat this plane
                    planes_done_list.append(plane.name)


    ############################
    ############################
    # Make a surface plane object
    ############################
    ############################


    bpy.ops.object.mode_set(mode='OBJECT')
    # Select the original surface object
    # Deselect all
    for ob_tmp in bpy.data.objects:
        ob_tmp.select = False
    ob.select = True
    context.scene.objects.active = ob

    # Duplicate
    bpy.ops.object.duplicate()

    # Add the duplicate to MCell
    obj_surf = bpy.data.objects[ob.name + ".001"]
    obj_surf.name = obj_surf.name[:-4] + "_Surface"

    # Select the new object
    # Deselect all
    for ob_tmp in bpy.data.objects:
        ob_tmp.select = False
    obj_surf.select = True
    context.scene.objects.active = obj_surf

    # Add to MCell
    preserve_selection_use_operator(bpy.ops.mcell.model_objects_add, obj_surf)

    # Remove all existing MCell regions
    obj_surf.mcell.regions.remove_all_regions(context)

    # Update the object
    obj_surf.data.update()

    # Add each of the surfaces as a region
    planes_done_list = []
    for sc_id, sc in mn_section_dict.items():
        for i_sg in range(0,sc.n_seg):
            # Region name
            reg_name = "sc_%02d_%02d_sg_%02d_B_surf" % (sc_id[0],sc_id[1],i_sg+1)
    
            # Make region
            obj_surf.mcell.regions.add_region_by_name(context,reg_name)
            # Get region
            new_reg = obj_surf.mcell.regions.region_list[reg_name]

            # Assign faces
            new_reg.set_region_faces(obj_surf.data, sc.surf_sg_f_list[i_sg])

            # Prevent double counting
            planes_done_list.append(plane.name)

    # Update (but dont validate because who knows)
    obj_surf.data.update()


    ############################
    ############################
    # Make a segment plane object
    ############################
    ############################


    # Vertex, edge, face lists
    v_new_list = []
    v_id_dict = {}
    e_new_list = []
    f_new_list = []
    plane_f_dict = {}

    # Original vertex list
    ob_vert_list = [tuple(item.co) for item in ob.data.vertices]

    # Go through all the planes
    planes_done_list = []
    for sc_id, sc in mn_section_dict.items():
        # List of all planes
        all_plane_list = sc.planes_sg_brdrs
        for end_list in sc.planes_sc_brdrs:
            all_plane_list += end_list
        # Go through all planes
        for plane in all_plane_list:
            if not plane.name in planes_done_list:
                
                # Add this to the lists
                
                # List of unique vertex ids
                v_all_list = [item for sublist in plane.edge_list for item in sublist]
                v_unique_list = list(set(v_all_list))

                # Add to the vertex list
                for v in v_unique_list:
                    if not v in v_id_dict:
                        v_new_list.append(tuple(ob_vert_list[v]))
                        v_id_dict[v] = len(v_new_list)-1

                v_new_list.append(tuple(plane.ctr_pt))
                ctr_id = len(v_new_list) - 1

                # Add to the face list
                plane_f_dict[plane.name] = [] # Also, store what the face ids are in the new obj
                for e in plane.edge_list:
                    f_new_list.append([v_id_dict[e[0]],v_id_dict[e[1]],ctr_id])
                    plane_f_dict[plane.name].append(len(f_new_list)-1)

                # Add to the edge list
                e_new_list = []
                for e in plane.edge_list:
                    e_new_list.append([v_id_dict[e[0]],v_id_dict[e[1]]])
                for v in v_unique_list:
                    e_new_list.append([ctr_id,v_id_dict[v]])

                # Store as done
                planes_done_list.append(plane.name)

    # Make the object
    obj_name = ob.name + "_Segment"
    mesh_new = bpy.data.meshes.new(obj_name + "_mesh")
    mesh_new.from_pydata(v_new_list,e_new_list,f_new_list)
    # Validate and update
    mesh_new.validate(verbose=False) # Important! and i dont know why
    mesh_new.update()
    # Overwrite existing object
    obj_old = bpy.data.objects.get(obj_name)
    if obj_old:
        context.scene.objects.unlink(obj_old)
        bpy.data.objects.remove(obj_old)
    # New one
    obj_seg = bpy.data.objects.new(obj_name,mesh_new)
    # Link
    context.scene.objects.link(obj_seg)

    # Select the new object
    # Deselect all
    for ob_tmp in bpy.data.objects:
        ob_tmp.select = False
    obj_seg.select = True
    context.scene.objects.active = obj_seg

    # Add to MCell
    preserve_selection_use_operator(bpy.ops.mcell.model_objects_add, obj_seg)

    # Update the object
    obj_seg.data.update()

    # Add each of the surfaces as a region
    planes_done_list = []
    for sc_id, sc in mn_section_dict.items():
        # List of all planes
        all_plane_list = sc.planes_sg_brdrs
        for end_list in sc.planes_sc_brdrs:
            all_plane_list += end_list
        # Go through all planes
        for plane in all_plane_list:
            if not plane.name in planes_done_list:

                # Region name
                reg_name = plane.name
        
                # Make region
                obj_seg.mcell.regions.add_region_by_name(context,reg_name)
                # Get region
                new_reg = obj_seg.mcell.regions.region_list[reg_name]

                # Assign faces
                new_reg.set_region_faces(obj_seg.data, plane_f_dict[plane.name])

                # Prevent double counting
                planes_done_list.append(plane.name)

    # Update (but dont validate because who knows)
    obj_seg.data.update()

    print("> Finished: f_compartmentize_cyl")
    print("> Time: " + str(t_st[-1] - t_st[0]))

    return
def f_compartmentize_tet(context, swc_filepath, n_seg_plen):

    print("> Running: f_compartmentize_tet")

    # Time
    t_st = []
    t_st.append(time.time())

    global mn_section_dict
    global mn_segment_dict
    
    mn_section_dict = {}
    mn_segment_dict = {}
    
    # Get data from the SWC file
    get_connections(swc_filepath)

    # Get the active object
    ob_list = context.selected_objects

    if len(ob_list) != 1:
        raise SystemError("Please select one (and only one) object")
    else:
        ob = ob_list[0]
        ob_name = ob.name
    
    # All vertices
    ob_vert_list = [tuple(item.co) for item in ob.data.vertices]

    # Make sure everything is de-selected before we start
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.select_all(action='DESELECT')
    bpy.ops.object.mode_set(mode='OBJECT')

    # Get the regions of the cube object
    reg_list = ob.mcell.regions.region_list

    # Go over all sections, get the border edges
    sc_edge_dict = {}
    for sec in reg_list:
        # Get the name
        sec_name = sec.name
        
        # Check that it is a section
        if len(sec_name) == 8 and sec_name[0:3] == 'sc_':
            
            print("Section: " + str(sec_name))

            # Get the vert indeces
            pt1 = int(sec_name[3:5])
            pt2 = int(sec_name[6:8])
            pt_min = min(pt1,pt2)
            pt_max = max(pt1,pt2)
            
            # Get the faces
            bpy.ops.object.mode_set(mode='EDIT')
            sec.select_region_faces(context)
            
            # Store the surface as a new plane
            bpy.ops.object.mode_set(mode='OBJECT')
            surf_face_list = [i.vertices for i in ob.data.polygons if i.select]
            surf_name = 'sc_%02d_%02d'%(pt_min,pt_max)
            surf_sides_names = (surf_name,-1)
            surf_sides_ids = ((pt_min,pt_max),(-1))
            # Make the plane
            plane_surf = MN_plane(name=surf_name, sides_names=surf_sides_names, sides_ids=surf_sides_ids, vert_list=ob_vert_list, face_list=surf_face_list, CONV=True)
            # Store the plane as the surface for this section
            mn_section_dict[(pt_min,pt_max)].plane_surf = plane_surf
            
            # Get the edges
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.region_to_loop()
            bpy.ops.object.mode_set(mode='OBJECT')
            sc_edge_dict[(pt_min,pt_max)] = [i.index for i in ob.data.edges if i.select]

            # Deselect all
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='DESELECT')
            bpy.ops.object.mode_set(mode='OBJECT')

    # Make sure the main guy is not selected
    ob.select = False
    context.scene.objects.active = ob

    # Time
    t_st.append(time.time())
    print("> Retrieved border edges: time: " + str(t_st[-1]-t_st[-2]))

    ###
    # Make SECTION boundaries
    ###

    # Go through all of the sections
    for this_sc_id in mn_section_dict.keys():
        this_sc = mn_section_dict[this_sc_id]
        
        # Border
        this_sc_edge = sc_edge_dict[this_sc_id]
        
        # Go through both of end verts
        for i_end,nghbr_sc_ids in enumerate(this_sc.nghbr_sc_ids):
            
            # Go through all of the neighboring sections
            for nghbr_sc_id in nghbr_sc_ids:
                
                # Don't double count
                if nghbr_sc_id > this_sc_id:
                    
                    # Border
                    nghbr_sc_edge = sc_edge_dict[nghbr_sc_id]

                    # Get the elements they share
                    edge_share = list(set(this_sc_edge).intersection(set(nghbr_sc_edge)))

                    if len(edge_share) > 0: # Do they share any?
                        
                        # Vertices
                        plane_vert_list = ob_vert_list + [tuple(this_sc.sc_pts[i_end])]
                        
                        # Index of the ctr pt
                        ctr_pt_id = len(plane_vert_list)-1
                        
                        # Convert the edges to vertices
                        verts_share = [[ob.data.edges[edge].vertices[0],ob.data.edges[edge].vertices[1]] for edge in edge_share]

                        # Faces
                        plane_face_list = [[ctr_pt_id, item[0], item[1]] for item in verts_share]

                        # Get the actual neighboring section
                        nghbr_sc = mn_section_dict[nghbr_sc_id]

                        # Make a new plane
                        name_plane = this_sc.name + "_B_" + nghbr_sc.name
                        plane = MN_plane(name=name_plane,sides_names=(this_sc.name,nghbr_sc.name),sides_ids=(this_sc_id,nghbr_sc_id),vert_list=plane_vert_list,face_list=plane_face_list,CONV=True)
                        
                        # Add the plane to both section boundaries
                        this_sc.planes_sc_brdrs[i_end].append(plane)
                        if nghbr_sc_id[0] == this_sc_id[i_end]:
                            nghbr_sc.planes_sc_brdrs[0].append(plane)
                        elif nghbr_sc_id[1] == this_sc_id[i_end]:
                            nghbr_sc.planes_sc_brdrs[1].append(plane)
                        else:
                            print("Something went wrong!")

                        print("Made plane: " + str(name_plane))

    # # # Free memory
    del ob

    # Time
    t_st.append(time.time())
    print("> Made section boundaries: time: " + str(t_st[-1]-t_st[-2]))

    ###
    # Mesh errr thang
    ###

    # Global list of tet face marker to plane it belongs to
    global face_marker_plane_dict
    face_marker_plane_dict = {}

    mesh_built = make_tetgen_mesh()

    # Make it an object
    #print("> Creating a Blender object from a TetGen mesh....")
    #new_obj_meshpy("tetgen", mesh_built);

    # Get the coordinates of the tet points
    mesh_vert_co_list = [tuple(item) for item in list(mesh_built.points)]

    # List of tets to vertex indices
    mesh_tet_vert_list = list(mesh_built.elements)

    # List of neighbours of each tet
    mesh_tet_nghbr_list = list(mesh_built.neighbors)

    # List of face vertex ids to face markers
    mesh_face_vert_marker_dict = dict(mesh_built.face_vertex_indices_to_face_marker)

    # Tet attributes - every index is a unique (but unknown :'( ) region
    mesh_tet_att = list(mesh_built.element_attributes)

    # # # Reclaim memory
    del mesh_built

    # Dictionary of section attribute to tet id
    sc_att_tet_dict = {}
    for i_tet, sc_att in enumerate(mesh_tet_att):
        if int(sc_att) in sc_att_tet_dict:
            sc_att_tet_dict[int(sc_att)].append(i_tet)
        else:
            sc_att_tet_dict[int(sc_att)] = [i_tet]

    # Figure out what attribute indices correspond to which section
    sc_tet_dict = {}
    for sc_att in sc_att_tet_dict.keys():
        sc_tets = sc_att_tet_dict[sc_att]
        
        # Find surface tets
        DONE = False
        for tet in sc_tets:
            if -1 in mesh_tet_nghbr_list[tet]:
                # Get its verts
                tet_verts = mesh_tet_vert_list[tet]
                # Get the triplets of face vertex indices
                for triplet in list(itertools.combinations(tet_verts,3)):
                    # Get the face marker
                    face_marker = mesh_face_vert_marker_dict[frozenset(triplet)]
                    # Is it a surface?
                    if face_marker < 0:
                        # What plane corresponds to this marker
                        plane = face_marker_plane_dict[face_marker]
                        # What is the section key for this plane
                        sc_id = plane.sides_ids[0]
                        # Store
                        sc_tet_dict[sc_id] = sc_tets
                        # Stop for this section
                        DONE = True
                        break
                            
                if DONE == True:
                    break

    # Time
    t_st.append(time.time())
    print("> Created Tetgen Mesh: time: " + str(t_st[-1]-t_st[-2]))

    ###
    # Segment each of the sections
    ###
    
    for sec_id in list(mn_section_dict.keys()):
        
        # if sec_id in sc_tet_dict: # Why should this not be the case? This occurs for some reason....
        
        sec = mn_section_dict[sec_id]
        print("Dividing tets into segments for section: " + str(sec_id))

        # Make the dividing planes
        segment_meshpy(mesh_vert_co_list, mesh_tet_vert_list, sc_tet_dict[sec_id], mesh_tet_nghbr_list, mesh_face_vert_marker_dict, sec, n_seg_plen)

    # Time
    t_st.append(time.time())
    print("> Created segment borders for each section: time: " + str(t_st[-1]-t_st[-2]))

    # # # Free memory
    del mesh_vert_co_list
    del mesh_tet_vert_list
    del mesh_tet_nghbr_list
    del mesh_face_vert_marker_dict
    del mesh_tet_att

    ###
    # After making all the segments: Convert segment->section boundaries into segment->segment boundaries!
    ###

    # Time
    t_st.append(time.time())

    # Go through all of the segments
    for mn_seg_id in mn_segment_dict.keys():
        mn_seg = mn_segment_dict[mn_seg_id]

        # Go through all of the section bounding planes
        for plane_sc in mn_seg.planes_sc:

            # What's on the other side of this plane?
            sides_ids = plane_sc.sides_ids
            if len(sides_ids[0]) == 2 and len(sides_ids[1]) == 3:
                other_side_sc = sides_ids[0]
            elif len(sides_ids[1]) == 2 and len(sides_ids[0]) == 3:
                other_side_sc = sides_ids[1]
            else:
                # Not a section -> segment plane - somehow?
                print("Warning! This isn't a segment->section plane! Should this be possible?")
            
            # Get all the segments that are in the other side's section
            for other_mn_seg_id in mn_segment_dict.keys():

                # Obviously, check yourself before you wreck yourself
                if mn_seg_id != other_mn_seg_id:
                    # Is this segment in the section we care about
                    if other_mn_seg_id[0:2] == other_side_sc:

                        # Get the segment
                        other_mn_seg = mn_segment_dict[other_mn_seg_id]

                        # Check all the planes in this segment for overlap
                        for other_plane_sc in other_mn_seg.planes_sc:
                            
                            # What's on the other side of this plane?
                            other_sides_ids = other_plane_sc.sides_ids
                            if len(other_sides_ids[0]) == 2 and len(other_sides_ids[1]) == 3:
                                other_other_side_sc = other_sides_ids[0]
                            elif len(other_sides_ids[1]) == 2 and len(other_sides_ids[0]) == 3:
                                other_other_side_sc = other_sides_ids[1]
                            else:
                                # Not a section -> segment plane - somehow?
                                print("Warning! This isn't a segment->section plane! Should this be possible?")

                            # Is the section on the other side of this plane to original one?
                            if mn_seg_id[0:2] == other_other_side_sc:
                                
                                # The would be plane id
                                min_id = min(mn_seg_id,other_mn_seg_id)
                                max_id = max(mn_seg_id,other_mn_seg_id)
                                p_sides_ids = (min_id,max_id)

                                # Make sure we didn't check this already
                                if not p_sides_ids in [plane0.sides_ids for seg0 in mn_segment_dict.values() for plane0 in seg0.planes_sg]:
                                
                                    # Check for overlap
                                    vert_list, face_list = plane_sc.overlap(other_plane_sc)
                                    if vert_list != None:
                                        
                                        # Yes! there's overlap
                                        # Make yet another plane
                                        p_name = 'sc_%02d_%02d_sg_%02d'%min_id + '_B_' + 'sc_%02d_%02d_sg_%02d'%max_id
                                        p_sides_names = ('sc_%02d_%02d_sg_%02d'%min_id, 'sc_%02d_%02d_sg_%02d'%max_id)
                                        plane = MN_plane(name=p_name, sides_names=p_sides_names, sides_ids=p_sides_ids, vert_list=vert_list, face_list=face_list, CONV=True)
                                        
                                        # Add the plane to both segments
                                        mn_seg.planes_sg.append(plane)
                                        other_mn_seg.planes_sg.append(plane)


    # Time
    t_st.append(time.time())
    print("> Fixed correspondence of section-section planes: time: " + str(t_st[-1]-t_st[-2]))

    ###
    # Create a single object out of all the plane surfaces with MCell regions
    ###
    
    plane_surf_vert_stack = []
    plane_surf_face_stack = []
    
    # Go through all segments
    plane_sides_ids_done = [] # Make sure not to double count any planes
    for mn_seg_id in mn_segment_dict.keys():
        mn_seg = mn_segment_dict[mn_seg_id]
        
        if mn_seg.plane_surf != -1: # No surface for this segment (definitely possible!)
            
            if not mn_seg.plane_surf.sides_ids in plane_sides_ids_done:
                
                plane_surf_vert_stack += [mn_seg.plane_surf.vert_list]
                plane_surf_face_stack += [mn_seg.plane_surf.face_list]

                # Prevent double counting
                plane_sides_ids_done.append(mn_seg.plane_surf.sides_ids)

    # Correct the lists
    plane_surf_vert_list, plane_surf_face_list = stack_lists(plane_surf_vert_stack, plane_surf_face_stack)

    # Make the plane
    mn_surface_plane = MN_plane(name=ob_name+"_surface",vert_list = plane_surf_vert_list, face_list = plane_surf_face_list, CONV=True)

    # Make make the plane
    obj_mn_surface_plane = mn_surface_plane.make_plane()

    # Set active
    context.scene.objects.active = obj_mn_surface_plane
    obj_mn_surface_plane.select = True

    # Add to MCell
    preserve_selection_use_operator(bpy.ops.mcell.model_objects_add, obj_mn_surface_plane)

    # Add each of the segments as a region
    face_idx_ctr = 0
    plane_sides_ids_done = [] # Make sure not to double count any planes
    for mn_seg_id in mn_segment_dict.keys():
        mn_seg = mn_segment_dict[mn_seg_id]
        
        if mn_seg.plane_surf != -1: # No surface for this segment (definitely possible!)
            
            if not mn_seg.plane_surf.sides_ids in plane_sides_ids_done:
                
                # Region name
                reg_name = mn_seg.plane_surf.name

                # Make region
                obj_mn_surface_plane.mcell.regions.add_region_by_name(context,reg_name)
                # Get region
                new_reg = obj_mn_surface_plane.mcell.regions.region_list[reg_name]
                
                # Assign faces
                face_ids = range(face_idx_ctr,face_idx_ctr+len(mn_seg.plane_surf.face_list))
                face_idx_ctr += len(mn_seg.plane_surf.face_list)
                new_reg.set_region_faces(obj_mn_surface_plane.data, face_ids)

                # Prevent double counting
                plane_sides_ids_done.append(mn_seg.plane_surf.sides_ids)

    # Update (but dont validate because who knows)
    obj_mn_surface_plane.data.update()

    # Deselect
    obj_mn_surface_plane.select = False

    # Time
    t_st.append(time.time())
    print("> Created surface plane: time: " + str(t_st[-1]-t_st[-2]))

    ###
    # Create a single object out of all the segments surfaces with MCell regions
    ###

    seg_surf_vert_stack = []
    seg_surf_face_stack = []

    # Go through all segments
    plane_sides_ids_done = [] # Make sure not to double count any planes
    for mn_seg_id in mn_segment_dict.keys():
        mn_seg = mn_segment_dict[mn_seg_id]
        
        # Go through all it's segment bounding planes
        for plane_sg in mn_seg.planes_sg:
            
            if not plane_sg.sides_ids in plane_sides_ids_done:
                seg_surf_vert_stack += [plane_sg.vert_list]
                seg_surf_face_stack += [plane_sg.face_list]
                
                # Prevent double counting
                plane_sides_ids_done.append(plane_sg.sides_ids)

    # Correct the lists
    seg_surf_vert_list, seg_surf_face_list = stack_lists(seg_surf_vert_stack, seg_surf_face_stack)

    # Make the plane
    mn_seg_plane = MN_plane(name=ob_name+"_segment",vert_list = seg_surf_vert_list, face_list = seg_surf_face_list, CONV=True)

    # Make make the plane
    obj_mn_seg_plane = mn_seg_plane.make_plane()

    # Set active
    context.scene.objects.active = obj_mn_seg_plane
    obj_mn_seg_plane.select = True

    # Add to MCell
    preserve_selection_use_operator(bpy.ops.mcell.model_objects_add, obj_mn_seg_plane)

    # Add each of the segments as a region
    face_idx_ctr = 0
    plane_sides_ids_done = [] # Make sure not to double count any planes
    for mn_seg_id in mn_segment_dict.keys():
        mn_seg = mn_segment_dict[mn_seg_id]
        
        # Go through all it's segment bounding planes
        for plane_sg in mn_seg.planes_sg:

            if not plane_sg.sides_ids in plane_sides_ids_done:
                
                # Region name
                reg_name = plane_sg.name
                
                # Make region
                obj_mn_seg_plane.mcell.regions.add_region_by_name(context,reg_name)
                # Get region
                new_reg = obj_mn_seg_plane.mcell.regions.region_list[reg_name]

                # Assign faces
                face_ids = range(face_idx_ctr,face_idx_ctr+len(plane_sg.face_list))
                face_idx_ctr += len(plane_sg.face_list)
                new_reg.set_region_faces(obj_mn_seg_plane.data, face_ids)

                # Prevent double counting
                plane_sides_ids_done.append(plane_sg.sides_ids)

    # Update (but dont validate because who knows)
    obj_mn_seg_plane.data.update()

    # Deselect
    obj_mn_seg_plane.select = False

    # Time
    t_st.append(time.time())
    print("> Created segment plane: time: " + str(t_st[-1]-t_st[-2]))


    ###
    # Fix normals
    ###

    ###
    # Surface
    ###

    # Set active
    context.scene.objects.active = obj_mn_surface_plane
    obj_mn_surface_plane.select = True

    # Edit mode
    bpy.ops.object.mode_set(mode='EDIT')

    # Go through all the regions in the object
    for reg in obj_mn_surface_plane.mcell.regions.region_list:
        # Select region
        reg.select_region_faces(context)

        # Fix normals
        bpy.ops.mesh.normals_make_consistent(inside=False)

        # Deselect all
        bpy.ops.mesh.select_all(action='DESELECT')

    # Object mode
    bpy.ops.object.mode_set(mode='OBJECT')

    # Deselect
    obj_mn_surface_plane.select = False

    ###
    # Segments
    ###

    # Set active
    context.scene.objects.active = obj_mn_seg_plane
    obj_mn_seg_plane.select = True
    
    # Edit mode
    bpy.ops.object.mode_set(mode='EDIT')
    
    # Go through all the regions in the object
    for reg in obj_mn_seg_plane.mcell.regions.region_list:
        # Select region
        reg.select_region_faces(context)
        
        # Fix normals
        bpy.ops.mesh.normals_make_consistent(inside=False)
        
        # Check that we are pointing toward the bigger index
        reg_name = reg.name
        id_min_min = int(reg_name[3:5])
        id_min_max = int(reg_name[6:8])
        id_max_min = int(reg_name[20:22])
        id_max_max = int(reg_name[23:25])
        if id_min_min == id_max_min and id_min_max == id_max_max:
            # Segment boundary
            sc_pts = mn_section_dict[(id_min_min,id_min_max)].sc_pts
            vec_check = sc_pts[1]-sc_pts[0]
        else:
            # Section boundary
            all_ids = [id_min_min, id_min_max, id_max_min, id_max_max]
            shared_id = list(set([item for item in all_ids if all_ids.count(item) == 2]))[0]
            uniq_ids = [item for item in all_ids if all_ids.count(item) == 1]
            max_uniq_id = max(uniq_ids)
            min_id = min(shared_id,max_uniq_id)
            max_id = max(shared_id,max_uniq_id)
            sc_pts = mn_section_dict[(min_id,max_id)].sc_pts
            vec_check = sc_pts[1]-sc_pts[0]

        # Finally, check any face - dot product should be positive!
        for face in obj_mn_seg_plane.data.polygons:
            if face.select == True:
                fSel = face.vertices
                break

        v_fSel = [obj_mn_seg_plane.data.vertices[v].co for v in fSel]
        vec_fSel = (v_fSel[1]-v_fSel[0]).cross(v_fSel[2]-v_fSel[1])
        # Dot product
        dp = vec_fSel.dot(vec_check)
        if dp < 0:
            # Flip?!
            bpy.ops.mesh.flip_normals()

        # Deselect all
        bpy.ops.mesh.select_all(action='DESELECT')

    # Object mode
    bpy.ops.object.mode_set(mode='OBJECT')

    # Deselect
    obj_mn_surface_plane.select = False

    print("> Finished: f_compartmentize_tet")
    print("> Time: " + str(t_st[-1] - t_st[0]))
def f_surface_sections(context, swc_filepath):

    print("> Running: f_surface_sections")

    # Get the object
    ob_list = context.selected_objects

    if len(ob_list) != 1:
        raise TypeError("Please select only one object.")

    ob = ob_list[0]

    # Get data from the SWC file
    pt_connect, vert_co_list, vert_r_list = get_connections(swc_filepath)

    # Construct the connectivity allowed
    conn_dict = construct_connectivity(pt_connect)

    # Construct dividing planes
    normal_dict = construct_dividing_plane_normals(pt_connect, vert_co_list)

    # List of vertices that are endpoints
    endpoint_list = []
    for i_pt_0, conn_pts in enumerate(pt_connect):
        i_pt = i_pt_0 + 1
        if len(conn_pts) == 1:
            endpoint_list.append(i_pt)

    # List of sections
    sc_list = []
    # Go through all the points
    for i_pt, conn_pts in enumerate(pt_connect):
        for conn_pt in conn_pts:
            # Check against duplicates
            if conn_pt > i_pt + 1:
                sc_list.append((i_pt + 1, conn_pt))

    # ORDER the sections by average radius of the two vertices, in decreasing order
    # That is, assign faces to large radius sections first, as these deserve "more" faces
    sc_r_list = []
    for v_pair in sc_list:
        r_ave = 0.5 * (vert_r_list[v_pair[0] - 1] + vert_r_list[v_pair[1] - 1])
        sc_r_list.append(r_ave)

    # Zipped sort
    sc_r_list, sc_list = (list(t) for t in zip(
        *sorted(zip(sc_r_list, sc_list), reverse=True)))

    # Create a list of face indexes in ob.data.polygons that need to be assigned to sections
    # As faces are assigned they are removed from this list
    face_idx_list = list(range(0, len(ob.data.polygons)))

    # zero vector
    zv = Vector([0.0, 0.0, 0.0])

    # Store the face idxs assigned to each section
    sc_face_dict = {}

    # Store the vertices that make up the border loop around each section
    sc_brdr_vert_vict = {}

    # Go through every section and assign faces to it
    for i_ctr, sc in enumerate(sc_list):
        print("Assigning faces for section " + str(sc) + " (" +
              str(i_ctr + 1) + "/" + str(len(sc_list)) + ")...")

        # Face selection mode
        context.tool_settings.mesh_select_mode = (False, False, True)

        ###
        # Step 1: Disregard faces that violate our bordering condition from the SWC file
        ###

        # Disregard list of faces that include these vertices
        vert_disregard_list = []

        # Go through all existing sections
        for sc_existing, vert_brdr_list in sc_brdr_vert_vict.items():

            # Is our current section allowed to border this one?
            if not sc_existing in conn_dict[sc]:

                # No, it isn't allowed to border this one!

                # Store to disregard
                vert_disregard_list += vert_brdr_list

        # Remove duplicates (is this necessary or superfluous?)
        vert_disregard_list = list(set(vert_disregard_list))

        ###
        # Step 2: find faces that are possible candidates for belonging to this section
        ###

        # Coordinate of vertices of this section
        v_co_0 = vert_co_list[sc[0] - 1]
        v_co_1 = vert_co_list[sc[1] - 1]

        # Max dist that faces are allowed to be away from either vertex
        max_dist = 1.5 * (v_co_1 - v_co_0).length

        # Get all the planes from this section, that occur at this vertex
        plane_normals_st = []
        for i_vert in [0, 1]:
            normal_tmp = normal_dict[sc[i_vert]]
            if not sc[(i_vert + 1) % 2] in normal_tmp:
                # This vertex has no other sections that connect to it!
                # Skip and check the next section
                plane_normals_st.append([])
            else:
                plane_normals_st.append(normal_tmp[sc[(i_vert + 1) % 2]])

        # First find all faces that are within max_dist
        tri_list = []
        for f in face_idx_list:

            # Distances from each vert
            ctr = ob.data.polygons[f].center

            disps = []
            if not sc[1] in endpoint_list:
                disps.append(ctr - v_co_0)
            else:
                disps.append(zv)  # Free pass for endpoint
            if not sc[0] in endpoint_list:
                disps.append(ctr - v_co_1)
            else:
                disps.append(zv)  # Free pass for endpoint

            # Check that distances are allowed
            if disps[0].length <= max_dist and disps[1].length <= max_dist:

                # Does this face share vertices with any of the illegal vertices? If so, disregard
                vert_f_idxs = list(ob.data.polygons[f].vertices)
                if not vert_f_idxs[
                        0] in vert_disregard_list and not vert_f_idxs[
                            1] in vert_disregard_list and not vert_f_idxs[
                                2] in vert_disregard_list:

                    # This is not sufficient - this just indicates that this face lies in the intersection region of two spheres!
                    # Now use the normals of the planes that define the regions to further constrain which elements are allowed

                    # Flag
                    COUNT_FACE = True

                    # Check each vertex
                    for i_vert in [0, 1]:

                        # But don't check the endpoints
                        if not sc[i_vert] in endpoint_list:

                            # Check all the normals
                            for normal in plane_normals_st[i_vert]:

                                if normal.dot(disps[i_vert]) < 0:
                                    COUNT_FACE = False
                                    break

                            # Don't keep search for this face if we already violated one of the vertices
                            # i.e. break out of the i_vert = [0,1] loop
                            if COUNT_FACE == False:
                                break

                    # Append
                    if COUNT_FACE == True:
                        tri_list.append(f)

        ###
        # Step 3: Proceed using the "select more" = bpy.ops.mesh.select_more() function
        ###

        # First set the mode
        bpy.ops.object.mode_set(mode='EDIT')
        # Face selection mode
        context.tool_settings.mesh_select_mode = (False, False, True)
        # Deselect all
        bpy.ops.mesh.select_all(action='DESELECT')
        # Must be in object mode to select faces!
        bpy.ops.object.mode_set(mode='OBJECT')

        # Initially select the faces in tri_list which are closest to each of the vertices
        min_f0 = -1
        dist_f0 = -1
        min_f1 = -1
        dist_f1 = -1
        for f in tri_list:
            dist_v0 = (ob.data.polygons[f].center - v_co_0).length
            if dist_v0 < dist_f0 or dist_f0 == -1:
                min_f0 = f
                dist_f0 = dist_v0
            dist_v1 = (ob.data.polygons[f].center - v_co_1).length
            if dist_v1 < dist_f1 or dist_f1 == -1:
                min_f1 = f
                dist_f1 = dist_v1

        ob.data.polygons[min_f0].select = True
        ob.data.polygons[min_f1].select = True

        # Search for faces that belong to this section

        # Init list of faces that belong to this section
        sc_face_dict[sc] = []
        # Init list of indexes to delete from the face_idx_list to prevent double checking
        delete_list = []

        SEARCH = True
        while SEARCH:

            # Use the select more function
            # Must be in edit mode to use select more!
            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_more()
            bpy.ops.object.mode_set(mode='OBJECT')

            # Check each of the possible faces if it is selected
            new_faces = []
            delete_tri_list = []

            # Fast but worse
            '''
            for i_f,f in enumerate(tri_list):
                if ob.data.polygons[f].select == True:
            '''
            # Slower but better
            for f, f_face in enumerate(ob.data.polygons):
                if f_face.select == True:
                    if f in tri_list:
                        i_f = tri_list.index(f)
                        # Add it as a valid face
                        new_faces.append(f)
                        # Store for deletion from tri_list to prevent double counting at next iteration
                        delete_tri_list.append(i_f)
                    else:
                        # Turn off the face selection so that we do not continue to "grow" in this direction using the select more function
                        f_face.select = False

            # Check that there was something new added
            if len(new_faces) == 0:
                SEARCH = False
                break

            # Delete triangles from tri_list to prevent double counting in the future (+ faster!)
            delete_tri_list.reverse()
            for i_f in delete_tri_list:
                del tri_list[i_f]

            # Add the new faces
            for f in new_faces:
                sc_face_dict[sc].append(f)
                delete_list.append(face_idx_list.index(f))

        # Select the region
        '''
        # First set the mode
        bpy.ops.object.mode_set(mode='EDIT')
        # Face selection mode
        context.tool_settings.mesh_select_mode = (False, False, True)
        # Deselect all
        bpy.ops.mesh.select_all(action='DESELECT')
        # Must be in object mode to select faces!
        bpy.ops.object.mode_set(mode='OBJECT')
        # Select
        for t in sc_face_dict[sc]:
            ob.data.polygons[t].select = True
        bpy.ops.object.mode_set(mode='EDIT')
        '''

        # Do the deletion
        delete_list.sort()
        delete_list.reverse()
        for i_f in delete_list:
            del face_idx_list[i_f]

        ###
        # Step 4 - Get the vertices that make up the border of this section
        ###

        # First deselect all
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_all(action='DESELECT')
        bpy.ops.object.mode_set(mode='OBJECT')

        # Next select the faces that belong to this section
        for f in ob.data.polygons:
            if f.index in sc_face_dict[sc]:
                f.select = True

        # Convert to edge loop
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.region_to_loop()

        # Get the edges
        bpy.ops.object.mode_set(mode='OBJECT')
        edge_loop = [i.index for i in ob.data.edges if i.select]

        # Convert to vertices and store
        sc_brdr_vert_vict[sc] = [
            item for edge in edge_loop for item in ob.data.edges[edge].vertices
        ]

    ###
    # Finished assigning faces to sections!
    ###

    ###
    # Sanity check: are all faces assigned?
    ###

    f_ctr = 0
    for f_list in sc_face_dict.values():
        f_ctr += len(f_list)

    print("Number of faces to assign: " + str(len(ob.data.polygons)))
    print("Number of faces assigned: " + str(f_ctr))

    if len(ob.data.polygons) != f_ctr:
        print(
            "WARNING! There are faces that have not been assigned to regions.")

    ###
    # Turn the dictionary into MCell regions
    ###

    print("Adding regions to MCell...")

    # Ensure object is active
    context.scene.objects.active = ob
    ob.select = True

    # First add the object to MCell
    preserve_selection_use_operator(bpy.ops.mcell.model_objects_add, ob)

    # Add the sections as regions
    existing_reg_names = [item.name for item in ob.mcell.regions.region_list]
    for sc_id, f_list in sc_face_dict.items():

        # Region name
        reg_name = "sc_%02d_%02d" % sc_id

        # Check that the region does not exist
        if reg_name in existing_reg_names:
            # Get region
            new_reg = ob.mcell.regions.region_list[reg_name]
            # Clear it
            new_reg.reset_region(context)
        else:
            # Make region
            ob.mcell.regions.add_region_by_name(context, reg_name)
            # Get region
            new_reg = ob.mcell.regions.region_list[reg_name]

        # Assign faces
        new_reg.set_region_faces(ob.data, f_list)

    # Update (but dont validate because who knows)
    ob.data.update()

    print("> Finished: f_surface_sections")