def triangulate(self, z=0.): """Triangulate shape using ear clipping algorithm.""" model = Model() v_list = [np.array([v[0], v[1], z]) for v in self._vertices] v_list_len = len(v_list) while v_list_len > 3: for i, v in enumerate(v_list): # print(f'processing {i}, {v}') v0 = v_list[(i - 1) % len(v_list)] v1 = v_list[(i + 1) % len(v_list)] ang = angle(v1 - v, v0 - v) #first: find a triangle with a convex corner if ang < math.pi: #second: check if no other vertices are inside triangle if not any(not vm.equal(vv, v) and not vm.equal(vv, v0) and not vm.equal(vv, v1) and self.__point_in_triangle(vv, v0, v, v1) for vv in v_list): model.add_face([v0, v, v1]) v_list.pop(i) break if v_list_len == len(v_list): raise RuntimeError( 'Ear clipping failed to find vertice tripple to triangulate' ) v_list_len = len(v_list) model.add_face(v_list) model._update() return model
def __divide_all(self, vertices, triangles): # Subdivide each triangle in the old approximation and normalize # the new points thus generated to lie on the surface of the unit # sphere. # Each input triangle with vertices labelled [0,1,2] as shown # below will be turned into four new triangles: # # Make new points # a = (0+2)/2 # b = (0+1)/2 # c = (1+2)/2 # 1 # /\ Normalize a, b, c # / \ # b/____\ c Construct new triangles # /\ /\ t1 [0,b,a] # / \ / \ t2 [b,1,c] # /____\/____\ t3 [a,b,c] # 0 a 2 t4 [a,c,2] v = [] for tri in triangles: v0 = vertices[tri[0]] v1 = vertices[tri[1]] v2 = vertices[tri[2]] a = vm.unit_vector((v0 + v2) * 0.5) b = vm.unit_vector((v0 + v1) * 0.5) c = vm.unit_vector((v1 + v2) * 0.5) v += [v0, b, a, b, v1, c, a, b, c, a, c, v2] return v, np.arange(len(v)).reshape((-1, 3))
def cost_func(path): """ This cost function just returns the linear distance between the nodes (assumed to be spatial nodes)""" cost = 0 for i in range(1, len(path)): cost += VecMath.length( VecMath.sub(path[i - 1].position, path[i].position)) return cost
def test_get_rotation_mtx(self): a = np.array([1., 0., 0.]) b = np.array([0., 1., 0.]) rot = VecMath.rotate_fromto_matrix(a, b) self.assertIsNotNone(rot) a2 = rot.__matmul__(a) self.assertNotEqual(VecMath.angle_between(a, b), 0.0) self.assertEqual(VecMath.angle_between(a2, b), 0.0)
def test_get_rotation_mtx_equal_vecs(self): """ edge case: would normaly break rot mtx. use identity mtx instead """ a = np.array([1., 0., 0.]) b = np.array([1., 0., 0.]) rot = VecMath.rotate_fromto_matrix(a, b) self.assertIsNotNone(rot) a2 = rot.__matmul__(a) self.assertEqual(VecMath.angle_between(a2, b), 0.0)
def is_equal(self, other): if isinstance(other, Edge): return (self.v0_id == other.v0_id and self.v1_id == other.v1_id) or (self.v1_id == other.v0_id and self.v0_id == other.v1_id) if isinstance(other, list): return (vm.equal(self._v0, other[0]) and vm.equal( self._v1, other[1])) or (vm.equal(self._v1, other[0]) and vm.equal(self._v0, other[1])) return False
def test_dist_point_to_line2(self): line_p = np.array([0., 0., 0.]) line_dir = np.array([1., 1., 1.]) dist = VecMath.dist_point_to_line(line_p, line_dir, np.array([0., .2, .5])) self.assertEqual(dist, 0.35590260840104376)
def test_dist_point_to_line(self): line_p = np.array([0., 0., 0.]) line_dir = np.array([1., 0., 0.]) dist = VecMath.dist_point_to_line(line_p, line_dir, np.array([-1., 2., 0.])) self.assertEqual(dist, 2.)
def embed_label(model, face, side, label, glyph): # transform face to shape (into which we will embed the label) transform = MtxMath.conv_to_euclid(VecMath.rotate_fromto_matrix(face._norm, np.array([0., 0., 1.]))) vertices = [transform * euclid.Point3(v[0], v[1], v[2]) for v in face._vertices] target_path = svg.Path.from_shape(svg.Shape([np.array([v[0], v[1]]) for v in vertices])) # render label into path label_path = svg.Path.combine([(glyph[int(c)], np.array([1., 0.])) for c in label]) # move/scale label to fit into target triangle p, s, r = fit_rect_in_triangle(vertices, side, label_path._bbox) label_path.scale(s[0], s[1]) pivot = label_path._bbox._center+np.array([0, label_path._bbox._size[1]*-.5]) label_path.rotate(r, anchor=pivot) label_path.translate(p-pivot) # embed the label embedded_model = target_path.embed([label_path.triangulate(z=vertices[0].z)], group_name='svg', z=vertices[0].z) embedded_model.transform(transform.inverse()) # (optional) extrude the label embedded_model.get_group('svg')._material._diffuse = [1., 0., 0.] embedded_model.extrude(-.5, faces=embedded_model.get_group('svg')._faces) # replace initial face with new 'labeled face' model.remove_face(face) model.merge(embedded_model) return embedded_model
def angle_between(self, other): """ Return the angle in radians between this plane and another plane or vector """ if isinstance(other, Plane): other_v = other._norm elif len(other) == 3: # elif isinstance(other, np.array) and len(other) == 3: other_v = other return vm.angle_between(self._norm, other_v)
def calculate_vertice_norms(self): self._vertices_norm = [] for v_id in range(len(self._vertices)): v_norm = np.array([0.,0.,0.]) for face in self._faces: if face.contains_vertex(v_id): v_norm += face._norm self._vertices_norm.append(VecMath.unit_vector(v_norm))
def get_path_from_face(face): transform = MtxMath.conv_to_euclid( VecMath.rotate_fromto_matrix(face._norm, np.array([0., 0., 1.]))) vertices = [ transform * euclid.Point3(v[0], v[1], v[2]) for v in face._vertices ] path = svg.Path.from_shape( svg.Shape([np.array([v[0], v[1]]) for v in vertices])) return path, transform
def get_first_plane(vertex, faces): """ First plane: Find the plane which center is closest to the vertex """ min_dist = sys.float_info.max min_idx = None for idx, face in enumerate(faces): dist = vm.dist_point_to_point(vertex, face._center) if dist < min_dist: min_idx = idx min_dist = dist # print(f'ref_plane #1: {min_idx} -> ref: {faces[min_idx]._id}') return min_idx
def createJoints(self): if not self.m_userJoints: self.m_bindJoints = [] startPos = cmds.xform(self.m_parent1, q=1, t=1, ws=1) endPos = cmds.xform(self.m_parent2, q=1, t=1, ws=1) stepVec = vec.divide( vec.subtract(endPos, startPos), self.m_numJoints - 1 ) currentPos = startPos for i in range(self.m_numJoints): newJoint = self.m_name+"_"+str(i)+"_BIND_JNT" newJoint = cmds.joint(n=newJoint, p=currentPos) cmds.setAttr("%s.radius" %(newJoint), 0.1) self.m_bindJoints.append(newJoint) currentPos = vec.add(currentPos, stepVec) #fix orientations cmds.joint( self.m_bindJoints[0], e=1, oj="xyz", sao = "yup", ch=1, zso=1 ) else: # Duplicate joints and rename newJoints = cmds.duplicate(self.m_userJoints[0], rc=1) for i in range(len(newJoints)): newJoint = "%s_%d_BIND_JNT" %(self.m_name, i) newJoints[i] = cmds.rename(newJoints[i], newJoint) self.m_bindJoints = newJoints #Put it in the right group cmds.parent(self.m_bindJoints[0], self.m_group) rc.addToLayer(self.m_sceneData, "ref", self.m_bindJoints[0]) #Strip sets for joint in self.m_bindJoints: rc.stripSets(joint) #Add all except first and last to bind set for joint in self.m_bindJoints[:-1]: rc.addToSet(self.m_sceneData, "bind", joint)
def simplify(self, epsilon=.00001): face_count_before = len(self._faces) for face in list(self._faces): # very important to copy the list first as we modify while iteration is not done yet for neighbour in face._neighbour_faces: if VecMath.angle_between(face._norm, neighbour._norm) < epsilon: v_ids = face.merge_face_vids(neighbour) # as we are modifying the list while iterating though it we need this test to check if the consolidation of this tri was already done if v_ids and len(face._vertex_ids) < len(v_ids): self.remove_face(face) self.remove_face(neighbour) self.add_face([self._vertices[id] for id in v_ids], group=face._group, tags=set(face._tags + neighbour._tags)) self._update() print(f'Simplify: Face count {face_count_before} before -> {len(self._faces)} after')
def write(model, filename, orientation_vec=None): """Rotate and center object to lay flat on the heat-bead""" model._update() if orientation_vec is None: transform = euclid.Matrix4.new_translate(-model._center[0], -model._center[1], -model._center[2]) else: transform = MtxMath.conv_to_euclid( VecMath.rotate_fromto_matrix(orientation_vec, np.array([0., 0., -1.]))) n = transform * euclid.Point3(-model._center[0], -model._center[1], -model._center[2]) transform = euclid.Matrix4.new_translate(n.x, n.y, n.z) * transform write_obj(model, filename, transform=transform)
def test_angle_between(self): self.assertEqual(VecMath.angle_between((1, 0, 0), (0, 1, 0)), 1.5707963267948966) self.assertEqual(VecMath.angle_between((1, 0, 0), (1, 0, 0)), 0.0) self.assertEqual(VecMath.angle_between((1, 0, 0), (-1, 0, 0)), 3.141592653589793)
model.remove_face(face) model.merge(embedded_model) return embedded_model if __name__ == "__main__": radius = 100. sphere = primitives.Sphere(radius) obj_model = sphere.triangulate(recursion_level=2) for iter in range(0,4): print(f'noise iteration({iter})') for idx, vertex in enumerate(obj_model._vertices): l = min([edge.length for edge in obj_model.get_edges_with_vertex(idx)]) * .1 rnd_d = get_random_norm() dir = VecMath.unit_vector(vertex) transf = MtxMath.conv_to_euclid(VecMath.rotate_fromto_matrix(np.array([0., 0., 1.]), dir)) tv = transf * (rnd_d*euclid.Vector3(l,l,10)) vertex += tv # sphere_model.triangulate() ObjExporter.write(obj_model, f'./export/_sphere_noise.obj') print(f'Faces: {len(obj_model._faces)}') print(f'Vertices: {len(obj_model._vertices)}') bbox_size = obj_model._size print(f'Boundingbox: [{bbox_size[0]}, {bbox_size[1]}, {bbox_size[2]}]') target_lid_size = 100. #mm^2 faceted_model = Model()
for face in dt.exportTriangles(): obj_model.add_face([vertices3d[face[0]], vertices3d[face[1]], vertices3d[face[2]]]) # calculate heights depending on point density obj_model._update() v_heights = [] for idx, vertex in enumerate(obj_model._vertices): vertex_conntected = [] connected_faces = obj_model.get_faces_with_vertex(idx) for face in connected_faces: for edge in face._edges: if edge.v0_id == idx: vertex_conntected.append(tuple([idx, edge.v1_id])) elif edge.v1_id == idx: vertex_conntected.append(tuple([idx, edge.v0_id])) dist = 0 for c in vertex_conntected: dist = dist + VecMath.dist_point_to_point(obj_model._vertices[c[0]], obj_model._vertices[c[1]]) v_heights.append(dist / float(len(vertex_conntected))) min_z = min(v_heights) max_z = max(v_heights) f = 1. / (max_z) for idx, vertex in enumerate(obj_model._vertices): obj_model._vertices[idx][VecMath._Z] = (1. - ((v_heights[idx]-min_z) / max_z)) * 100 print(obj_model._vertices[idx][VecMath._Z]) # export to obj file obj_model._update() ObjExporter.write(obj_model, f'./export/_delanay2d.obj')
def __init__(self, graph, path=None): self.root = tk.Tk() self.root.geometry("1200x800") self.root.config(bg='white') self.canvas = tk.Canvas(width=1200, height=800) self.canvas.config(bg='white') self.radius = 15 for node in graph.nodes: pos = node.position self.canvas.create_oval(pos[0] - self.radius, pos[1] - self.radius, pos[0] + self.radius, pos[1] + self.radius, fill='red') self.canvas.create_text(pos[0], pos[1], text=str(node.id), anchor='center') for succ in node.successors: to_succ = VecMath.normal(VecMath.sub(succ.position, node.position)) arrow_start = VecMath.add(node.position, VecMath.multiply(to_succ, self.radius)) arrow_end = VecMath.sub(succ.position, VecMath.multiply(to_succ, self.radius)) self.canvas.create_line(arrow_start[0], arrow_start[1], arrow_end[0], arrow_end[1], arrow=tk.LAST) if path is not None: self.path_lines = [] for i in range(1, len(path)): a_to_b = VecMath.normal(VecMath.sub(path[i].position, path[i - 1].position)) arrow_start = VecMath.add(path[i - 1].position, VecMath.multiply(a_to_b, self.radius)) arrow_end = VecMath.sub(path[i].position, VecMath.multiply(a_to_b, self.radius)) self.path_lines.append( self.canvas.create_line(arrow_start[0], arrow_start[1], arrow_end[0], arrow_end[1], arrow=tk.LAST, dash=(2, 2), fill='yellow', width=10)) self.canvas.update() self.canvas['bg'] = 'green' self.canvas.pack() self.root.mainloop()
def test_unit_vector(self): self.assertEqual( np.linalg.norm(VecMath.unit_vector(np.array([1., 2., 3.]))), 1.)
def calculate_norm(self, vertices): self._norm = np.cross( vertices[self._vertex_ids[1]] - vertices[self._vertex_ids[0]], vertices[self._vertex_ids[2]] - vertices[self._vertex_ids[0]]) self._norm = VecMath.unit_vector(self._norm)
d = euclid.Vector3(random.uniform(0, 1), random.uniform(0, 1), 0.).normalize() # d = euclid.Vector3(0., 0., 0.) # d.x = random.uniform(-1, 1) # d.y = random.uniform(-1, 1) # d.z = random.uniform(-.1, .1) # d.z = 1. return d if __name__ == "__main__": radius = 100. sphere = primitives.Sphere(radius) sphere_model = sphere.triangulate(recursion_level=2) for iter in range(0, 4): print(f'noise iteration({iter})') for idx, vertex in enumerate(sphere_model._vertices): l = min([ edge.length for edge in sphere_model.get_edges_with_vertex(idx) ]) * .1 rnd_d = get_random_norm() dir = vm.unit_vector(vertex) transf = MtxMath.conv_to_euclid( vm.rotate_fromto_matrix(np.array([0., 0., 1.]), dir)) tv = transf * (rnd_d * euclid.Vector3(l, l, 10)) vertex += tv # sphere_model.triangulate() ObjExporter.write(sphere_model, f'./export/_sphere_noise.obj')
def from_points(cls, p0, p1, p2, norm_dir=1.): """ Create a plane from 3 points that span the plane """ return Plane(p0, vm.unit_vector(np.cross(p1 - p0, p2 - p0) * norm_dir))
def get_identical_vertices(self, other): res = [(u, v) for u, v in list( itertools.product(range(len(self._vertices)), range(len(other._vertices)))) if vm.equal(self._vertices[u], other._vertices[v])] return res
def createControls(self): #If no blend control is speified add one if not self.m_blendControl: self.m_blendControl = cmds.circle(n=self.m_name+"Blend_CTRL")[0] cmds.parent(self.m_blendControl, self.m_group) self.m_allControls.append(self.m_blendControl) rc.addToControlDict( self.m_allControls, "%s_StretchChainCtrl" %(self.m_baseName), self.m_blendControl ) cmds.addAttr( self.m_blendControl, ln=self.m_attrHeading, k=True, at = "enum", en = "---------:" ) if self.m_isAutoBend: cmds.addAttr( self.m_blendControl, ln=self.m_blendAttrName, k=1, min=0, max=1, dv=0 ) blendNodeAttr = self.m_blendControl+"."+self.m_blendAttrName oppBlendNodeAttr = rc.create1MinusNode( blendNodeAttr, self.m_name+"OppBlend_CTRL" ) self.m_controls = [] self.m_parentCtrls = [] self.m_pointCtrls = [] startPos = cmds.xform(self.m_parent1, q=1, t=1, ws=1) endPos = cmds.xform(self.m_parent2, q=1, t=1, ws=1) stepVec = vec.divide( vec.subtract(endPos, startPos), self.m_numCtrls + 1 ) currentPos = vec.add(startPos, stepVec) for i in range(self.m_numCtrls): newCtrl = self.m_name+"_"+str(i)+"_CTRL" parentCtrl = self.m_name+"_"+str(i)+"_parent_CTRL" pointCtrl = self.m_name+"_"+str(i)+"_point_CTRL" newCtrl = cmds.spaceLocator(n=newCtrl)[0] self.m_controls.append(newCtrl) cmds.parent(newCtrl, self.m_group) cmds.xform(newCtrl, t=currentPos, ws=1) newCtrlGroups = rg.add3Groups(newCtrl, ["_SDK", "_CONST", "_0"]) if self.m_isAutoBend: parentCtrl = cmds.duplicate(newCtrl, n=parentCtrl)[0] cmds.parent(parentCtrl, newCtrlGroups[2]) #cmds.setAttr(parentCtrl+".visibility", 0) pointCtrl = cmds.duplicate(newCtrl, n=pointCtrl)[0] cmds.parent(pointCtrl, newCtrlGroups[2]) #cmds.setAttr(pointCtrl+".visibility", 0) rc.addToLayer(self.m_sceneData, "hidden", [parentCtrl, pointCtrl]) #Create blend between parent and point setups blendConst = cmds.pointConstraint(parentCtrl, newCtrlGroups[1]) cmds.connectAttr(blendNodeAttr, blendConst[0]+"."+parentCtrl+"W0") blendConst = cmds.pointConstraint(pointCtrl, newCtrlGroups[1]) cmds.connectAttr(oppBlendNodeAttr, blendConst[0]+"."+pointCtrl+"W1") self.m_parentCtrls.append(parentCtrl) self.m_pointCtrls.append(pointCtrl) grandParent = cmds.listRelatives(self.m_parent1, p=1) if grandParent == None or not self.m_isParentBend: grandParent = self.m_parent1 parentConst = rc.applyWeightedConstraint( grandParent, parentCtrl, self.m_parent2, cmds.parentConstraint ) pointConst = rc.applyWeightedConstraint( self.m_parent1, pointCtrl, self.m_parent2, cmds.pointConstraint ) cmds.aimConstraint( self.m_parent2, newCtrlGroups[1] ) currentPos = vec.add(currentPos, stepVec) #lock attrs cmds.setAttr(newCtrl+".rotate", l=1) cmds.setAttr(newCtrl+".scale", l=1) rc.addToLayer(self.m_sceneData, "detailCtrl", self.m_controls) # Add controls i=0 for control in self.m_controls: rc.addToControlDict(self.m_allControls, "%s_%d_detail" %(self.m_baseName, i), control) i+=1
def isColliding(self, bX, bY): return VecMath.refresh(bX, bY, self.surface)
def linear_distance_heuristic(path, goal): return VecMath.length(VecMath.sub(goal.position, path[-1].position))