class ParticleManager: _particle_types = {} @classmethod def register_particle_type(cls, type, name): cls._particle_types[name] = type @classmethod def unregister_particle_type(cls, name): del cls._particle_types[name] def __init__(self, context): self.obj = context.active_object self.particles = [] self.edges = [] self.draw_layer = grease_draw.StrokeLayer(context) self.kd_tree = None def create_particle(self, type, location, radius=0.03): if type in self._particle_types: p = self._particle_types[type](radius, location, self) self.particles.append(p) return p else: raise KeyError("No such particle registered: %s" % type) def build_kd_tree(self): self.kd_tree = KDTree(len(self.particles)) for index, particle in enumerate(self.particles): self.kd_tree.insert(particle.location, index) self.kd_tree.balance() def nearest_n_particles(self, location, n): for location, index, distance in self.kd_tree.find_n(location, n): particle = self.particles[index] yield particle, distance def nearest_n_tag_particles(self, location, n, tag): def tag_filter(i): return self.particles[i].tag is tag for location, index, distance in self.kd_tree.find_n( location, n, filter=tag_filter): particle = self.particles[index] yield particle, distance def step(self, speed): for particle in self.particles: particle.step(speed) def sample_obj(self, location): return self.obj.closest_point_on_mesh(location)
def connect_verts(bm, z_idx, v1, verts2_bm, connections, max_rho): tree = KDTree(len(verts2_bm)) for i, v2 in enumerate(verts2_bm): tree.insert(v2.co, i) tree.balance() for co, i, dist in tree.find_n(v1.co, connections): if dist <= max_rho: v2 = verts2_bm[i] if bm.edges.get((v1, v2)) is None: bm.edges.new((v1, v2)) bm.edges.ensure_lookup_table()
def repeal_particles(self, iterations=20, factor=0.01): particles = list(self.particles) tree = KDTree(len(particles)) for index, particle in enumerate(particles): tree.insert(particle.co, index) tree.balance() for i in range(iterations): new_tree = KDTree(len(self.particles)) for index, particle in enumerate(particles): if particle.tag in {"SHARP", "GREASE"}: continue d = Vector() for loc, other_index, dist in tree.find_n(particle.co, 3): if dist == 0: continue other = particles[other_index] vec = particle.co - other.co d += (vec / (dist ** 3)) if not self.triangle_mode: u = particle.dir v = u.cross(particle.normal) for vec in (u + v, u - v, -u + v, -u - v): vec *= particle.radius vec += other.co vec -= particle.co dist = vec.length d -= vec * 0.3 / (dist ** 3) d.normalize() location, normal, dir, s, c = self.field.sample_point(particle.co + (d * factor * particle.radius)) if location: particle.co = location particle.normal = normal self.grid.update(particle) particle.dir = dir new_tree.insert(particle.co, index) new_tree.balance() tree = new_tree yield i
def unique_points(points, eps=1e-4): kdt = KDTree(len(points)) for i, p in enumerate(points): kdt.insert(p, i) kdt.balance() unique = [] repeating = [] mask = [] for p in points: found = kdt.find_n(p, 2) if len(found) > 1: loc, idx, distance = found[1] ok = distance > eps mask.append(ok) if ok: unique.append(p) else: repeating.append(p) return mask, unique, repeating
def finish(self, context): #ray cast the entire grid into if 'Posterior Plane' in bpy.data.objects: Plane = bpy.data.objects['Posterior Plane'] Plane.hide = False else: me = bpy.data.meshes.new('Posterior Plane') Plane = bpy.data.objects.new('Posterior Plane', me) context.scene.objects.link(Plane) pbme = bmesh.new() pbme.verts.ensure_lookup_table() pbme.edges.ensure_lookup_table() pbme.faces.ensure_lookup_table() bmesh.ops.create_grid(pbme, x_segments = 200, y_segments = 200, size = 39.9) pbme.to_mesh(Plane.data) pt, pno = calculate_plane(self.crv.b_pts) if self.splint.jaw_type == 'MANDIBLE': Zw = Vector((0,0,-1)) Xw = Vector((1,0,0)) Yw = Vector((0,-1,1)) else: Zw = Vector((0,0,1)) Xw = Vector((1,0,0)) Yw = Vector((0,1,0)) Z = pno Z.normalize() if Zw.dot(Z) < 0: Z *= -1 Y = Z.cross(Xw) X = Y.cross(Z) R = Matrix.Identity(3) #make the columns of matrix U, V, W R[0][0], R[0][1], R[0][2] = X[0] ,Y[0], Z[0] R[1][0], R[1][1], R[1][2] = X[1], Y[1], Z[1] R[2][0] ,R[2][1], R[2][2] = X[2], Y[2], Z[2] R = R.to_4x4() T = Matrix.Translation(pt - 5 * Z) Plane.matrix_world = T * R pmx = Plane.matrix_world ipmx = pmx.inverted() bme_pln = bmesh.new() bme_pln.from_mesh(Plane.data) bme_pln.verts.ensure_lookup_table() bme_pln.edges.ensure_lookup_table() bme_pln.faces.ensure_lookup_table() bvh = BVHTree.FromBMesh(bme_pln) #we are going to raycast the user world coordinate points #into a grid, and identify all points in the grid from the local Z direction #Then we will store the local location of the user picked coordinate in a dictionary key_verts = {} for loc in self.crv.b_pts: res = bvh.ray_cast(ipmx * loc, -Z, 30) if res[0] != None: f = bme_pln.faces[res[2]] for v in f.verts: key_verts[v] = ipmx * loc v.select_set(True) continue res = bvh.ray_cast(ipmx * loc, Z, 30) if res[0] != None: f = bme_pln.faces[res[2]] for v in f.verts: key_verts[v] = ipmx * loc v.select_set(True) continue #bme_pln.to_mesh(Plane.data) #bme_pln.free() #return kdtree = KDTree(len(key_verts)) for v in key_verts.keys(): kdtree.insert(v.co, v.index) kdtree.balance() #raycast the shell if we can raycast_shell = False if 'Splint Shell' in bpy.data.objects: shell = bpy.data.objects.get('Splint Shell') bvh_shell = BVHTree.FromObject(shell, context.scene) mx_shell = shell.matrix_world imx_shell = mx_shell.inverted() Z_shell = imx_shell.to_3x3()*Z raycast_shell = True right_side = set() left_side = set() ray_casted = set() to_delete = set() for v in bme_pln.verts: if v in key_verts: v.co[2] = key_verts[v][2] if v.co[1] > 0: left_side.add(v) else: right_side.add(v) continue results = kdtree.find_range(v.co, .5) if len(results): N = len(results) r_total = 0 v_new = Vector((0,0,0)) for res in results: r_total += 1/res[2] v_new += (1/res[2]) * key_verts[bme_pln.verts[res[1]]] v_new *= 1/r_total v.co[2] = v_new[2] if v.co[1] > 0: left_side.add(v) else: right_side.add(v) continue results = kdtree.find_range(v.co, 6) if len(results): N = len(results) r_total = 0 v_new = Vector((0,0,0)) for res in results: r_total += (1/res[2])**2 v_new += ((1/res[2])**2) * key_verts[bme_pln.verts[res[1]]] v_new *= 1/r_total v.co[2] = v_new[2] if v.co[1] > 0: left_side.add(v) else: right_side.add(v) continue loc, no, index, d = bvh_shell.ray_cast(imx_shell * pmx * v.co, Z_shell) if loc: ray_casted.add(v) results = kdtree.find_n(v.co, 4) N = len(results) r_total = 0 v_new = Vector((0,0,0)) for res in results: r_total += (1/res[2])**2 v_new += ((1/res[2])**2) * key_verts[bme_pln.verts[res[1]]] v_new *= 1/r_total v.co[2] = v_new[2] continue total_verts = ray_casted | left_side | right_side ant_left = max(left_side, key = lambda x: x.co[0]) ant_right = max(right_side, key = lambda x: x.co[0]) new_verts = set() dilation_verts = set() for v in total_verts: for ed in v.link_edges: v_new = ed.other_vert(v) if v_new in total_verts or v_new in new_verts: continue else: new_verts.add(v_new) print('adding %i new verts' % len(new_verts)) total_verts.update(new_verts) dilation_verts.update(new_verts) #for v in ray_casted: # if v.co[1] > 0: # if v.co[0] > ant_left.co[0]: # to_delete.add(v) # else: # if v.co[0] > ant_right.co[0]: # to_delete.add(v) #print('added %i ray_casted' % len(ray_casted)) #total_verts = ray_casted | left_side | right_side #total_verts.difference_update(to_delete) #new_verts = set() #for v in total_verts: # for ed in v.link_edges: # v_new = ed.other_vert(v) # if v_new in total_verts: continue # if v_new.co[1] > 0 and v_new.co[0] < ant_left.co[0]: # if v in to_delete: # new_verts.add(v) # if v_new.co[1] <= 0 and v_new.co[0] < ant_right.co[0]: # if v in to_delete: # new_verts.add(v) #to_delete.difference_update(new_verts) #print('adding %i new verts' % len(new_verts)) for j in range(0,3): newer_verts = set() for v in new_verts: for ed in v.link_edges: v_new = ed.other_vert(v) if v_new in total_verts or v_new in newer_verts: continue newer_verts.add(v_new) total_verts.update(newer_verts) dilation_verts.update(newer_verts) new_verts = newer_verts to_delete = set(bme_pln.verts[:]) - total_verts #filter out anteior dilation for v in dilation_verts: if v.co[1] > 0 and v.co[0] > ant_left.co[0]: to_delete.add(v) continue if v.co[1] <= 0 and v.co[0] > ant_right.co[0]: to_delete.add(v) continue results = kdtree.find_n(v.co, 4) N = len(results) r_total = 0 v_new = Vector((0,0,0)) for res in results: r_total += (1/res[2])**2 v_new += ((1/res[2])**2) * key_verts[bme_pln.verts[res[1]]] v_new *= 1/r_total v.co[2] = v_new[2] #filter out anteior dilation for v in ray_casted: if v.co[1] > 0 and v.co[0] > ant_left.co[0]: to_delete.add(v) continue if v.co[1] <= 0 and v.co[0] > ant_right.co[0]: to_delete.add(v) continue bmesh.ops.delete(bme_pln, geom = list(to_delete), context = 1) bme_pln.to_mesh(Plane.data) Plane.data.update() smod = Plane.modifiers.new('Smooth', type = 'SMOOTH') smod.iterations = 5 smod.factor = 1 self.splint.ops_string += 'Mark Posterior Cusps:'
class ParticleManager: def __init__(self, obj): self.particles = [] self.obj = obj self.field = vector_fields.FrameField(obj) self.inv_mat = obj.matrix_world.inverted() self.bm = self.field.bm self.kd_tree = KDTree(0) self.kd_tree.balance() self.draw_obj = draw_3d.DrawObject() self.triangle_mode = False def build_field(self, context, use_gp, x_mirror): self.field.build_major_curvatures() frame = get_gp_frame(context) if frame: self.field.from_grease_pencil(frame, mat=self.inv_mat, x_mirror=x_mirror) self.field.marching_growth() self.field.smooth(2) else: self.field.erase_part(2) self.field.marching_growth() self.field.smooth() def build_kdtree(self): tree = KDTree(len(self.particles)) for id, p in enumerate(self.particles): tree.insert(p.location, id) tree.balance() self.kd_tree = tree def initialize_particles_from_gp(self, resolution, adaptive, context): scale = max(self.obj.dimensions) / max(self.obj.scale) target_resolution = scale / resolution frame = get_gp_frame(context) created_particles = 0 if not frame: return created_particles for stroke in frame.strokes: co = self.inv_mat * stroke.points[0].co last_particle = self.create_particle(Partile, co) last_particle.target_resolution = target_resolution last_particle.radius = target_resolution / ( last_particle.last_hit.curvature * adaptive + (1 - adaptive)) last_particle.adaptive = adaptive created_particles += 1 for point in stroke.points: co = self.inv_mat * point.co if (co - last_particle.location ).length >= last_particle.radius * 2: last_particle = self.create_particle(Partile, co) last_particle.target_resolution = target_resolution last_particle.radius = target_resolution / ( last_particle.last_hit.curvature * adaptive + (1 - adaptive)) last_particle.adaptive = adaptive created_particles += 1 return created_particles def initialize_from_features(self, verts, resolution=20, adaptive=0, count=50): scale = max(self.obj.dimensions) / max(self.obj.scale) target_resolution = scale / resolution verts = sorted(self.field.bm.verts, key=lambda v: self.field.sharpness_field.get( v.index, float("inf")), reverse=True) for i in range(min(count, len(self.field.bm.verts))): vert = verts[i] co = vert.co.copy() p1 = self.create_particle(Partile, co) p1.radius = target_resolution p1.target_resolution = target_resolution p1.adaptive = adaptive def initialize_from_verts(self, verts, adaptive): for vert in verts: p = self.create_particle(Partile, vert.co) p.adaptive = adaptive def initialize_grid(self, verts, resolution=20, use_x_mirror=True, adaptive=0): particle_locations = set() scale = max(self.obj.dimensions) target_resolution = 1 / ((1 / scale) * resolution) for vert in verts: co = vert.co.copy() co /= scale co *= resolution x = int(co.x) y = int(co.y) z = int(co.z) if use_x_mirror: if x > 0: particle_locations.add((x, y, z)) else: particle_locations.add((x, y, z)) for location in particle_locations: co = Vector(location) co *= scale co /= resolution hit = self.sample_surface(co) p1 = self.create_particle(Partile, hit.co) p1.adaptive = adaptive p1.target_resolution = target_resolution if use_x_mirror: hit.co.x *= -1 p2 = self.create_particle(Partile, hit.co) p2.adaptive = adaptive p2.target_resolution = target_resolution p1.counter_pair, p2.counter_pair = p2, p1 def mirror_particles(self, any_side=False): new_particles = [] for particle in self.particles: if particle.location.x > particle.radius or any_side: co = particle.location.copy() co.x *= -1 p1 = particle p2 = Partile(co, self) p2.radius = p1.radius p2.tag = p1.tag p2.adaptive = p1.adaptive p2.target_resolution = p1.target_resolution p1.counter_pair, p2.counter_pair = p2, p1 new_particles.append(p2) new_particles.append(p1) elif -particle.radius * 0.5 < particle.location.x < particle.radius * 0.5: new_particles.append(particle) particle.lock_x = True self.particles = new_particles self.build_kdtree() def create_particle(self, type, location, prepend=False): p = type(location, self) if not prepend: self.particles.append(p) else: self.particles.insert(0, p) return p def remove_particle(self, particle): self.particles.remove(particle) def step( self, speed, ): new_tree = KDTree(len(self.particles)) self.draw_obj.commands.clear() for id, particle in enumerate(self.particles): particle.step(speed) particle.draw() new_tree.insert(particle.location, id) new_tree.balance() self.kd_tree = new_tree def spread_step(self): count = 0 new_particles = [] self.draw_obj.commands.clear() for particle in self.particles: new_particles += particle.spread() count += len(new_particles) for particle in self.particles: if not particle.tag == "REMOVE": new_particles.append(particle) self.particles = new_particles new_tree = KDTree(len(self.particles)) for id, particle in enumerate(self.particles): particle.draw() new_tree.insert(particle.location, id) new_tree.balance() self.kd_tree = new_tree return count def get_nearest(self, location, n): for location, index, dist in self.kd_tree.find_n(location, n): yield self.particles[index], dist def sample_surface(self, location): return self.field.sample_point(location) def draw(self): self.draw_obj.commands.clear() for particle in self.particles: particle.draw() def simplify_mesh(self, bm): class Ownership: def __init__(self, particle, dist): self.particle = particle self.distance = dist self.valid = False bmesh.ops.triangulate(bm, faces=bm.faces) last_edges = float("+inf") while True: edges = set() for edge in bm.edges: le = (edge.verts[0].co - edge.verts[1].co).length_squared center = edge.verts[0].co + edge.verts[1].co center /= 2 for p, dist in self.get_nearest(center, 1): if p.radius**2 < le: edges.add(edge) if not len(edges) < last_edges: break last_edges = len(edges) bmesh.ops.subdivide_edges(bm, edges=list(edges), cuts=1) bmesh.ops.triangulate(bm, faces=bm.faces) bm.faces.ensure_lookup_table() bm.verts.ensure_lookup_table() tree = KDTree(len(bm.verts)) for vert in bm.verts: tree.insert(vert.co, vert.index) tree.balance() ownership_mapping = {} ownership_validation_front = set() for vert in bm.verts: for p, dist in self.get_nearest(vert.co, 1): ownership_mapping[vert] = Ownership(p, dist) for particle in self.particles: location, index, dist = tree.find(particle.location) vert = bm.verts[index] if vert in ownership_mapping: if ownership_mapping[vert].particle == particle: ownership_mapping[vert].valid = True ownership_validation_front.add(vert) while True: new_front = set() for vert in ownership_validation_front: for edge in vert.link_edges: other_vert = edge.other_vert(vert) if other_vert not in ownership_mapping: continue if ownership_mapping[other_vert].valid: continue if other_vert in ownership_mapping: if ownership_mapping[ vert].particle is ownership_mapping[ other_vert].particle: new_front.add(other_vert) ownership_mapping[other_vert].valid = True ownership_validation_front = new_front if not new_front: break new_bm = bmesh.new() for particle in self.particles: particle.vert = new_bm.verts.new(particle.location) for face in bm.faces: connections = set() for vert in face.verts: if vert in ownership_mapping: if ownership_mapping[vert].valid: p = ownership_mapping[vert].particle connections.add(p) if len(connections) == 3: try: new_bm.faces.new( [particle.vert for particle in connections]) except ValueError: pass while True: stop = True for vert in new_bm.verts: if len(vert.link_edges) < 3: new_bm.verts.remove(vert) stop = False if stop: break bmesh.ops.holes_fill(new_bm, edges=new_bm.edges) bmesh.ops.triangulate(new_bm, faces=new_bm.faces) bmesh.ops.recalc_face_normals(new_bm, faces=new_bm.faces) if not self.triangle_mode: bmesh.ops.join_triangles(new_bm, faces=new_bm.faces, angle_face_threshold=1.0, angle_shape_threshold=3.14) return new_bm