def emparentar(seleccionados): wm = bpy.context.window_manager nube, caras, objetos = [], [], [] d1 = Vector([0.06, 0.08, 0.0]) d2 = Vector([0.06, -0.08, 0.0]) d3 = Vector([-0.1, 0.0, 0.0]) c = 0 for ob in seleccionados: objetos.append(ob) dd = ob.location mat = ob.matrix_world if wm.single: nube.append(dd) else: dd1 = d1.copy() dd2 = d2.copy() dd3 = d3.copy() if wm.rotate: dd1.rotate(mat) dd2.rotate(mat) dd3.rotate(mat) nube.append(dd + dd1 * wm.scale) nube.append(dd + dd2 * wm.scale) nube.append(dd + dd3 * wm.scale) caras.append([c, c + 1, c + 2]) c += 3 malla = bpy.data.meshes.new('puntos') padre = bpy.data.objects.new('padre', malla) bpy.context.scene.objects.link(padre) padre.hide_render = True padre.draw_type = 'WIRE' malla.from_pydata(nube, [], caras) malla.update() bpy.context.scene.objects.active = padre bpy.ops.object.select_all(action = 'DESELECT') bpy.ops.object.mode_set() for c in range(len(nube)): malla.vertices[c].select = False for c in range(len(objetos)): objetos[c].select = True if wm.single: malla.vertices[c].select = True else: for n in range(3): malla.vertices[c * 3 + n].select = True bpy.ops.object.editmode_toggle() bpy.ops.object.vertex_parent_set() bpy.ops.object.editmode_toggle() if wm.single: malla.vertices[c].select = False else: for n in range(3): malla.vertices[c * 3 + n].select = False objetos[c].select = False padre.select = True
def emparentar(seleccionados): wm = bpy.context.window_manager nube, caras, objetos = [], [], [] d1 = Vector([0.06, 0.08, 0.0]) d2 = Vector([0.06, -0.08, 0.0]) d3 = Vector([-0.1, 0.0, 0.0]) c = 0 for ob in seleccionados: objetos.append(ob) dd = ob.location mat = ob.matrix_world if wm.single: nube.append(dd) else: dd1 = d1.copy() dd2 = d2.copy() dd3 = d3.copy() if wm.rotate: dd1.rotate(mat) dd2.rotate(mat) dd3.rotate(mat) nube.append(dd + dd1 * wm.scale) nube.append(dd + dd2 * wm.scale) nube.append(dd + dd3 * wm.scale) caras.append([c, c + 1, c + 2]) c += 3 malla = bpy.data.meshes.new('puntos') padre = bpy.data.objects.new('padre', malla) bpy.context.scene.objects.link(padre) padre.hide_render = True padre.draw_type = 'WIRE' malla.from_pydata(nube, [], caras) malla.update() bpy.context.scene.objects.active = padre bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.mode_set() for c in range(len(nube)): malla.vertices[c].select = False for c in range(len(objetos)): objetos[c].select = True if wm.single: malla.vertices[c].select = True else: for n in range(3): malla.vertices[c * 3 + n].select = True bpy.ops.object.editmode_toggle() bpy.ops.object.vertex_parent_set() bpy.ops.object.editmode_toggle() if wm.single: malla.vertices[c].select = False else: for n in range(3): malla.vertices[c * 3 + n].select = False objetos[c].select = False padre.select = True
def createLeaves(tree, probability=0.5, size=0.5, randomsize=0.1, randomrot=0.1, maxconnections=2, bunchiness=1.0): p = bpy.context.scene.cursor_location verts = [] faces = [] c1 = Vector((-size / 10, -size / 2, 0)) c2 = Vector((size, -size / 2, 0)) c3 = Vector((size, size / 2, 0)) c4 = Vector((-size / 10, size / 2, 0)) t = gauss(1.0 / probability, 0.1) bpswithleaves = 0 for bp in tree.branchpoints: if bp.connections < maxconnections: dv = tree.branchpoints[ bp.parent].v - bp.v if bp.parent else Vector((0, 0, 0)) dvp = Vector((0, 0, 0)) bpswithleaves += 1 nleavesonbp = 0 while t < bpswithleaves: nleavesonbp += 1 rx = ( random() - 0.5 ) * randomrot * 6.283 # TODO vertical tilt in direction of tropism ry = (random() - 0.5) * randomrot * 6.283 rot = Euler((rx, ry, random() * 6.283), 'ZXY') scale = 1 + (random() - 0.5) * randomsize v = c1.copy() v.rotate(rot) verts.append(v * scale + bp.v + dvp) v = c2.copy() v.rotate(rot) verts.append(v * scale + bp.v + dvp) v = c3.copy() v.rotate(rot) verts.append(v * scale + bp.v + dvp) v = c4.copy() v.rotate(rot) verts.append(v * scale + bp.v + dvp) n = len(verts) faces.append((n - 4, n - 3, n - 2, n - 1)) t += gauss( 1.0 / probability, 0.1 ) # this is not the best choice of distribution because we might get negative values especially if sigma is large dvp = nleavesonbp * ( dv / (probability**bunchiness) ) # TODO add some randomness to the offset mesh = bpy.data.meshes.new('Leaves') mesh.from_pydata(verts, [], faces) mesh.update(calc_edges=True) mesh.uv_textures.new() return mesh
class Eyes: def __init__(self, cross_eye=0.0, default_look_distance=200, eye_fov=40): self.default_look_target = Vector((0, default_look_distance, 0)) self.look_target = self.default_look_target self.rotation = Quaternion((1, 0, 0, 0)) self.position = Vector((0, 0, 0)) self.leye_pos = Vector((-5.96898, 6.09201, 3.69949)) self.reye_pos = Vector((5.96898, 6.09201, 3.69949)) self.leye_norm = Vector((cross_eye, 1, 0)) self.reye_norm = Vector((-cross_eye, 1, 0)) self.pupil_pos = np.zeros(4) # lx ly rx ry self.eye_fov = eye_fov def set_rotation(self, rotation): self.rotation = rotation def update(self): self.update_pupil_pos() new_look_target = self.update_look_target() if new_look_target: print("blonk") self.look_target = new_look_target self.update() else: print(self.pupil_pos) def update_pupil_pos(self): leye_pos = self.leye_pos.copy() leye_pos.rotate(self.rotation) leye_pos += self.position reye_pos = self.reye_pos.copy() reye_pos.rotate(self.rotation) reye_pos += self.position leye_beam = self.look_target.copy() - leye_pos reye_beam = self.look_target.copy() - reye_pos leye_beam.rotate(self.rotation.inverted()) leye_beam_angle = self.leye_norm.rotation_difference(leye_beam) reye_beam.rotate(self.rotation.inverted()) reye_beam_angle = self.reye_norm.rotation_difference(reye_beam) self.pupil_pos = -1*np.degrees([leye_beam_angle.z, leye_beam_angle.x, reye_beam_angle.z, reye_beam_angle.x]) def update_look_target(self, forced=False): fov = np.full(4, self.eye_fov) / np.array([2, 4, 2, 4]) if forced or np.any(self.pupil_pos < -1*fov) or np.any(self.pupil_pos > fov): new_look_target = self.default_look_target.copy() new_look_target.rotate(self.rotation) new_look_target += self.position return new_look_target else: return False
def createLeaves( tree, probability=0.5, size=0.5, randomsize=0.1, randomrot=0.1, maxconnections=2, bunchiness=1.0, connectoffset=-0.1 ): p = bpy.context.scene.cursor_location verts = [] faces = [] c1 = Vector((connectoffset, -size / 2, 0)) c2 = Vector((size + connectoffset, -size / 2, 0)) c3 = Vector((size + connectoffset, size / 2, 0)) c4 = Vector((connectoffset, size / 2, 0)) t = gauss(1.0 / probability, 0.1) bpswithleaves = 0 for bp in tree.branchpoints: if bp.connections < maxconnections: dv = tree.branchpoints[bp.parent].v - bp.v if bp.parent else Vector((0, 0, 0)) dvp = Vector((0, 0, 0)) bpswithleaves += 1 nleavesonbp = 0 while t < bpswithleaves: nleavesonbp += 1 rx = (random() - 0.5) * randomrot * 6.283 # TODO vertical tilt in direction of tropism ry = (random() - 0.5) * randomrot * 6.283 rot = Euler((rx, ry, random() * 6.283), "ZXY") scale = 1 + (random() - 0.5) * randomsize v = c1.copy() v.rotate(rot) verts.append(v * scale + bp.v + dvp) v = c2.copy() v.rotate(rot) verts.append(v * scale + bp.v + dvp) v = c3.copy() v.rotate(rot) verts.append(v * scale + bp.v + dvp) v = c4.copy() v.rotate(rot) verts.append(v * scale + bp.v + dvp) n = len(verts) faces.append((n - 1, n - 4, n - 3, n - 2)) t += gauss( 1.0 / probability, 0.1 ) # this is not the best choice of distribution because we might get negative values especially if sigma is large dvp = nleavesonbp * (dv / (probability ** bunchiness)) # TODO add some randomness to the offset mesh = bpy.data.meshes.new("Leaves") mesh.from_pydata(verts, [], faces) mesh.update(calc_edges=True) mesh.uv_textures.new() return mesh
class Turtle: def __init__(self, position=(0, 0, 0),direction =(0,0,1), orientation=(0, 1, 0),axe_rotation = (0,0,1), vitesse=1, angle=90,imperfection = 0.2): self.position = Vector(position) self.direction = Vector(direction).normalized() self.orientation = Vector(orientation).normalized() self.vitesse = vitesse self.angle = radians(angle) self.memoireEtat = [] self.comportement_initialisation() self.imperfection = imperfection def comportement_initialisation(self): self.comportements = { '+':self.comportement_plus, '-':self.comportement_moins, 'F':self.comportement_F, '[':self.comportement_save_etat, ']':self.comportement_restor_etat } def comportement_F(self): p_debut = self.position.copy() self.position += self.direction * self.vitesse dx = (random() - 0.5) * self.imperfection dy = (random() - 0.5) * self.imperfection dz = (random() - 0.5) * self.imperfection self.direction += Vector((dx,dy,dz)) p_fin = self.position.copy() return Section(debut = p_debut,fin = p_fin) def comportement_save_etat(self): etat = (self.position.copy(), self.direction.copy(), self.vitesse, self.angle) self.memoireEtat.append(etat) def comportement_restor_etat(self): (self.position, self.direction, self.vitesse, self.angle) = self.memoireEtat.pop() def comportement_plus(self): rotation = Matrix.Rotation(self.angle,4,self.orientation) self.direction.rotate(rotation) def comportement_moins(self): rotation = Matrix.Rotation(- self.angle, 4,self.orientation) self.direction.rotate(rotation) def interpretation(self,s): for char in s: comportement = self.comportements[char]() if char in self.comportements else None yield comportement
def _getScreenQuad(self): vec_A = Vector(self.box_coords[0]) vec_B = Vector(self.box_coords[1]) delta = vec_B - vec_A vec_a = vec_A.copy() vec_a.x += delta.x vec_b = vec_B.copy() vec_b.x -= delta.x return [vec_A, vec_a, vec_B, vec_b]
def unmirror_sym(obj_list): '''Unmirror symetrical elements.''' for object in obj_list: mesh = object.data # remove the mirror modifier # set object active bpy.context.scene.objects.active = object bpy.ops.object.modifier_remove(modifier='Mirror') # the first vertice gives us the coordinates for the backtransformation v = Vector((mesh.vertices[0].co[0], mesh.vertices[0].co[1], mesh.vertices[0].co[2])) # backtransformation mesh.transform(Matrix.Translation(-v)) #recalculate !!!!!!! odd behaviour if not done !!!!!!! mesh.update() # set location point back # adaption for FG CSYS if bpy.context.scene.csys == '1': object.location = (v) elif bpy.context.scene.csys == '0': u = v.copy() u.x = -u.x u.y = -u.y object.location = u
def nautical_euler_from_axes(forward, right): x = Vector(right) y = Vector(forward) world_x = Vector((1, 0, 0)) world_z = Vector((0, 0, 1)) if abs(y.z) > (1 - 1e-12): # sufficiently close to vertical roll = 0.0 xdir = x.copy() else: xdir = y.cross(world_z) rollPos = angle_signed(-y, x, xdir, 0.0) rollNeg = angle_signed(-y, x, -xdir, 0.0) if abs(rollNeg) < abs(rollPos): roll = rollNeg xdir = -xdir else: roll = rollPos xdir = Vector((xdir.x, xdir.y, 0)).normalized() yaw = angle_signed(-world_z, xdir, world_x, 0.0) zdir = xdir.cross(y).normalized() pitch = angle_signed(-xdir, zdir, world_z, 0.0) return Euler((pitch, roll, yaw), 'YXZ')
def calculate_bbox(verts, matrix=None): mapped_verts = verts if matrix is not None: mapped_verts = map(lambda v, M=matrix: M @ v, verts) bbox_min = Vector(next(mapped_verts)) bbox_max = bbox_min.copy() for v in mapped_verts: if v.x < bbox_min.x: bbox_min.x = v.x if v.y < bbox_min.y: bbox_min.y = v.y if v.z < bbox_min.z: bbox_min.z = v.z if v.x > bbox_max.x: bbox_max.x = v.x if v.y > bbox_max.y: bbox_max.y = v.y if v.z > bbox_max.z: bbox_max.z = v.z # Return bounding box verts return bbox_min, bbox_max
def _bay_bow_vertical_filler_strip( corner_v: Vector, start_angle: float, angle: float, height: float, frame_th: float, shift=(0, 0, 0)) -> Tuple[list, list]: """ Create the triangular filler strip found between bay and bow windows. Return vertices and faces. :param corner_v: the location of the corner of the frame :param start_angle: the angle, measured from the +X axis, with positive being CCW of corner_v :param angle: how wide the angle is for the triangle :param height: the height of the strip :param frame_th: the thickness of the frame :param shift: Amount to add to all x, y, and z positions :return: the vertices and faces of the strip """ verts, faces = [], [] dx, dy, dz = shift v1 = Vector((frame_th, 0, 0)) v2 = v1.copy() v1.rotate(Euler((0, 0, start_angle))) v2.rotate(Euler((0, 0, start_angle + angle))) for x, y in (corner_v[:2], (corner_v + v1)[:2], (corner_v + v2)[:2]): verts.append((dx + x, dy + y, dz)) verts.append((dx + x, dy + y, dz + height)) faces.extend( ((0, 2, 3, 1), (2, 4, 5, 3), (4, 0, 1, 5), (1, 3, 5), (0, 4, 2))) return verts, faces
def widget_iter_shapekey(context, mpr, ob, fmap, fmap_target): from mathutils import ( Vector, ) # generic initialize if USE_VERBOSE: print("(iter-init)") context.area.header_text_set("ShapeKey face-map: {}".format(fmap.name)) # invoke() # ... if USE_VERBOSE: print("(iter-invoke)") event = yield tweak = set() # modal(), first step mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) mval = mval_init.copy() # impl vars shape = fmap_target del fmap_target value_init = shape.value # Keep this loop fast! runs on mouse-movement. while True: event, tweak_next = yield if event in {True, False}: break tweak = tweak_next if USE_VERBOSE: print("(iter-modal)", event, tweak) mval = Vector((event.mouse_region_x, event.mouse_region_y)) input_scale = 1.0 is_precise = 'PRECISE' in tweak if is_precise: input_scale /= 10.0 final_value = value_init + ((mval.y - mval_init.y) / 200.0) * input_scale if 'SNAP' in tweak: final_value = round(final_value, 2 if is_precise else 1) shape.value = final_value # exit() if USE_VERBOSE: print("(iter-exit)", event) if event is True: # cancel shape.value = scale_init else: shape.id_data.keyframe_insert(shape.path_from_id() + ".value") context.area.header_text_set("")
def widget_iter_shapekey(context, mpr, ob, fmap, fmap_target): from mathutils import ( Vector, ) # generic initialize if USE_VERBOSE: print("(iter-init)") context.area.header_text_set("ShapeKey face-map: {}".format(fmap.name)) # invoke() # ... if USE_VERBOSE: print("(iter-invoke)") event = yield tweak = set() # modal(), first step mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) mval = mval_init.copy() # impl vars shape = fmap_target del fmap_target value_init = shape.value # Keep this loop fast! runs on mouse-movement. while True: event, tweak_next = yield if event in {True, False}: break tweak = tweak_next if USE_VERBOSE: print("(iter-modal)", event, tweak) mval = Vector((event.mouse_region_x, event.mouse_region_y)) input_scale = 1.0 is_precise = 'PRECISE' in tweak if is_precise: input_scale /= 10.0 final_value = value_init + ((mval.y - mval_init.y) / 200.0) * input_scale if 'SNAP' in tweak: final_value = round(final_value, 2 if is_precise else 1) shape.value = final_value # exit() if USE_VERBOSE: print("(iter-exit)", event) if event is True: # cancel shape.value = scale_init else: shape.id_data.keyframe_insert(shape.path_from_id() + ".value") context.area.header_text_set()
class Turtle: def __init__(self, position=(0, 0, 0), orientation=(1, 0, 0), vitesse=1, angle=radians(90)): self.position = Vector(position) self.orientation = Vector(orientation).normalized() self.vitesse = vitesse self.angle = angle self.memoireEtat = [] self.comportement_initialisation() def comportement_initialisation(self): self.comportements = { '+':self.comportement_plus, '-':self.comportement_moins, 'F':self.comportement_F, '[':self.comportement_save_etat, ']':self.comportement_restor_etat } def comportement_F(self): p_debut = self.position.copy() self.position += self.orientation * self.vitesse p_fin = self.position.copy() return Section(debut = p_debut,fin = p_fin) def comportement_save_etat(self): etat = (self.position.copy(), self.orientation.copy(), self.vitesse, self.angle) self.memoireEtat.append(etat) def comportement_restor_etat(self): (self.position, self.orientation, self.vitesse, self.angle) = self.memoireEtat.pop() def comportement_plus(self): rotation = Matrix.Rotation(self.angle,4,(0,1,0)) self.orientation.rotate(rotation) def comportement_moins(self): rotation = Matrix.Rotation(- self.angle, 4,(0,1,0)) self.orientation.rotate(rotation) def interpretation(self,s): for char in s: comportement = self.comportements[char]() if char in self.comportements else None yield comportement
class TextBox: def __init__(self): self.text = "" self.position = Vector((0, 0)) self.width = 400 self.background_color = (1.0, 1.0, 1.0, 1.0) self.background_border_color = (0.9, 0.76, 0.4, 1.0) self.text_color = (0.1, 0.1, 0.1, 1.0) self.font_size = 8 self.font = 1 self.line_height = 23 self.padding = 5 def draw(self): blf.size(self.font, self.font_size, int(getDpi())) self.calc_lines() background = self.get_background_rectangle() background.draw() glColor4f(*self.text_color) pos = self.position.copy() pos.x += self.padding pos.y -= self.padding - self.line_height / 2 pos.y -= blf.dimensions(self.font, "i")[1] / 2 for i, line in enumerate(self.lines): pos.y -= self.line_height blf.position(self.font, pos.x, pos.y, 0) blf.draw(self.font, line) def calc_lines(self): lines = self.text.split("\n") while len(lines) > 0: if lines[-1] != "": break del lines[-1] self.lines = lines def get_background_rectangle(self): self.calc_height() self.calc_width() background = Rectangle( x1 = self.position.x, y1 = self.position.y, x2 = self.position.x + self.width, y2 = self.position.y - self.height ) background.border_thickness = -1 background.color = self.background_color background.border_color = self.background_border_color return background def calc_height(self): self.height = 2 * self.padding + self.line_height * len(self.lines) def calc_width(self): widths = [blf.dimensions(self.font, line)[0] for line in self.lines] self.width = max(widths) + 2 * self.padding
class TextBox: def __init__(self): self.text = "" self.position = Vector((0, 0)) self.width = 400 self.background_color = (1.0, 1.0, 1.0, 1.0) self.background_border_color = (0.9, 0.76, 0.4, 1.0) self.text_color = (0.1, 0.1, 0.1, 1.0) self.font_size = 8 self.font = 1 self.line_height = 23 self.padding = 5 def draw(self): blf.size(self.font, self.font_size, int(getDpi())) self.calc_lines() background = self.get_background_rectangle() background.draw() glColor4f(*self.text_color) pos = self.position.copy() pos.x += self.padding pos.y -= self.padding - self.line_height / 2 pos.y -= blf.dimensions(self.font, "i")[1] / 2 for i, line in enumerate(self.lines): pos.y -= self.line_height blf.position(self.font, pos.x, pos.y, 0) blf.draw(self.font, line) def calc_lines(self): lines = self.text.split("\n") while len(lines) > 0: if lines[-1] != "": break del lines[-1] self.lines = lines def get_background_rectangle(self): self.calc_height() self.calc_width() background = Rectangle(x1=self.position.x, y1=self.position.y, x2=self.position.x + self.width, y2=self.position.y - self.height) background.border_thickness = -1 background.color = self.background_color background.border_color = self.background_border_color return background def calc_height(self): self.height = 2 * self.padding + self.line_height * len(self.lines) def calc_width(self): widths = [blf.dimensions(self.font, line)[0] for line in self.lines] self.width = max(widths) + 2 * self.padding
class Transform(object): world_scale = 0.1 world_scale_reciprocal = 10.0 # := 1/world_scale def __init__(self): self.translation = Vector((0, 0, 0)) self.rotation = Quaternion() self.scale = 1 def read(self, file): v = read_vector4(file) q = read_vector4(file) scale = read_vector4(file) self.translation = Vector(v.xyz) * self.world_scale self.rotation = Quaternion(q.wxyz) self.scale = scale.z def write(self, file): v = self.translation * self.world_scale_reciprocal v = (v.x, v.y, v.z, 0) q = self.rotation q = (q.x, q.y, q.z, q.w) scale = (self.scale, self.scale, self.scale, 0) write_vector4_raw(file, v) write_vector4_raw(file, q) write_vector4_raw(file, scale) def __mul__(self, other): t = Transform() v = Vector(other.translation) # dup v.rotate(self.rotation) t.translation = self.translation + v * self.scale t.rotation = self.rotation * other.rotation t.scale = self.scale * other.scale return t def to_matrix(self): m_rotation = self.rotation.to_matrix().to_4x4() # 3x3 to 4x4 m_scale = Matrix.Scale(self.scale, 4) m = m_rotation * m_scale m.translation = self.translation return m def copy(self): t = Transform() t.translation = self.translation.copy() t.rotation = self.rotation.copy() t.scale = self.scale return t
def spokes(n): up = Vector((0,1,0)) angle = Euler((0,0,2*pi/float(n)),'XYZ') angle2 = Euler((0,0,pi/float(n)),'XYZ') of = rotate(up, angle2) verts = [] for i in range(n): verts.append(up.copy()) verts.append(up+of) up = rotate(up, angle) verts.append(up+of) of = rotate(of, angle) faces = [] for i in range(n): faces.append((3*i,3*i+1,3*i+2,(3*i+3) % (n*3))) return verts, faces
def align(aligny=Vector((0, 1, 0)), alignz=Vector((0, 0, 1))): ''' Get a Rotation Matrix. The Matrix Local +Y Axis gets aligned with aligny. The +Z Local Axis gets aligned with alignz as best as possible, without disturbing the Y alignment. This implementation looks a little brutish to the Coders eyes. Better ideas are very welcome. ''' X = Vector((1, 0, 0)) Y = Vector((0, 1, 0)) Z = Vector((0, 0, 1)) if alignz.length == 0: alignz = Z.copy() mat = Matrix().to_3x3() #Align local-Y axis with aligny axis, angle = axisangle(Y, aligny) if axis.length == 0: axis = X rot_to_y = Matrix.Rotation(angle, 3, axis) bm1 = get_axis_aligned_bm(rot_to_y) #Align local-Z with projected alignz eul = rot_to_y.to_euler() target_localx = aligny.cross(alignz).normalized() target_localz = target_localx.cross(aligny).normalized() angle = target_localz.angle(bm1.verts[2].co) ### NEED SOME TEST FOR ANGLE FLIPPING eul.rotate_axis('Y', angle) mat = eul.to_matrix().to_4x4() ### Test if rotation is good bmf = get_axis_aligned_bm(mat) dist = distance_point_to_plane(bmf.verts[2].co, target_localz, target_localz.cross(alignz)) error = 1e-06 ### Flip the angle if abs(dist) > error: eul = rot_to_y.to_euler() eul.rotate_axis('Y', -angle) mat = eul.to_matrix().to_4x4() bm1.free() bmf.free() return mat.to_4x4()
def align(aligny=Vector((0,1,0)), alignz=Vector((0,0,1))): ''' Get a Rotation Matrix. The Matrix Local +Y Axis gets aligned with aligny. The +Z Local Axis gets aligned with alignz as best as possible, without disturbing the Y alignment. This implementation looks a little brutish to the Coders eyes. Better ideas are very welcome. ''' X=Vector((1,0,0)) Y=Vector((0,1,0)) Z=Vector((0,0,1)) if alignz.length == 0: alignz = Z.copy() mat = Matrix().to_3x3() #Align local-Y axis with aligny axis, angle = axisangle(Y, aligny) if axis.length == 0: axis = X rot_to_y = Matrix.Rotation(angle,3,axis) bm1 = get_axis_aligned_bm(rot_to_y) #Align local-Z with projected alignz eul = rot_to_y.to_euler() target_localx = aligny.cross(alignz).normalized() target_localz = target_localx.cross(aligny).normalized() angle = target_localz.angle(bm1.verts[2].co) ### NEED SOME TEST FOR ANGLE FLIPPING eul.rotate_axis('Y', angle) mat = eul.to_matrix().to_4x4() ### Test if rotation is good bmf = get_axis_aligned_bm(mat) dist = distance_point_to_plane(bmf.verts[2].co, target_localz, target_localz.cross(alignz)) error = 1e-06 ### Flip the angle if abs(dist)>error: eul = rot_to_y.to_euler() eul.rotate_axis('Y', -angle) mat = eul.to_matrix().to_4x4() bm1.free() bmf.free() return mat.to_4x4()
def invoke(self, context, event): bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='EDIT') active_space = context.space_data if active_space.type == 'VIEW_3D': context.window_manager.modal_handler_add(self) self._handle = context.region.callback_add(draw_lines,\ (self, context), 'POST_PIXEL') self.vertices = [] mouseco = Vector((event.mouse_region_x, event.mouse_region_y, 0)) self.mouseco = mouseco self.current_vec = mouseco.copy() self.along = -1 self.snap_type = '' self.vec_snap_vert = None context.area.tag_redraw() self.time = time.time() return {'RUNNING_MODAL'} else: self.report({'WARNING'}, "Active space must be a View3d") return {'CANCELLED'}
def box_selection_vis(cont): global b scene = logic.getCurrentScene() cam = scene.active_camera own = cont.owner click = cont.sensors["MouseClick"] if click.positive: if own['held']: a = Vector(logic.mouse.position) draw_box(a, b) else: a = Vector(logic.mouse.position) b = a.copy() own['held'] = True else: if own['held']: own['held'] = False
def group_in_frame(node_tree, name, nodes): frame_node = node_tree.nodes.new("NodeFrame") frame_node.label = name frame_node.name = name + "_frame" min_pos = Vector(nodes[0].location) max_pos = min_pos.copy() for node in nodes: top_left = node.location bottom_right = top_left + Vector((node.width, -node.height)) for i in (0, 1): min_pos[i] = min(min_pos[i], top_left[i], bottom_right[i]) max_pos[i] = max(max_pos[i], top_left[i], bottom_right[i]) node.parent = frame_node frame_node.width = max_pos[0] - min_pos[0] + 50 frame_node.height = max(max_pos[1] - min_pos[1] + 50, 450) frame_node.shrink = True return frame_node
def draw_square_pyramid(point, orientation, angle=45, depth=1, colour=[1, 1, 1], pyramid=True, incline=True): points = [] axis_values = [-1, 1] hypotenuse = depth if depth else 1 angle = radians(angle) for x in axis_values: for z in axis_values: x_coordinate = hypotenuse * sin(angle) * x if incline: z += 1 z_coordinate = hypotenuse * cos(angle) * z point_a = Vector((x_coordinate, depth, z_coordinate)) points.append(point_a) for point_a in points: for point_b in points: if point_a is point_b: continue same_axis = [True for i in range(3) if point_a[i] == point_b[i]] if len(same_axis) < 2: continue a = point_a.copy() a.rotate(orientation) b = point_b.copy() b.rotate(orientation) render.drawLine(a + point, b + point, colour) if pyramid: render.drawLine(point, b + point, colour)
def execute(self, context): # define origin and axis start = Vector(self.origin) #print(start) #print(Vector(start)) #print(start[0]) #print(start[1]) #print(start[2]) if self.direction_specification_style == "endpoint": end = Vector(self.e3_vec3) axis = end - start else: end = Vector(self.origin) + Vector(self.e3_vec3) axis = Vector(self.e3_vec3) # set up x,y,z vectors for convenience x = Vector((1, 0, 0)) y = Vector((0, 1, 0)) z = Vector((0, 0, 1)) # create un-normalized basis vectors c = axis.copy() b = axis.cross(Vector(self.e1_vec3)) a = b.cross(c) self.a_raw = a.copy() self.b_raw = b.copy() self.c_raw = c.copy() self.a_normalized = a.copy() self.b_normalized = b.copy() self.c_normalized = c.copy() self.a_normalized.normalize() self.b_normalized.normalize() self.c_normalized.normalize() if self.norm_specification_style == "normalized": xdim = 1 ydim = 1 zdim = 1 elif self.norm_specification_style == "norm_from_vectors": xdim = a.length ydim = b.length zdim = c.length else: xdim = self.e1_norm ydim = self.e2_norm zdim = self.e3_norm # normalize the basis a.normalize() b.normalize() c.normalize() # create a,b,c,d vectors for the final transformation matrix a = a.resized(4) b = b.resized(4) c = c.resized(4) d = Vector((0, 0, 0, 1)) # create scaling+translation matrices Sx = Matrix.Scale(xdim, 4, Vector((1, 0, 0))) Sy = Matrix.Scale(ydim, 4, Vector((0, 1, 0))) Sz = Matrix.Scale(zdim, 4, Vector((0, 0, 1))) T = Matrix.Translation(start) # create the final transformation matrix Mfinal = T * Matrix((a, b, c, d)).transposed() * Sx * Sy * Sz # apply the final transformation matrix obj = context.object if obj is None: self.report({'ERROR'}, "No object selected!") return {'FINISHED'} # TODO: Fix inverse transformation. Does not seem to work properly when translation+rotation+scaling are used. if not self.bool_inverse: obj.matrix_local = Mfinal else: obj.matrix_local = Mfinal.inverted() print('obj.matrix_local = ', obj.matrix_local) #print('FINISHED') return {'FINISHED'}
def widget_iter_pose_scale(context, mpr, ob, fmap, fmap_target): from mathutils import ( Vector, ) # generic initialize if USE_VERBOSE: print("(iter-init)") context.area.header_text_set("Scale face-map: {}".format(fmap.name)) # invoke() # ... if USE_VERBOSE: print("(iter-invoke)") event = yield tweak = set() # modal(), first step mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) mval = mval_init.copy() # impl vars pose_bone = fmap_target del fmap_target tweak_attr = "scale" tweak_attr_lock = "lock_scale" scale_init = pose_bone.scale.copy() # Keep this loop fast! runs on mouse-movement. while True: event, tweak_next = yield if event in {True, False}: break tweak = tweak_next if USE_VERBOSE: print("(iter-modal)", event, tweak) mval = Vector((event.mouse_region_x, event.mouse_region_y)) input_scale = 1.0 is_precise = 'PRECISE' in tweak if is_precise: input_scale /= 10.0 scale_factor = ((mval.y - mval_init.y) / 200.0) * input_scale if 'SNAP' in tweak: # relative scale_factor = round(scale_factor, 2 if is_precise else 1) final_value = scale_init * (1.0 + scale_factor) pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, final_value) # exit() if USE_VERBOSE: print("(iter-exit)", event) if event is True: # cancel pose_bone.scale = scale_init else: pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock) context.area.header_text_set()
def draw(self, context, render=False): if self.on: """ draw from bottom to top so we are able to always fit needs """ x_min, x_max, y_min, y_max = self.screen.size(context) available_w = x_max - x_min - 2 * self.spacing.x main_title_size = self.main_title.text_size(context) + Vector((5, 0)) # h = context.region.height # 0,0 = bottom left pos = Vector((x_min + self.spacing.x, y_min)) shortcuts = [] # sort by lines lines = [] line = [] space = 0 sum_txt = 0 for key, ks, label, ls in self.shortcuts: space += ks.x + ls.x + self.spacing.x if pos.x + space > available_w: txt_spacing = (available_w - sum_txt) / (max(1, len(line) - 1)) sum_txt = 0 space = ks.x + ls.x + self.spacing.x lines.append((txt_spacing, line)) line = [] sum_txt += ks.x + ls.x line.append([key, ks, label, ls]) if len(line) > 0: txt_spacing = (available_w - sum_txt) / (max(1, len(line) - 1)) lines.append((txt_spacing, line)) # reverse lines to draw from bottom to top lines = list(reversed(lines)) for spacing, line in lines: pos.y += self.spacing.y pos.x = x_min + self.spacing.x for key, ks, label, ls in line: key.pos_3d = pos.copy() pos.x += ks.x label.pos_3d = pos.copy() pos.x += ls.x + spacing shortcuts.extend([key, label]) pos.y += ks.y + self.spacing.y n_shortcuts = len(shortcuts) # shortcut area self.shortcut_area.pts_3d = [ (x_min, self.margin), (x_max, self.margin), (x_max, pos.y), (x_min, pos.y) ] # small space between shortcut area and main title bar if n_shortcuts > 0: pos.y += 0.5 * self.spacing.y self.title_area.pts_3d = [ (x_min, pos.y), (x_max, pos.y), (x_max, pos.y + main_title_size.y + 2 * self.spacing.y), (x_min, pos.y + main_title_size.y + 2 * self.spacing.y) ] pos.y += self.spacing.y title_size = self.title.text_size(context) # check for space available: # if explanation + title + main_title are too big # 1 remove main title # 2 remove title explanation_size = self.explanation.text_size(context) self.show_title = True self.show_main_title = True if title_size.x + explanation_size.x > available_w: # keep only explanation self.show_title = False self.show_main_title = False elif main_title_size.x + title_size.x + explanation_size.x > available_w: # keep title + explanation self.show_main_title = False self.title.pos_3d = (x_min + self.spacing.x, pos.y) else: self.title.pos_3d = (x_min + self.spacing.x + main_title_size.x, pos.y) self.explanation.pos_3d = (x_max - self.spacing.x - explanation_size.x, pos.y) self.main_title.pos_3d = (x_min + self.spacing.x, pos.y) self.shortcut_area.draw(context) self.title_area.draw(context) if self.show_title: self.title.draw(context) if self.show_main_title: self.main_title.draw(context) self.explanation.draw(context) for s in shortcuts: s.draw(context) self.top = Vector((x_min, pos.y + main_title_size.y + self.spacing.y))
def widget_iter_pose_rotate(context, mpr, ob, fmap, fmap_target): from mathutils import ( Vector, Quaternion, ) # generic initialize if USE_VERBOSE: print("(iter-init)") tweak_attr = pose_bone_rotation_attr_from_mode(fmap_target) context.area.header_text_set("Rotating ({}) face-map: {}".format( tweak_attr, fmap.name)) tweak_attr_lock = "lock_rotation" # invoke() # ... if USE_VERBOSE: print("(iter-invoke)") event = yield tweak = set() # modal(), first step mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) mval = mval_init.copy() # impl vars pose_bone = fmap_target del fmap_target # Could use face-map center too # Don't update these while interacting bone_matrix_init = pose_bone.matrix.copy() depth_location = bone_matrix_init.to_translation() rot_center = bone_matrix_init.to_translation() world_to_local_3x3 = pose_bone_calc_transform_orientation(pose_bone) # for rotation local_view_vector = ( calc_view_vector(context) @ world_to_local_3x3).normalized() rot_init = getattr(pose_bone, tweak_attr).copy() # Keep this loop fast! runs on mouse-movement. while True: event, tweak_next = yield if event in {True, False}: break if event.type == 'INBETWEEN_MOUSEMOVE': continue tweak = tweak_next if USE_VERBOSE: print("(iter-modal)", event, tweak) mval = Vector((event.mouse_region_x, event.mouse_region_y)) # calculate rotation matrix from input co_init, co = coords_to_loc_3d(context, (mval_init, mval), depth_location) # co_delta = world_to_local_3x3 @ (co - co_init) input_scale = 1.0 is_precise = 'PRECISE' in tweak if is_precise: input_scale /= 10.0 if False: # Dial logic, not obvious enough unless we show graphical line to center... # but this is typically too close to the center of the face-map. rot_delta = ( co_init - rot_center).rotation_difference(co - rot_center).to_matrix() else: # Steering wheel logic, left to rotate left, right to rotate right :) # use X-axis only # Calculate imaginary point as if mouse was moved 100px to the right # then transform to local orientation and use to see where the cursor is rotate_angle = ((mval.x - mval_init.x) / 100.0) rotate_angle *= input_scale if 'SNAP' in tweak: v = math.radians(1.0 if is_precise else 15.0) rotate_angle = round(rotate_angle / v) * v del v rot_delta = Quaternion(local_view_vector, rotate_angle).to_matrix() # rot_delta = (co_init - rot_center).rotation_difference(co - rot_center).to_matrix() rot_matrix = rot_init.to_matrix() rot_matrix = rot_matrix @ rot_delta if tweak_attr == "rotation_quaternion": final_value = rot_matrix.to_quaternion() elif tweak_attr == "rotation_euler": final_value = rot_matrix.to_euler(pose_bone.rotation_mode, rot_init) else: assert (tweak_attr == "rotation_axis_angle") final_value = rot_matrix.to_quaternion().to_axis_angle() final_value = (*final_value[0], final_value[1]) # flatten pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, final_value) # exit() if USE_VERBOSE: print("(iter-exit)", event) if event is True: # cancel setattr(pose_bone, tweak_attr, rot_init) else: pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock) context.area.header_text_set(None)
class ThirdPersonCamera(bge.types.KX_PythonComponent): args = OrderedDict([ ("Activate", True), ("Mouse Sensibility", 2.0), ("Invert Mouse X Axis", False), ("Invert Mouse Y Axis", False), ("Camera Height", 0.7), ("Camera Distance", 5.0), ("Camera Crab (Side)", 0.6), ("Camera Collision", True), ("Camera Collision Property", "ground"), ("Align Player to View", {"Never", "On Player Movement", "Always"}), ("Align Player Smooth", 0.5), ]) # Start Function def start(self, args): self.active = args["Activate"] self.mouseSens = args["Mouse Sensibility"] * (-0.001) self.invertX = [1, -1][args["Invert Mouse X Axis"]] self.invertY = [1, -1][args["Invert Mouse Y Axis"]] # Camera Position self.__cameraPos = Vector([0, 0, 0]) self.setCameraPos(args["Camera Crab (Side)"], -args["Camera Distance"], args["Camera Height"]) self.cameraCol = args["Camera Collision"] self.cameraColProp = args["Camera Collision Property"] self.__camAlign = [] self.setCameraAlign(args["Align Player to View"]) self.camAlignSmooth = args["Align Player Smooth"] # To catch errors self.__error = (self.object.parent == None) if self.__error: print( "[Third Person Camera] Error: The camera must be parent to an object." ) # Private Variables self.__cameraPan = Matrix.Identity(3) # Rotate around Z axis (global) self.__cameraTilt = Matrix.Identity(3) # Rotate around X axis (local) self.__playerPos = None if not self.__error: self.__playerPos = self.object.parent.worldPosition.copy() ####-<PRIVATE FUNCTIONS>-###################################################### # Private function: Rotate on the Z axis (pan) def __pan(self, angle): xyz = self.__cameraPan.to_euler() xyz[2] += angle self.__cameraPan = xyz.to_matrix() # Private function: Rotate on the X axis (Tilt) def __tilt(self, angle): xyz = self.__cameraTilt.to_euler() xyz[0] += angle self.__cameraTilt = xyz.to_matrix() # Private function: Gets the world camera position # (based on the tilt and pan) def __getWorldCameraPos(self): vec = self.__cameraPos.copy() vec = self.__cameraTilt * vec vec = self.__cameraPan * vec return self.object.parent.worldPosition + vec # Private function: Defines a rotation limit to the camera to avoid # it from rotating too much (and gets upside down) def __limitCameraRot(self): xyz = self.__cameraTilt.to_euler() if xyz[0] > 1.4: xyz[0] = 1.4 elif xyz[0] < -1.4: xyz[0] = -1.4 self.__cameraTilt = xyz.to_matrix() # Private function: Verifies if the player is moving def __getPlayerMovementStatus(self): flag = False vec = self.__playerPos - self.object.parent.worldPosition.copy() if vec.length > 0.001: flag = True self.__playerPos = self.object.parent.worldPosition.copy() return flag # Private function: Applies the camera position def __applyCameraPosition(self): camPos = self.__getWorldCameraPos() if self.cameraCol: target = self.object.parent.worldPosition + \ Vector([0, 0, self.__cameraPos[2] * 0.5]) obHit, obPos, _ = self.object.rayCast(target, camPos, 0, self.cameraColProp, 1, 0, 0) if obHit != None: camPos = obPos self.object.worldPosition = camPos align = self.__cameraTilt * Vector([0, 1, 0]) align = self.__cameraPan * align self.object.alignAxisToVect([0, 0, 1], 1, 1) self.object.alignAxisToVect(align * (-1), 2, 1) ####-<PUBLIC FUNCTIONS>-####################################################### # Public function to change the camera alignment. def setCameraAlign(self, type): self.__camAlign = { "Never": [0, 0], "On Player Movement": [0, 1], "Always": [1, 1] }[type] # Public function to change the camera position. def setCameraPos(self, x, y, z): self.__cameraPos = Vector([x, y, z]) # Mouselook function: Makes the mouse look at where you move your mouse. def mouselook(self): wSize = Vector( [bge.render.getWindowWidth(), bge.render.getWindowHeight()]) wCenter = Vector([int(wSize[0] * 0.5), int(wSize[1] * 0.5)]) mPos = Vector(bge.logic.mouse.position) mPos[0] = int(mPos[0] * wSize[0]) mPos[1] = int(mPos[1] * wSize[1]) bge.render.setMousePosition(int(wCenter[0]), int(wCenter[1])) mDisp = mPos - wCenter mDisp *= self.mouseSens # Invert Mouselook mDisp[0] *= self.invertX mDisp[1] *= self.invertY self.__pan(mDisp[0]) self.__tilt(mDisp[1]) self.__limitCameraRot() # Aligns the player to the Camera view def alignPlayerToView(self): vec = self.getCameraView() self.object.parent.alignAxisToVect(vec, 1, 1.0 - self.camAlignSmooth) self.object.parent.alignAxisToVect([0, 0, 1], 2, 1) # Returns the camera view direction def getCameraView(self): return self.__cameraPan * Vector([0, 1, 0]) # Update Function def update(self): if self.active and not self.__error: self.mouselook() if self.__camAlign[self.__getPlayerMovementStatus()]: self.alignPlayerToView() self.__applyCameraPosition()
class RayCaster(): ''' This class is an extension of a mesh object's ray cast method to make it more convenient, specifically for the purpose of casting a ray from region space coordinates such as the mouse cursor. ''' def __init__(self): self.coordinate_system = 'OBJECT' self.mesh_object = None self.ray_origin = Vector() self.ray_target = Vector() def set_ray_from_region(self, x, y): context = bpy.context mesh_object = self.mesh_object region = context.region region_co = Vector((x, y)) rv3d = context.region_data sv3d = context.space_data # Determine the view's clipping distances. if rv3d.view_perspective == 'CAMERA': camera_data = sv3d.camera.data clip_start = camera_data.clip_start clip_end = camera_data.clip_end else: clip_start = sv3d.clip_start clip_end = sv3d.clip_end # Determine the ray's direction in world space. ray_direction = view3d_utils.region_2d_to_vector_3d( region, rv3d, region_co) ray_direction.normalize() # For orthographic projections in Blender versions prior to 2.72, the # ray's direction needs to be inverted to point into the scene. if bpy.app.version < (2, 72, 0): if rv3d.view_perspective == 'ORTHO' or ( rv3d.view_perspective == 'CAMERA' and sv3d.camera.data.type == 'ORTHO'): ray_direction *= -1 # Determine the ray's origin in world space. ray_origin =\ view3d_utils.region_2d_to_origin_3d(region, rv3d, region_co) # Determine the ray's target in world space. ray_target = ray_origin + clip_end * ray_direction # If the view is an orthographic projection, the ray's origin may exist # behind the mesh object. Therefore, it is necessary to move the ray's # origin a sufficient distance antiparallel to the ray's direction to # ensure that the ray's origin is in front of the mesh object. if rv3d.view_perspective == 'ORTHO': ray_origin -= 1000 * ray_direction # Otherwise, if the view is a perspective projection or projected from # a camera then advance the ray's origin to the near clipping plane. else: ray_origin += clip_start * ray_direction # Convert the ray's origin and target from world space to object space, # if necessary. if self.coordinate_system == 'OBJECT': inverse_model_matrix = mesh_object.matrix_world.inverted() for co in ray_origin, ray_target: co.xyz = inverse_model_matrix * co # Set the ray caster object's ray attributes. self.ray_origin = ray_origin self.ray_target = ray_target def ray_cast(self): mesh_object = self.mesh_object polygons = mesh_object.data.polygons # The mesh object's ray cast method is valid only if polygons are # present. if not polygons: raise Exception( ("'{0}' has no polygons available for ray intersection " + "testing.").format(mesh_object.name)) # The mesh object's ray cast data is not accessible in Edit mode. if mesh_object.mode == 'EDIT': bpy.ops.object.mode_set(mode='OBJECT') # Convert the ray's origin and target from world space to object space, # if necessary. if self.coordinate_system == 'WORLD': inverse_model_matrix = mesh_object.matrix_world.inverted() ray_origin = self.ray_origin.copy() ray_target = self.ray_target.copy() for co in ray_origin, ray_target: co.xyz = inverse_model_matrix * co else: ray_origin = self.ray_origin ray_target = self.ray_target # Perform the ray cast intersection test. if bpy.app.version < (2, 76, 9): location, normal, face_index =\ mesh_object.ray_cast(ray_origin, ray_target) else: hit, location, normal, face_index =\ mesh_object.ray_cast(ray_origin, ray_target) # Convert the object space intersection information to world space, if # necessary. if self.coordinate_system == 'WORLD': model_matrix = mesh_object.matrix_world for co in location, normal: co.xyz = model_matrix * co normal = (normal - mesh_object.location).normalized() # Return the intersection information. return (location, normal, face_index)
class MESH_OT_bm_fake_knife(bpy.types.Operator): bl_label = 'BMesh Fake Knife' bl_idname = 'mesh.bm_fake_knife' bl_description = 'Line knife' bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} # dir(points) >> ['__doc__', 'add', 'clear', 'move', 'remove'] points = bpy.props.CollectionProperty( type=Point, options={'HIDDEN', 'SKIP_SAVE', 'ANIMATABLE'}) perspective_matrix = bpy.props.FloatVectorProperty( name='Perspective Matrix', subtype='MATRIX', size=16, options={'HIDDEN', 'SKIP_SAVE', 'ANIMATABLE'}) deselect = bpy.props.BoolProperty( name='Deselect', default=True) split_faces = bpy.props.BoolProperty( name='Split Faces', default=True) @classmethod def poll(cls, context): return context.mode == 'EDIT_MESH' def draw_callback(self, context): if context.region != self.region: return box_size = 10 circle_size = 12 region = context.region rv3d = context.region_data bgl.glColor4f(1.0, 1.0, 1.0, 1.0) blf.position(0, 70, 30, 0) blf.draw(0, self.snap_type) # axis if self.axis is not None: blf.position(0, 70, 45, 0) blf.draw(0, str(math.degrees(self.axis))) bgl.glEnable(bgl.GL_BLEND) """# axis if self.axis is not None: v = vav.project(region, rv3d, self.points[-2]) bgl.glColor4f(1.0, 1.0, 1.0, 0.5) bgl.glBegin(bgl.GL_LINE_STRIP) bgl.glVertex2f() bgl.glVertex2f() bgl.glEnd() """ # snap bgl.glColor4f(1.0, 1.0, 1.0, 1.0) if self.snap_vector: v = vav.project(region, rv3d, self.snap_vector) if self.snap_grid: xmin = v[0] - box_size / 2 ymin = v[1] - box_size / 2 w = box_size h = box_size vagl.draw_box(xmin, ymin, w, h, poly=False) else: vagl.draw_circle(v[0], v[1], circle_size / 2, 16, poly=False) """# line bgl.glColor4f(1.0, 1.0, 1.0, 0.2) bgl.glBegin(bgl.GL_LINE_STRIP) for point in self.points[:len(self.points) - 1]: # [:-1]がバグってる bgl.glVertex2f(*vav.project(region, rv3d, point.co).to_2d()) bgl.glEnd() """ # line bgl.glColor4f(1.0, 1.0, 1.0, 0.2) for polyline in self.polylines: bgl.glBegin(bgl.GL_LINE_STRIP) for vec in polyline.point_coords: bgl.glVertex2f(*vav.project(region, rv3d, vec).to_2d()) bgl.glEnd() # point x_size2 = 3 bgl.glColor4f(1.0, 1.0, 1.0, 1.0) bgl.glBegin(bgl.GL_LINES) for polyline in self.polylines: for vec in polyline.point_coords[1:]: v = vav.project(region, rv3d, vec) bgl.glVertex2f(v[0] - x_size2, v[1] - x_size2) bgl.glVertex2f(v[0] + x_size2, v[1] + x_size2) bgl.glVertex2f(v[0] - x_size2, v[1] + x_size2) bgl.glVertex2f(v[0] + x_size2, v[1] - x_size2) bgl.glEnd() # current line bgl.glColor4f(1.0, 1.0, 1.0, 1.0) bgl.glBegin(bgl.GL_LINE_STRIP) for point in self.points[-2:]: bgl.glVertex2f(*vav.project(region, rv3d, point.co).to_2d()) bgl.glEnd() # mouse cross_size = 10 cross_ofs = 30 mco = self.mco bgl.glColor4f(1.0, 1.0, 1.0, 0.7) bgl.glBegin(bgl.GL_LINES) bgl.glVertex2f(mco[0] + cross_ofs, mco[1]) # right bgl.glVertex2f(mco[0] + cross_ofs + cross_size, mco[1]) bgl.glVertex2f(mco[0] - cross_ofs, mco[1]) # left bgl.glVertex2f(mco[0] - cross_ofs - cross_size, mco[1]) bgl.glVertex2f(mco[0], mco[1] + cross_ofs) # top bgl.glVertex2f(mco[0], mco[1] + cross_ofs + cross_size) bgl.glVertex2f(mco[0], mco[1] - cross_ofs) # bottom bgl.glVertex2f(mco[0], mco[1] - cross_ofs - cross_size) bgl.glEnd() def persp_fac_to_world_fac(self, fac, inverted_perspective_matrix, v1, v2): """v1,v2はpersp座標""" v3 = v1 + (v2 - v1) * fac v4 = v3.copy() v3[2] = 0.0 v4[2] = 0.5 vec1 = mul_persp(inverted_perspective_matrix, v1) vec2 = mul_persp(inverted_perspective_matrix, v2) vec3 = mul_persp(inverted_perspective_matrix, v3) vec4 = mul_persp(inverted_perspective_matrix, v4) i1, i2 = geo.intersect_line_line(vec1, vec2, vec3, vec4) vec12 = vec2 - vec1 return vec12.dot(i1 - vec1) / vec12.length ** 2 def calc_verts_on_line(self, verts, p1, p2, threshold=THRESHOLD): # 頂点とp1-p2ラインとの交差判定 verts_on_line = set() for eve in verts: v = self.persp_coords[eve].to_2d() if isect_point_line_v2(v, p1, p2, threshold): verts_on_line.add(eve) return verts_on_line def point_is_inside_of_tri(self, p, tri, threshold=THRESHOLD, exclude_edges=False): """ pがtriの中に含まれるか調べる。 p: 2d persp coord exclude_edges: triの三辺上に来る場合は偽とする。 """ tri_vecs = [self.persp_coords[loop.vert].to_2d() for loop in tri] # 三角形の三辺上にくるか調べる for i in range(3): v1 = tri_vecs[i - 1] v2 = tri_vecs[i] if isect_point_line_v2(p, v1, v2, threshold): if exclude_edges: return False else: return True return bool(geo.intersect_point_tri_2d(p, *tri_vecs)) def point_is_inside_of_tris(self, p, tris, threshold=THRESHOLD, exclude_edges=False): """ pがtrisの中に含まれるか調べる。 p: 2d persp coord exclude_edges: trisの外周上に来る場合は偽とする。 """ edge_key_tris = tris.vert_pair_dict() for tri in tris: is_inside = self.point_is_inside_of_tri(p, tri, threshold) if is_inside and exclude_edges: for i in range(3): eve1 = tri[i - 1].vert eve2 = tri[i].vert #ekey = tris.make_edge_key(eve1, eve2) ekey = tris.hash_sorted((eve1, eve2)) if len(edge_key_tris[ekey]) == 1: # 外周 v1 = self.persp_coords[eve1].to_2d() v2 = self.persp_coords[eve2].to_2d() if isect_point_line_v2(p, v1, v2, threshold): is_inside = False if is_inside: return True return False def search_end_vert(self, context, pmat, polyline): # polylineで面を切るvertと、必要ならベクトルを求める obmat = context.active_object.matrix_world obimat = obmat.inverted() efa = polyline.face face_tris = vabm.LoopTris.from_faces([efa]) face_tris.correct() vert_tris = face_tris.vert_dict() # 最後のpointが面の外ならこのループ以降判定しない p = mul_persp(pmat, polyline.point_coords[-1]).to_2d() if not self.point_is_inside_of_tris(p, face_tris): polyline.is_valid = False p1 = mul_persp(pmat, polyline.point_coords[0]).to_2d() p2 = mul_persp(pmat, polyline.point_coords[1]).to_2d() verts_on_line = self.calc_verts_on_line(efa.verts, p1, p2) end_vert = None for loop1 in efa.loops: if loop1.vert == polyline.vert: break for tri in vert_tris[loop1.vert]: #print('current tri', [l.vert.index for l in tri]) polyline.clear() tri_loops_on_line = [loop for loop in tri if loop.vert in verts_on_line] if len(tri_loops_on_line) == 3: continue elif len(tri_loops_on_line) == 2: tri_loops_on_line.remove(loop1) end_vert = tri_loops_on_line[0].vert break i = tri.index(loop1) loop_pairs = [(tri[i - 2], tri[i - 1])] cnt = -1 while True: cnt += 1 if len(loop_pairs) != 1: # 二巡目以降 #print('current tri', [l.vert.index for l in tri]) if tri_loops_on_line: end_vert = tri_loops_on_line[0].vert break for l2, l3 in loop_pairs: """ while 一巡目 while 二巡目以降 loop1 | l2 __|__ l2 /|\ \ | / l2 /___\l3 l3 \ / """ v2 = self.persp_coords[l2.vert].to_2d() v3 = self.persp_coords[l3.vert].to_2d() ivec, ivec_p1, ivec_p2 = isect_line_line_v2(v2, v3, p1, p2) if ivec: for next_tri in vert_tris[l2.vert]: if l3 in next_tri and next_tri != tri: break tri = next_tri t = list(tri) t.remove(l2) if l3 not in t: # バグ回避 break t.remove(l3) # たまにバグる。何処が原因かさっぱり l0 = t[0] loop_pairs = [(l2, l0), (l3, l0)] tri_loops_on_line = [loop for loop in tri if loop.vert in verts_on_line] break else: # 内側に存在するか inside = self.point_is_inside_of_tri(p2, tri, exclude_edges=True) if inside: # and len(polyline.point_coords) > 2: # polyline.coordsを求める vec1 = self.persp_coords[tri[0].vert] vec2 = self.persp_coords[tri[1].vert] vec3 = self.persp_coords[tri[2].vert] ivec = isect_point_tri_persp_to_world(pmat, p2, vec1, vec2, vec3) if ivec: # 必ず交差するはずだが…… polyline.coords.append(obimat * ivec) polyline.point_index += 1 if len(polyline.point_coords) - polyline.point_index < 2: break loop_pairs = [(tri[i], tri[i - 1]) for i in range(3)] i = polyline.point_index p1 = mul_persp(pmat, polyline.point_coords[i]).to_2d() p2 = mul_persp(pmat, polyline.point_coords[i + 1]).to_2d() verts_on_line = self.calc_verts_on_line(efa.verts, p1, p2) tri_loops_on_line = [loop for loop in tri if loop.vert in verts_on_line] else: break if end_vert: break return end_vert def line_cut(self, context, pmat, p1wld:'world co', p2wld:'world co'): pimat = pmat.inverted() obmat = context.active_object.matrix_world p1 = mul_persp(pmat, p1wld).to_2d() p2 = mul_persp(pmat, p2wld).to_2d() p1_p2 = p2 - p1 if p1_p2.length == 0.0: return # 頂点とラインとの交差判定 # set of BMVert self.verts_on_line = self.calc_verts_on_line(self.bm.verts, p1, p2) intersected = [] # edge_splitの引数(edge, facs) # edge.verts[0]基準 # 辺を分割する位置を求める for eed in self.bm.edges: if not eed.select or eed.hide: continue mv1, mv2 = eed.verts # Get 2D coords of edge's verts v1 = self.persp_coords[mv1].to_2d() v2 = self.persp_coords[mv2].to_2d() v1_v2 = v2 - v1 ivec, ivec_p1, ivec_p2 = isect_line_line_v2(v1, v2, p1, p2) factors = [] # 0 ~ 2 for v in (ivec, ivec_p1, ivec_p2): if v and (v != v1 and v != v2): fac = v1_v2.dot(v - v1) / v1_v2.length ** 2 fac_world = self.persp_fac_to_world_fac( fac, pimat, self.persp_coords[mv1], self.persp_coords[mv2]) factors.append(fac_world) if factors: factors.sort() intersected.append((eed, factors)) # 辺を分割 for eed, factors in intersected: eve = eed.verts[0] ofs = 0 vnum = len(self.bm.verts) enum = len(self.bm.edges) for i, fac in enumerate(factors): f = fac - ofs eed2, eve = bmesh.utils.edge_split(eed, eve, f) eve.index = vnum + i eed2.index = enum + i eed2.select = eed.select ofs += fac self.verts_on_line.add(eve) self.persp_coords[eve] = mul_persp(pmat, obmat * eve.co) self.select_verts.append(eve) if eed in self.select_edges: self.select_edges.append(eed2) # 既存のpolylineへのポイント追加 for polyline in self.polylines: polyline.point_coords.append(p2wld.copy()) # copyしとかないと落ちる # polylinesへの新規登録 for efa in self.bm.faces: if not efa.select or efa.hide: continue for eve in self.verts_on_line.intersection(efa.verts): # p2が頂点と重なる場合はパス if (self.persp_coords[eve].to_2d() - p2).length >= THRESHOLD: polyline = PolyLine(efa, eve, [p1wld.copy(), p2wld.copy()]) self.polylines.append(polyline) polyline_index = 0 while polyline_index < len(self.polylines): polyline = self.polylines[polyline_index] if not polyline.is_valid: polyline_index += 1 continue efa = polyline.face face_verts = list(efa.verts) end_vert = self.search_end_vert(context, pmat, polyline) if not end_vert: polyline_index += 1 continue start_loop = efa.loops[face_verts.index(polyline.vert)] end_loop = efa.loops[face_verts.index(end_vert)] # 分割 subdivide = False if start_loop != end_loop: # start_loop == end_loopはどうやっても不可 if polyline.coords: subdivide = True else: if start_loop.link_loop_next != end_loop and \ start_loop.link_loop_prev != end_loop: subdivide = True if subdivide and self.split_faces: vnum = len(self.bm.verts) findex = efa.index new_face, new_loop = bmesh.utils.face_split( efa, polyline.vert, end_vert, polyline.coords) new_face.select = efa.select # face_split()でcoordsを指定した場合(空の時は指定しない場合と同じ)、 # 元のface.is_validはFalseとなり # 新規のfaceが2つ追加される。但し、一方のface.indexは元のfaceと同じ、 # bm.facesでも同位置にある # memcpyでも行われたか? # インデックスアクセスの前にensure_lookup_table()が必要 self.bm.faces.ensure_lookup_table() old_face = self.bm.faces[findex] # インデックス new_face.index = len(self.bm.faces) - 1 self.bm.edges.index_update() # coordsを指定した分割では、bm.vertsの順番がバラバラになる事がある。 update = False for i, eve in enumerate(self.bm.verts[vnum:]): if eve.index == -1: eve.index = vnum + i self.persp_coords[eve] = mul_persp(pmat, obmat * eve.co) else: update = True if update: for i, eve in enumerate(self.bm.verts): if eve.index == -1: print(i, eve) self.persp_coords[eve] = mul_persp(pmat, obmat * eve.co) self.bm.verts.index_update() # 古い面と新規の二つの面から共通する辺を求めて選択する for eed in old_face.edges: if eed in new_face.edges: self.select_edges.append(eed) # 一度分割したら用済みなので消す polyline.is_valid = False # 他のpolylineのfaceを確認して必要ならPolyLineの追加・無効化 for pl in self.polylines[:]: if not pl.is_valid or pl.face != efa: continue pl.face = old_face # pl.face.is_valid == False # new if pl.vert in new_face.verts: new_polyline = pl.copy() new_polyline.face = new_face self.polylines.append(new_polyline) # is_valid = False if pl.vert not in pl.face.verts: pl.is_valid = False polyline_index += 1 # remove for polyline in self.polylines[:]: if not polyline.is_valid: self.polylines.remove(polyline) def selection_update(self, context): #self.bm.select_history.clear() if self.deselect: for v in self.bm.verts: v.select = False for e in self.bm.edges: e.select = False for f in self.bm.faces: f.select = False for v in self.select_verts: if v.is_valid: v.select = True #self.bm.select_history.add(v) for e in self.select_edges: if e.is_valid: #self.bm.select_history.add(e) e.select = True e.verts[0].select = True e.verts[1].select = True if self.bm.select_mode == {'FACE'}: for f in self.bm.faces: select = True for v in f.verts: if not v.select: select = False f.select = select """if 'EDGE' not in self.bm.select_mode: for e in f.edges: e.select = select if 'VERT' not in self.bm.select_mode: for v in f.verts: v.select = select """ self.bm.select_flush_mode() # self.bm.normal_update() def execute(self, context): actob = context.active_object obmat = actob.matrix_world context.tool_settings.mesh_select_mode = self.select_mode if hasattr(self, 'bm_bak'): bpy.ops.object.mode_set(mode='OBJECT') self.bm_bak.to_mesh(actob.data) bpy.ops.object.mode_set(mode='EDIT') self.init_exec_attrs(context) pmat_pre = None for pmat, point_coords in self.points_executed: if pmat != pmat_pre: self.persp_coords = {eve: mul_persp(pmat, obmat * eve.co) for eve in self.bm.verts} pmat_pre = pmat self.polylines[:] = [] for i in range(len(point_coords) - 1): p1co = point_coords[i] p2co = point_coords[i + 1] self.line_cut(context, pmat, p1co, p2co) pmat = self.perspective_matrix if pmat != pmat_pre: self.persp_coords = {eve: mul_persp(pmat, obmat * eve.co) for eve in self.bm.verts} self.polylines[:] = [] for i in range(len(self.points) - 1): p1co = self.points[i].co p2co = self.points[i + 1].co self.line_cut(context, pmat, p1co, p2co) self.selection_update(context) # actob.update_tag({'OBJECT', 'DATA'}) # Modifierを更新 context.area.tag_redraw() return {'FINISHED'} def check_view_lock(self, context): self.view_move_zoom_lock = False self.view_rotate_lock = False for polyline in self.polylines: if len(polyline.point_coords) > 1: self.view_rotate_lock = True if context.region_data.view_perspective != 'ORTHO': self.view_move_zoom_lock = True def modal(self, context, event): if event.type == 'INBETWEEN_MOUSEMOVE': return {'PASS_THROUGH'} area = context.area region = context.region rv3d = context.region_data pmat = rv3d.perspective_matrix actob = context.active_object obmat = actob.matrix_world if len(self.points) == 0: self.points.add() mco = self.mco = Vector((event.mouse_region_x, event.mouse_region_y)) # マウス座標を3D化。Z値は直前のKnifePointを参照する。 mco3d = mco.to_3d() mco3d[2] = 0.5 # 0以外を指定しておく if len(self.points) >= 2: v_win = vav.project(region, rv3d, self.points[-2].co) mco3d[2] = v_win[2] mco_wld = vav.unproject(region, rv3d, mco3d) self.points[-1].co[:] = mco_wld # perspective座標更新 if pmat != self.perspective_matrix: self.persp_coords = {eve: mul_persp(pmat, obmat * eve.co) for eve in self.bm.verts} self.perspective_matrix = flatten_matrix(pmat) final_vector = self.points[-1].co.copy() if event.ctrl: # スナップ if event.shift: # グリッドにスナップ。 self.snap_grid = True """v3d = context.space_data if hasattr(v3d, 'use_local_grid') and v3d.use_local_grid: origin = v3d.local_grid_location quat = v3d.local_grid_rotation else: origin = quat = None """ unit_system = unitsystem.UnitSystem(context) final_vector = unit_system.snap_local_grid(context, mco_wld, event.alt) self.snap_vector = final_vector else: self.snap_grid = False result = self.snap_objects.snap(context, mco, self.snap_type) if result: if result['object'] == actob and self.snap_type == 'EDGE' \ and (event.alt or event.oskey): eed = self.bm.edges[result['index']] vec = obmat * ((eed.verts[0].co + eed.verts[1].co) / 2) else: vec = result['location'] final_vector = self.snap_vector = vec else: self.snap_vector = None else: self.snap_vector = None if self.axis is not None: # snap_vecを修正してself.points[-1]を置き換える axis_vector = Vector((math.cos(self.axis), math.sin(self.axis), 0)) v1 = vav.project(region, rv3d, self.points[-2].co) v2 = vav.project(region, rv3d, final_vector) v_on_axis = v1 + (v2 - v1).project(axis_vector) v_on_axis[2] = v2[2] final_vector = vav.unproject(region, rv3d, v_on_axis) self.points[-1].co[:] = final_vector[:] #print(event.type, event.value) if event.type in ('LEFT_CTRL', 'LEFT_ALT', 'LEFT_SHIFT', 'RIGHT_ALT', 'RIGHT_CTRL', 'RIGHT_SHIFT'): context.area.tag_redraw() elif event.type == 'TAB' and event.value == 'PRESS': if self.snap_type == 'VERTEX': self.snap_type = 'EDGE' else: self.snap_type = 'VERTEX' context.area.tag_redraw() elif event.type == 'C' and event.value == 'PRESS': if self.axis is not None: self.axis = None elif len(self.points) >= 2: v1 = vav.project(region, rv3d, self.points[-2].co).to_2d() v2 = vav.project(region, rv3d, self.points[-1].co).to_2d() v = v2 - v1 if len(v) > 1.0: angle = math.atan2(v[1], v[0]) if event.ctrl: self.axis = angle else: pi8 = math.pi / 4 for i in range(9): a = -math.pi + pi8 * i if a - pi8 / 2 <= angle < a + pi8 / 2: self.axis = a break if self.axis == -math.pi: self.axis = math.pi context.area.tag_redraw() elif event.type == 'E' and event.value == 'PRESS': coords = [p.co.copy() for p in self.points] pmat = self.perspective_matrix.copy() self.points_executed.append([pmat, coords]) self.points.clear() self.polylines[:] = [] context.area.tag_redraw() elif event.type == 'F' and event.value == 'PRESS': self.split_faces = not self.split_faces elif event.type == 'Z' and event.value == 'PRESS': if not (event.shift or event.ctrl or event.alt or event.oskey): return {'PASS_THROUGH'} elif event.type == 'D' and event.value == 'PRESS': if event.alt and \ not event.shift and not event.ctrl and not event.oskey: return {'PASS_THROUGH'} elif event.type == 'MOUSEMOVE': context.area.tag_redraw() elif event.type == 'LEFTMOUSE' and event.value == 'PRESS': self.axis = None if len(self.points) >= 2: point1 = self.points[-2] point2 = self.points[-1] if point1.co == point2.co: #if self.mco == self.mco_bak: self.selection_update(context) context.space_data.draw_handler_remove(self._handle, 'WINDOW') area.header_text_set() context.area.tag_redraw() self.snap_objects.free() return {'FINISHED'} else: self.mco_bak = self.mco.copy() self.line_cut(context, pmat, point1.co, point2.co) # actob.update_tag({'OBJECT', 'DATA'}) # Modifierを更新 self.bm.normal_update() bmesh.update_edit_mesh(actob.data, True, True) point = self.points.add() point.co = self.points[-2].co.copy() context.area.tag_redraw() self.snap_objects.update(context) elif event.type == 'MIDDLEMOUSE': # 視点変更 self.check_view_lock(context) if event.shift and not event.ctrl and not event.alt \ and not event.oskey: # move if not self.view_move_zoom_lock: bpy.ops.view3d.move('INVOKE_DEFAULT') elif event.ctrl and not event.shift and not event.alt \ and not event.oskey: # zoom if not self.view_move_zoom_lock: bpy.ops.view3d.zoom('INVOKE_DEFAULT') elif not event.shift and not event.ctrl and not event.alt \ and not event.oskey: # rotation if not self.view_rotate_lock: bpy.ops.view3d.rotate('INVOKE_DEFAULT') elif event.type in ('WHEELDOWNMOUSE', 'WHEELUPMOUSE'): # 視点変更 self.check_view_lock(context) if event.shift and not event.ctrl and not event.alt \ and not event.oskey: # move vertical if not self.view_move_zoom_lock: if event.type == 'WHEELDOWNMOUSE': pan_type = 'PANDOWN' else: pan_type = 'PANUP' bpy.ops.view3d.view_pan('INVOKE_DEFAULT', type=pan_type) elif event.ctrl and not event.shift and not event.alt \ and not event.oskey: # move horizontal if not self.view_move_zoom_lock: if event.type == 'WHEELDOWNMOUSE': pan_type = 'PANLEFT' else: pan_type = 'PANRIGHT' bpy.ops.view3d.view_pan('INVOKE_DEFAULT', type=pan_type) elif not event.shift and not event.ctrl and not event.alt \ and not event.oskey: # zoom if not self.view_move_zoom_lock: if event.type == 'WHEELDOWNMOUSE': delta = -1 else: delta = 1 bpy.ops.view3d.zoom(delta=delta) elif event.type in ('ENTER', 'RET', 'SPACE'): #self.points[-1:] = [] # 未確定のマウス位置のポイントを排除 #self.execute(context) self.selection_update(context) context.space_data.draw_handler_remove(self._handle, 'WINDOW') area.header_text_set() context.area.tag_redraw() self.snap_objects.free() return {'FINISHED'} elif event.type in ('RIGHTMOUSE', 'ESC'): # 中止 bpy.ops.object.mode_set(mode='OBJECT') self.bm_bak.to_mesh(context.active_object.data) self.bm_bak.free() bpy.ops.object.mode_set(mode='EDIT') context.space_data.draw_handler_remove(self._handle, 'WINDOW') area.header_text_set() context.area.tag_redraw() self.snap_objects.free() return {'CANCELLED'} texts = ['LMB: define cut lines', 'Return/ Spacebar: confirm', 'Esc / RMB: cancel', 'E: new cut', 'F: split faces ({0})', 'Tab: toggle snap type', 'Ctrl: snap {1}', 'Ctrl + Shift: snap grid', '(Ctrl + (Shift +)) C: angle constraint'] text = ', '.join(texts) bool_str = {True: 'On', False: 'Off'} text = text.format(bool_str[self.split_faces], self.snap_type.title()) area.header_text_set(text) return {'RUNNING_MODAL'} def init_exec_attrs(self, context): actob = context.active_object obmat = actob.matrix_world self.bm = bmesh.from_edit_mesh(actob.data) self.bm.verts.index_update() self.bm.edges.index_update() self.bm.faces.index_update() # キャンセル時の為 if not hasattr(self, 'bm_bak'): self.bm_bak = self.bm.copy() # [[pmat, coords], ,,,] Eキーでクリアした物をこっちへ移動。 if not hasattr(self, 'points_executed'): self.points_executed = [] if not hasattr(self, 'select_mode'): self.select_mode = list(context.tool_settings.mesh_select_mode) self.polylines = [] # list of Polyline self.select_verts = [] # 最後にこの要素だけを選択状態にする self.select_edges = [] # 最後にこの要素だけを選択状態にする def invoke(self, context, event): v3d = context.space_data rv3d = context.region_data pmat = rv3d.perspective_matrix.copy() if len(self.points): if self.perspective_matrix.median_scale == 0.0: if v3d.type != 'VIEW_3D': txt = 'Need "perspective_matrix" argument if not View3d' self.report({'WARNING'}, txt) return {'CANCELLED'} self.perspective_matrix = flatten_matrix(pmat) self.execute(context) return {'FINISHED'} if v3d.type != 'VIEW_3D': self.report({'WARNING'}, 'Active space must be a View3d') return {'CANCELLED'} context.window_manager.modal_handler_add(self) self._handle = context.space_data.draw_handler_add(self.draw_callback, (context,), 'WINDOW', 'POST_PIXEL') self.region = context.region self.init_exec_attrs(context) self.perspective_matrix = flatten_matrix(pmat) self.mco = Vector((event.mouse_region_x, event.mouse_region_y)) self.mco_bak = self.mco.copy() actob = context.active_object obmat = actob.matrix_world self.persp_coords = {eve: mul_persp(pmat, obmat * eve.co) for eve in self.bm.verts} self.snap_type = 'VERTEX' # or 'EDGE'. toggle TAB key self.snap_grid = False # False: vert or edge, True: grid self.snap_vector = None # Vector self.axis = None # float. toggle C key self.snap_objects = SnapObjects(context) # モード決定 (solidなら、ナイフのZ:cut through(OFF)と同様の挙動) if v3d.viewport_shade == 'RENDERED': self.mode = 'solid' elif v3d.viewport_shade in ('BOUNDBOX', 'WIREFRAME') or \ not v3d.use_occlude_geometry: self.mode = 'wire' else: self.mode = 'solid' # 視点変更 self.view_move_zoom_lock = False self.view_rotate_lock = False context.area.tag_redraw() return {'RUNNING_MODAL'}
t1 = numer / denom co = Vector( [ getattr( p1, i ) + t1 * getattr( v1, i ) for i in 'xyz' ] ) return co pt = Vector((0,1,0)) n = 9 a = Euler(( 0, 0, radians( 360 / n ) )) bm = bmesh.new() for i in range(n): v1 = bm.verts.new(( pt )) # This is the next point next = pt.copy() next.rotate(a) # The point after that nextAgain = next.copy() nextAgain.rotate(a) # One before - rotated in opposite direction prev = pt.copy() prev.rotate( Euler([ ai * -1 for ai in a ]) ) edge1 = ( pt, nextAgain ) edge2 = ( prev, next ) intersection = find_intersection([ edge1, edge2 ])
def widget_iter_pose_rotate(context, mpr, ob, fmap, fmap_target): from mathutils import ( Vector, Quaternion, ) # generic initialize if USE_VERBOSE: print("(iter-init)") tweak_attr = pose_bone_rotation_attr_from_mode(fmap_target) context.area.header_text_set("Rotating ({}) face-map: {}".format(tweak_attr, fmap.name)) tweak_attr_lock = "lock_rotation" # invoke() # ... if USE_VERBOSE: print("(iter-invoke)") event = yield tweak = set() # modal(), first step mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) mval = mval_init.copy() # impl vars pose_bone = fmap_target del fmap_target # Could use face-map center too # Don't update these while interacting bone_matrix_init = pose_bone.matrix.copy() depth_location = bone_matrix_init.to_translation() rot_center = bone_matrix_init.to_translation() world_to_local_3x3 = pose_bone_calc_transform_orientation(pose_bone) # for rotation local_view_vector = (calc_view_vector(context) * world_to_local_3x3).normalized() rot_init = getattr(pose_bone, tweak_attr).copy() # Keep this loop fast! runs on mouse-movement. while True: event, tweak_next = yield if event in {True, False}: break tweak = tweak_next if USE_VERBOSE: print("(iter-modal)", event, tweak) mval = Vector((event.mouse_region_x, event.mouse_region_y)) # calculate rotation matrix from input co_init, co = coords_to_loc_3d(context, (mval_init, mval), depth_location) # co_delta = world_to_local_3x3 * (co - co_init) input_scale = 1.0 is_precise = 'PRECISE' in tweak if is_precise: input_scale /= 10.0 if False: # Dial logic, not obvious enough unless we show graphical line to center... # but this is typically too close to the center of the face-map. rot_delta = (co_init - rot_center).rotation_difference(co - rot_center).to_matrix() else: # Steering wheel logic, left to rotate left, right to rotate right :) # use X-axis only # Calculate imaginary point as if mouse was moved 100px to the right # then transform to local orientation and use to see where the cursor is rotate_angle = ((mval.x - mval_init.x) / 100.0) rotate_angle *= input_scale if 'SNAP' in tweak: v = math.radians(1.0 if is_precise else 15.0) rotate_angle = round(rotate_angle / v) * v del v rot_delta = Quaternion(local_view_vector, rotate_angle).to_matrix() # rot_delta = (co_init - rot_center).rotation_difference(co - rot_center).to_matrix() rot_matrix = rot_init.to_matrix() rot_matrix = rot_matrix * rot_delta if tweak_attr == "rotation_quaternion": final_value = rot_matrix.to_quaternion() elif tweak_attr == "rotation_euler": final_value = rot_matrix.to_euler(pose_bone.rotation_mode, rot_init) else: assert(tweak_attr == "rotation_axis_angle") final_value = rot_matrix.to_quaternion().to_axis_angle() final_value = (*final_value[0], final_value[1]) # flatten pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, final_value) # exit() if USE_VERBOSE: print("(iter-exit)", event) if event is True: # cancel setattr(pose_bone, tweak_attr, rot_init) else: pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock) context.area.header_text_set()
def execute(self, context): selected_objects = context.selected_objects uv_obj = context.scene.objects.active wrap_name = uv_obj.name.replace('_WRAP', '') if len(selected_objects) < 2: self.report({'WARNING'}, "Select more objects") return {'CANCELLED'} if uv_obj not in selected_objects or '_WRAP' not in uv_obj.name: self.report({'WARNING'}, "Select WRAP object at the end of selection") return {'CANCELLED'} if not wrap_name in context.scene.objects: self.report({'WARNING'}, "No object " + wrap_name) return {'CANCELLED'} wrap_obj = context.scene.objects[wrap_name] if len(wrap_obj.data.polygons) != len(uv_obj.data.polygons): self.report({'WARNING'}, "Object " + wrap_name + " and object " + uv_obj.name + " have different faces count") return {'CANCELLED'} bvh = mathu.bvhtree.BVHTree.FromObject(uv_obj, context.scene) uv_matrix = uv_obj.matrix_world uv_matrix_inv = uv_matrix.inverted() wrap_matrix = wrap_obj.matrix_world wrap_matrix_inv = wrap_matrix.inverted() for the_obj in selected_objects: if the_obj != uv_obj: if self.copy_objects: # create new object new_mesh = the_obj.to_mesh(scene=context.scene, apply_modifiers=True, settings='PREVIEW') new_obj = bpy.data.objects.new(wrap_obj.name + '_WRAP', new_mesh) new_obj.select = True context.scene.objects.link(new_obj) new_obj.matrix_world = the_obj.matrix_world new_obj.data.update() final_obj = new_obj else: final_obj = the_obj # all verts if self.transform_objects: all_verts = [final_obj.location] else: all_verts = [] if final_obj.type == 'MESH': if final_obj.data.shape_keys: all_verts = final_obj.data.shape_keys.key_blocks[final_obj.active_shape_key_index].data else: all_verts = final_obj.data.vertices elif final_obj.type == 'CURVE': if final_obj.data.shape_keys: all_verts = final_obj.data.shape_keys.key_blocks[final_obj.active_shape_key_index].data else: for spline in final_obj.data.splines: if spline.type == 'BEZIER': for point in spline.bezier_points: all_verts.append(point) else: for point in spline.points: all_verts.append(point) # wrap main code for vert in all_verts: if self.transform_objects: vert_pos = vert # here vert is just object's location else: if final_obj.type == 'CURVE': vert_pos = Vector((vert.co[0], vert.co[1], vert.co[2])) vert_pos = final_obj.matrix_world * vert_pos else: vert_pos = final_obj.matrix_world * vert.co.copy() # near vert_pos_zero = vert_pos.copy() vert_pos_zero[1] = uv_obj.location[1] vert_pos_zero = uv_obj.matrix_world.inverted() * vert_pos_zero nearest = bvh.find_nearest(vert_pos_zero) if nearest and nearest[2] is not None: near_face = uv_obj.data.polygons[nearest[2]] near_center = uv_obj.matrix_world * near_face.center near_axis1 = ut_base.get_normal_world(near_face.normal, uv_matrix, uv_matrix_inv) near_v1 = uv_obj.matrix_world * uv_obj.data.vertices[near_face.vertices[0]].co near_v2 = uv_obj.matrix_world * uv_obj.data.vertices[near_face.vertices[1]].co near_axis2 = (near_v1 - near_v2).normalized() near_axis3 = near_axis1.cross(near_axis2).normalized() dist_1 = mathu.geometry.distance_point_to_plane(vert_pos, near_center, near_axis1) dist_2 = mathu.geometry.distance_point_to_plane(vert_pos, near_center, near_axis2) dist_3 = mathu.geometry.distance_point_to_plane(vert_pos, near_center, near_axis3) # wrap wrap_face = wrap_obj.data.polygons[nearest[2]] wrap_center = wrap_obj.matrix_world * wrap_face.center wrap_axis1 = ut_base.get_normal_world(wrap_face.normal, wrap_matrix, wrap_matrix_inv) wrap_v1 = wrap_obj.matrix_world * wrap_obj.data.vertices[wrap_face.vertices[0]].co wrap_v2 = wrap_obj.matrix_world * wrap_obj.data.vertices[wrap_face.vertices[1]].co wrap_axis2 = (wrap_v1 - wrap_v2).normalized() wrap_axis3 = wrap_axis1.cross(wrap_axis2).normalized() # move to face relative_scale = (wrap_v1 - wrap_center).length / (near_v1 - near_center).length new_vert_pos = wrap_center + (wrap_axis2 * dist_2 * relative_scale) + (wrap_axis3 * dist_3 * relative_scale) # interpolate between Face Normal and Point Normal if self.deform_normal == 'FaceAndVert': vert2_min = None vert2_min_dist = None vert2_pos_world = None for vert2_id in wrap_face.vertices: vert2 = wrap_obj.data.vertices[vert2_id] vert2_pos_world = wrap_obj.matrix_world * vert2.co v2_dist = (vert2_pos_world - new_vert_pos).length if not vert2_min: vert2_min = vert2 vert2_min_dist = v2_dist elif vert2_min_dist > v2_dist: vert2_min = vert2 vert2_min_dist = v2_dist vert2_min_nor = ut_base.get_normal_world(vert2_min.normal, wrap_matrix, wrap_matrix_inv) mix_val = 0.0 mix_v1 = (new_vert_pos - wrap_center).length mix_v2 = (vert2_pos_world - wrap_center).length if mix_v2 != 0: mix_val = min(mix_v1 / mix_v2, 1.0) wrap_normal = wrap_axis1.lerp(vert2_min_nor, mix_val).normalized() # Take just Face Normal else: wrap_normal = wrap_axis1 if self.normal_offset == 0: normal_dist = dist_1 * relative_scale else: normal_dist = dist_1 * self.normal_offset # Add normal direction to position new_vert_pos += (wrap_normal * normal_dist) # Mesh Vertex Transform! if not self.transform_objects: if final_obj.type == 'CURVE': new_vert_pos_world = final_obj.matrix_world.inverted() * new_vert_pos vert.co[0] = new_vert_pos_world[0] vert.co[1] = new_vert_pos_world[1] vert.co[2] = new_vert_pos_world[2] else: vert.co = final_obj.matrix_world.inverted() * new_vert_pos # Object Transform else: if self.normal_offset == 0: final_obj_scale = final_obj.scale * relative_scale else: final_obj_scale = final_obj.scale * self.normal_offset final_matrix = final_obj.matrix_world final_obj_axis1 = vert_pos + Vector((final_matrix[0][0], final_matrix[1][0], final_matrix[2][0])).normalized() # we substract here because Y axis is negative final_obj_axis2 = vert_pos - Vector((final_matrix[0][1], final_matrix[1][1], final_matrix[2][1])).normalized() ax1_dist_1 = mathu.geometry.distance_point_to_plane(final_obj_axis1, near_center, near_axis1) ax1_dist_2 = mathu.geometry.distance_point_to_plane(final_obj_axis1, near_center, near_axis2) ax1_dist_3 = mathu.geometry.distance_point_to_plane(final_obj_axis1, near_center, near_axis3) ax2_dist_1 = mathu.geometry.distance_point_to_plane(final_obj_axis2, near_center, near_axis1) ax2_dist_2 = mathu.geometry.distance_point_to_plane(final_obj_axis2, near_center, near_axis2) ax2_dist_3 = mathu.geometry.distance_point_to_plane(final_obj_axis2, near_center, near_axis3) ax1_normal_dist = ax1_dist_1 * relative_scale ax2_normal_dist = ax2_dist_1 * relative_scale ax1_vert_pos = wrap_center + (wrap_axis2 * ax1_dist_2 * relative_scale) + (wrap_axis3 * ax1_dist_3 * relative_scale) ax1_vert_pos += (wrap_normal * ax1_normal_dist) ax2_vert_pos = wrap_center + (wrap_axis2 * ax2_dist_2 * relative_scale) + (wrap_axis3 * ax2_dist_3 * relative_scale) ax2_vert_pos += (wrap_normal * ax2_normal_dist) final_obj_vec1 = (ax1_vert_pos - new_vert_pos).normalized() final_obj_vec2 = (ax2_vert_pos - new_vert_pos).normalized() final_obj_vec3 = final_obj_vec1.cross(final_obj_vec2).normalized() final_obj_vec1 = final_obj_vec3.cross(final_obj_vec2).normalized() final_mat = mathu.Matrix().to_3x3() final_mat[0][0], final_mat[1][0], final_mat[2][0] = final_obj_vec1[0], final_obj_vec1[1], final_obj_vec1[2] final_mat[0][1], final_mat[1][1], final_mat[2][1] = final_obj_vec2[0], final_obj_vec2[1], final_obj_vec2[2] final_mat[0][2], final_mat[1][2], final_mat[2][2] = final_obj_vec3[0], final_obj_vec3[1], final_obj_vec3[2] #final_mat = final_mat.normalized() final_obj.matrix_world = final_mat.to_4x4() # position and scale final_obj.scale = final_obj_scale final_obj.location = new_vert_pos if final_obj.type == 'MESH' and not self.transform_objects: final_obj.data.update() return {'FINISHED'}
def modal(self, context, event): region = bpy.context.region sx, sy = region.width, region.height v3d = context.space_data rv3d = context.region_data persmat = rv3d.perspective_matrix mouseco = Vector((event.mouse_region_x, event.mouse_region_y, 0)) self.mouseco = mouseco.copy() vertices = self.vertices do_redraw = False if time.time() - self.time >= redraw_rate: self.time = time.time() if event.type == 'MOUSEMOVE': ### ???????? self.calc_basic(context, rv3d, sx, sy, persmat, mouseco) if event.ctrl: if event.shift or event.alt: ### Snap Grid self.calc_snap_to_grid(context, event) else: ### Snap Vert pass #self.calc_snap_to_vert(context) else: self.snap_type = '' do_redraw = True else: # calc_snap_to_vert??????????????? if not (event.ctrl and not (event.shift or event.alt)): self.calc_basic(context, rv3d, sx, sy, persmat, mouseco) if event.ctrl: self.calc_snap_to_grid(context, event) do_redraw = True if event.type == 'LEFTMOUSE' and event.value == 'PRESS': self.time = time.time() self.calc_basic(context, rv3d, sx, sy, persmat, mouseco) if event.ctrl: if event.shift or event.alt: self.calc_snap_to_grid(context, event) else: pass #self.calc_snap_to_vert(context) do_redraw = True vertices.append(self.current_vec) self.along = -1 elif event.type == 'MIDDLEMOUSE' and event.value == 'PRESS': if event.shift: return {'PASS_THROUGH'} else: if self.along == -1 and vertices: v = convert_world_to_window(vertices[-1], persmat, sx, sy) relative = mouseco - v if abs(relative[0]) >= abs(relative[1]): self.along = 0 else: self.along = 1 else: self.along = -1 do_redraw = True elif event.type in ('WHEELUPMOUSE', 'WHEELDOWNMOUSE'): return {'PASS_THROUGH'} elif event.type in ('ENTER', 'RET', 'SPACE'): context.region.callback_remove(self._handle) context.area.tag_redraw() self.execute(context) return {'FINISHED'} elif event.type in ('RIGHTMOUSE', 'ESC'): context.region.callback_remove(self._handle) context.area.tag_redraw() return {'CANCELLED'} if do_redraw: context.area.tag_redraw() return {'RUNNING_MODAL'}
def generate_newnormals(self, context): genmode = context.window_manager.vn_genmode me = context.active_object.data bm = bmesh.new() if context.mode == 'EDIT_MESH': bm = bmesh.from_edit_mesh(me) else: bm.from_mesh(me) me.update() faces_list = [f for f in bm.faces] verts_list = [v for v in bm.verts] # DEFAULT: Blender default if (genmode == 'DEFAULT'): wasobjmode = (context.mode == 'OBJECT') if wasobjmode: bpy.ops.object.mode_set(mode='EDIT') bm = bmesh.from_edit_mesh(me) me.update() faces_list = [f for f in bm.faces] verts_list = [v for v in bm.verts] bpy.ops.mesh.normals_make_consistent() if context.window_manager.edit_splitnormals: normals_data.cust_normals_ppoly.clear() for i in range(len(faces_list)): faceverts = [v for v in faces_list[i].verts] normals_data.cust_normals_ppoly.append([]) for j in range(len(faceverts)): normals_data.cust_normals_ppoly[len(normals_data.cust_normals_ppoly) - 1].append(faceverts[j].normal.copy()) else: normals_data.cust_normals_pvertex.clear() for i in range(len(verts_list)): normals_data.cust_normals_pvertex.append(verts_list[i].normal.copy()) if wasobjmode: bpy.ops.object.mode_set(mode='OBJECT') # UPVECT: custom direction elif (genmode == 'UPVECT'): if context.window_manager.edit_splitnormals: if context.window_manager.vn_genselectiononly: for i in range(len(normals_data.cust_normals_ppoly)): for j in range(len(normals_data.cust_normals_ppoly[i])): if faces_list[i].verts[j].select: normals_data.cust_normals_ppoly[i][j] = Vector(context.window_manager.vn_dirvector) else: for i in range(len(normals_data.cust_normals_ppoly)): for j in range(len(normals_data.cust_normals_ppoly[i])): normals_data.cust_normals_ppoly[i][j] = Vector(context.window_manager.vn_dirvector) else: if context.window_manager.vn_genselectiononly: for i in range(len(verts_list)): if verts_list[i].select: normals_data.cust_normals_pvertex[i] = Vector(context.window_manager.vn_dirvector) else: for i in range(len(verts_list)): normals_data.cust_normals_pvertex[i] = Vector(context.window_manager.vn_dirvector) # BENT: Bent from point (3D cursor) elif (genmode == 'BENT'): cursorloc = context.scene.cursor_location if context.window_manager.edit_splitnormals: if context.window_manager.vn_genselectiononly: for i in range(len(normals_data.cust_normals_ppoly)): for j in range(len(normals_data.cust_normals_ppoly[i])): if not (faces_list[i].hide) and faces_list[i].select: tempv = Vector(faces_list[i].verts[j].co) - cursorloc tempv = tempv.normalized() normals_data.cust_normals_ppoly[i][j] = tempv.copy() else: for i in range(len(faces_list)): for j in range(len(faces_list[i].verts)): tempv = Vector(vd.vpos) - cursorloc tempv = tempv.normalized() normals_data.cust_normals_ppoly[i][j] = tempv.copy() else: if context.window_manager.vn_genselectiononly: for i in range(len(verts_list)): if verts_list[i].select: tempv = Vector(verts_list[i].co) - cursorloc tempv = tempv.normalized() tempv = (normals_data.cust_normals_pvertex[i] * (1.0 - context.window_manager.vn_genbendingratio)) + (tempv * (context.window_manager.vn_genbendingratio)) normals_data.cust_normals_pvertex[i] = tempv else: for i in range(len(verts_list)): tempv = Vector(verts_list[i].co) - cursorloc tempv = tempv.normalized() tempv = (normals_data.cust_normals_pvertex[i] * (1.0 - context.window_manager.vn_genbendingratio)) + (tempv * (context.window_manager.vn_genbendingratio)) normals_data.cust_normals_pvertex[i] = tempv # G_FOLIAGE: combination of bent and up-vector for ground foliage elif (genmode == 'G_FOLIAGE'): ignorehidden = context.window_manager.vn_genignorehidden cursorloc = Vector(context.window_manager.vn_centeroffset) if context.window_manager.edit_splitnormals: for i in range(len(faces_list)): ignoreface = False if ignorehidden: if faces_list[i].hide: ignoreface = True for j in range(len(faces_list[i].verts)): if faces_list[i].verts[j].select: if not ignoreface: normals_data.cust_normals_ppoly[i][j] = Vector((0.0, 0.0, 1.0)) else: if not ignoreface: tempv = faces_list[i].verts[j].co - cursorloc normals_data.cust_normals_ppoly[i][j] = tempv.normalized() else: for i in range(len(verts_list)): if ignorehidden: if not verts_list[i].hide: if verts_list[i].select: normals_data.cust_normals_pvertex[i] = Vector((0.0, 0.0, 1.0)) else: tempv = verts_list[i].co - cursorloc normals_data.cust_normals_pvertex[i] = tempv.normalized() else: if verts_list[i].select: normals_data.cust_normals_pvertex[i] = Vector((0.0, 0.0, 1.0)) else: tempv = verts_list[i].co - cursorloc normals_data.cust_normals_pvertex[i] = tempv.normalized() # CUSTOM: generate for selected faces independently from mesh (or for the whole mesh) # - based on existing face nomals, so the mesh requires faces # - seems to be weighted by mesh topology when used in poly mode # - number of intersecting edges on connected face influences the direction elif (genmode == 'CUSTOM'): if context.window_manager.edit_splitnormals: for i in range(len(faces_list)): f = faces_list[i] if context.window_manager.vn_genselectiononly: if f.select: for j in range(len(f.verts)): fncount = 0 tempfvect = Vector((0.0, 0.0, 0.0)) if f.verts[j].select: for vf in f.verts[j].link_faces: if vf.select: fncount += 1 tempfvect = tempfvect + vf.normal if fncount > 0: normals_data.cust_normals_ppoly[i][j] = (tempfvect / float(fncount)).normalized() else: for j in range(len(f.verts)): fncount = len(f.verts[j].link_faces) tempfvect = Vector((0.0, 0.0, 0.0)) for vf in f.verts[j].link_faces: tempfvect = tempfvect + vf.normal normals_data.cust_normals_ppoly[i][j] = (tempfvect / float(fncount)).normalized() else: for i in range(len(verts_list)): v = verts_list[i] if context.window_manager.vn_genselectiononly: if v.select: fncount = 0 tempfvect = Vector((0.0, 0.0, 0.0)) for j in range(len(v.link_faces)): if v.link_faces[j].select: fncount += 1 tempfvect = tempfvect + v.link_faces[j].normal if fncount > 0: normals_data.cust_normals_pvertex[i] = (tempfvect / float(fncount)).normalized() else: fncount = len(v.link_faces) tempfvect = Vector((0.0, 0.0, 0.0)) for j in range(len(v.link_faces)): tempfvect = tempfvect + v.link_faces[j].normal normals_data.cust_normals_pvertex[i] = (tempfvect / float(fncount)).normalized() save_normalsdata(context) if (hasattr(context.active_object.data, "define_normals_split_custom") or not context.window_manager.edit_splitnormals) and context.window_manager.vn_settomeshongen: set_meshnormals(context)
def handling_event(self, context, event): mouseco = Vector((event.mouse_region_x, event.mouse_region_y, 0.0)) handled = True EXECUTE = True # execute等を実行後、すぐにreturn {'RUNNING_MODAL'} if self.inputexp: # evalの式の入力 if event.value == 'PRESS': handled_by_exp = self.exp.input(event) if handled_by_exp: self.set_values() return handled, EXECUTE shortcut_name = check_shortcuts(self.shortcuts, event) if event.type == 'TAB' and event.value == 'PRESS': # <Input Expression> if self.inputexp and (event.shift or event.ctrl): self.inputexp = False self.update(context, event) self.set_values() elif not self.inputexp and not (event.shift or event.ctrl): self.inputexp = True self.set_values() else: handled = False elif event.type in ('ESC', 'RIGHTMOUSE') and event.value == 'PRESS': if self.inputexp: self.inputexp = False self.update(context, event) self.set_values() else: handled = False elif event.type in ('LEFT_SHIFT', 'RIGHT_SHIFT'): if event.value == 'PRESS': self.shift = mouseco.copy() elif event.value == 'RELEASE': self.shift = None self.update(context, event) self.set_values() else: handled = False elif event.type in ('LEFT_CTRL', 'RIGHT_CTRL'): if event.value == 'PRESS': self.snap = True elif event.value == 'RELEASE': self.snap = False self.update(context, event) self.set_values() elif event.type == 'MOUSEMOVE': # <Move Mouse> self.update(context, event) self.set_values() elif shortcut_name == 'lock': # <Lock Trans Axis> if self.lock is None: self.lock = mouseco.copy() else: self.lock = None elif shortcut_name == 'reset': # <Reset> if self.lock: self.lock = self.lock - self.origin + mouseco self.origin = mouseco.copy() self.update(context, event) self.set_values() else: handled = False return handled, False
def export_layer(scale,l): # data s = "" layer = [ob for ob in scene.objects if ob.layers[l]] if len(layer)>0: obcontext = [o for o in layer if o.type == 'MESH'][0] lightcontexts = [o for o in layer if o.type == 'LAMP'] obdata = obcontext.data bm = bmesh.new() bm.from_mesh(obdata) # create vertex group lookup dictionary for names vgroup_names = {vgroup.index: vgroup.name for vgroup in obcontext.vertex_groups} # create dictionary of vertex group assignments per vertex vgroups = {v.index: [vgroup_names[g.group] for g in v.groups] for v in obdata.vertices} # create a map loop index -> vertex index (see: https://www.python.org/dev/peps/pep-0274/) loop_vert = {l.index:l.vertex_index for l in obdata.loops} vlen = len(obdata.vertices) + len(lightcontexts)*2 # vertices s = s + "{:02x}".format(vlen) for v in obdata.vertices: s = s + "{}{}{}".format(pack_double(v.co.x), pack_double(v.co.z), pack_double(v.co.y)) # lamp vertices front = Vector((0,-48,0)) for l in lightcontexts: # light position s = s + "{}{}{}".format(pack_double(l.location.x), pack_double(l.location.z), pack_double(l.location.y)) # light direction tmp = front.copy() tmp.rotate(l.rotation_euler) tmp += l.location # light end point ("normal") s = s + "{}{}{}".format(pack_double(tmp.x), pack_double(tmp.z), pack_double(tmp.y)) # faces faces = [] for i in range(len(obdata.polygons)): f=obdata.polygons[i] fs = "" # color is_dual_sided = False if len(obcontext.material_slots)>0: slot = obcontext.material_slots[f.material_index] mat = slot.material is_dual_sided = mat.game_settings.use_backface_culling==False fs = fs + "{:02x}".format(diffuse_to_p8color(mat.diffuse_color)) else: fs = fs + "{:02x}".format(1) # default color # is face part of a solid? face_verts = {loop_vert[li]:li for li in f.loop_indices} solid_group = {vgroups[k][0]:v for k,v in face_verts.items() if len(vgroups[k])==1} solid_group = set([solid_db[k] for k,v in solid_group.items() if k in solid_db]) if len(solid_group)>1: raise Exception('Multiple vertex groups for the same face') if len(solid_group)==1: # get group ID solid_group=solid_group.pop() fs = fs + "{:02x}".format(solid_group) else: fs = fs + "{:02x}".format(0) # + face count fs = fs + "{:02x}".format(len(f.loop_indices)) # + vertex id (= edge loop) for li in f.loop_indices: fs = fs + "{:02x}".format(loop_vert[li]+1) faces.append({'face': f, 'flip': False, 'data': fs}) if is_dual_sided: faces.append({'face': f, 'flip': True, 'data': fs}) # push face data to buffer (inc. dual sided faces) s = s + "{:02x}".format(len(faces)) for f in faces: s += f['data'] # normals s = s + "{:02x}".format(len(faces)) for f in faces: flip = -1 if f['flip'] else 1 f = f['face'] s = s + "{}{}{}".format(pack_float(flip * f.normal.x), pack_float(flip * f.normal.z), pack_float(flip * f.normal.y)) # all edges (except pure edge face) es = "" es_count = 0 # select pure edges edges = [e for e in bm.edges if e.is_wire] for e in edges: v0 = e.verts[0].index v1 = e.verts[1].index # get vertex groups g0 = vgroups[v0] g1 = vgroups[v1] # light line? if len(g0)>0 and len(g1)>0: # find common group (if any) cg = set(g0).intersection(g1) if len(cg)>1: raise Exception('Multiple vertex groups for the same edge ({},{}): {} x {} -> {}'.format(obdata.vertices[v0].co,obdata.vertices[v1].co,g0,g1,cg)) if len(cg)==1: # get light specifications light_group_name=cg.pop() light=lines_db[light_group_name] light_color_index=light['color'] light_kind=light['kind'] # light color + light type es = es + "{:02x}{:02x}{:02x}{:02x}".format(v0+1, v1+1, light_kind, light_color_index) es_count = es_count + 1 # additional properties if light_kind==0: # number of lights # find out number of lights according to segment length # convert model scale to meters (1 game unit = 48 meters) num_lights=int(round(max(48*e.calc_length()/light['n']/scale,2))) if num_lights>255: raise Exception('Too many lights ({}) for edge: ({},{}) category: {}'.format(num_lights,obdata.vertices[v0].co,obdata.vertices[v1].co,light_group_name)) light_scale=light['intensity'] es = es + "{:02x}{}".format(num_lights,pack_float(light_scale)) # PAPI lights lightindex = len(obdata.vertices) for l in lightcontexts: es = es + "{:02x}{:02x}{:02x}{:02x}".format(lightindex+1, lightindex + 2, 1, diffuse_to_p8color(l.data.color)) lightindex += 2 es_count = es_count + 1 s = s + "{:02x}".format(es_count) + es return s
class Line(Projection): """ 2d Line Internally stored as p: origin and v:size and direction moving p will move both ends of line moving p0 or p1 move only one end of line p1 ^ | v p0 == p """ def __init__(self, p=None, v=None, p0=None, p1=None): """ Init by either p: Vector or tuple origin v: Vector or tuple size and direction or p0: Vector or tuple 1 point location p1: Vector or tuple 2 point location Will convert any into Vector 2d both optionnals """ Projection.__init__(self) if p is not None and v is not None: self.p = Vector(p).to_2d() self.v = Vector(v).to_2d() elif p0 is not None and p1 is not None: self.p = Vector(p0).to_2d() self.v = Vector(p1).to_2d() - self.p else: self.p = Vector((0, 0)) self.v = Vector((0, 0)) self.line = None @property def copy(self): return Line(self.p.copy(), self.v.copy()) @property def p0(self): return self.p @property def p1(self): return self.p + self.v @p0.setter def p0(self, p0): """ Note: setting p0 move p0 only """ p1 = self.p1 self.p = Vector(p0).to_2d() self.v = p1 - p0 @p1.setter def p1(self, p1): """ Note: setting p1 move p1 only """ self.v = Vector(p1).to_2d() - self.p @property def length(self): """ 3d length """ return self.v.length @property def angle(self): """ 2d angle on xy plane """ return atan2(self.v.y, self.v.x) @property def a0(self): return self.angle @property def angle_normal(self): """ 2d angle of perpendicular lie on the right side p1 |--x p0 """ return atan2(-self.v.x, self.v.y) @property def reversed(self): return Line(self.p, -self.v) @property def oposite(self): return Line(self.p + self.v, -self.v) @property def cross_z(self): """ 2d Vector perpendicular on plane xy lie on the right side p1 |--x p0 """ return Vector((self.v.y, -self.v.x)) @property def cross(self): return Vector((self.v.y, -self.v.x)) def signed_angle(self, u, v): """ signed angle between two vectors range [-pi, pi] """ return atan2(u.x * v.y - u.y * v.x, u.x * v.x + u.y * v.y) def delta_angle(self, last): """ signed delta angle between end of line and start of this one this value is object's a0 for segment = self """ if last is None: return self.angle return self.signed_angle(last.straight(1, 1).v, self.straight(1, 0).v) def normal(self, t=0): """ 2d Line perpendicular on plane xy at position t in current segment lie on the right side p1 |--x p0 """ return Line(self.lerp(t), self.cross_z) def sized_normal(self, t, size): """ 2d Line perpendicular on plane xy at position t in current segment and of given length lie on the right side when size > 0 p1 |--x p0 """ return Line(self.lerp(t), size * self.cross_z.normalized()) def lerp(self, t): """ 3d interpolation """ return self.p + self.v * t def intersect(self, line): """ 2d intersection on plane xy return True if intersect p: point of intersection t: param t of intersection on current line """ c = line.cross_z d = self.v.dot(c) if d == 0: return False, 0, 0 t = c.dot(line.p - self.p) / d return True, self.lerp(t), t def intersect_ext(self, line): """ same as intersect, but return param t on both lines """ c = line.cross_z d = self.v.dot(c) if d == 0: return False, 0, 0, 0 dp = line.p - self.p c2 = self.cross_z u = c.dot(dp) / d v = c2.dot(dp) / d return u > 0 and v > 0 and u < 1 and v < 1, self.lerp(u), u, v def point_sur_segment(self, pt): """ _point_sur_segment point: Vector 2d t: param t de l'intersection sur le segment courant d: distance laterale perpendiculaire positif a droite """ dp = pt - self.p dl = self.length if dl == 0: return dp.length < 0.00001, 0, 0 d = (self.v.x * dp.y - self.v.y * dp.x) / dl t = self.v.dot(dp) / (dl * dl) return t > 0 and t < 1, d, t def steps(self, len): steps = max(1, round(self.length / len, 0)) return 1 / steps, int(steps) def in_place_offset(self, offset): """ Offset current line offset > 0 on the right part """ self.p += offset * self.cross_z.normalized() def offset(self, offset): """ Return a new line offset > 0 on the right part """ return Line(self.p + offset * self.cross_z.normalized(), self.v) def tangeant(self, t, da, radius): p = self.lerp(t) if da < 0: c = p + radius * self.cross_z.normalized() else: c = p - radius * self.cross_z.normalized() return Arc(c, radius, self.angle_normal, da) def straight(self, length, t=1): return Line(self.lerp(t), self.v.normalized() * length) def translate(self, dp): self.p += dp def rotate(self, a): """ Rotate segment ccw arroud p0 """ ca = cos(a) sa = sin(a) self.v = Matrix([ [ca, -sa], [sa, ca] ]) @ self.v return self def scale(self, length): self.v = length * self.v.normalized() return self def tangeant_unit_vector(self, t): return self.v.normalized() def as_curve(self, context): """ Draw Line with open gl in screen space aka: coords are in pixels """ curve = bpy.data.curves.new('LINE', type='CURVE') curve.dimensions = '2D' spline = curve.splines.new('POLY') spline.use_endpoint_u = False spline.use_cyclic_u = False pts = self.pts spline.points.add(len(pts) - 1) for i, p in enumerate(pts): x, y, z = p spline.points[i].co = (x, y, 0, 1) curve_obj = bpy.data.objects.new('LINE', curve) context.scene.collection.objects.link(curve_obj) curve_obj.select_set(state=True) def make_offset(self, offset, last=None): """ Return offset between last and self. Adjust last and self start to match intersection point """ line = self.offset(offset) if last is None: return line if hasattr(last, "r"): res, d, t = line.point_sur_segment(last.c) c = (last.r * last.r) - (d * d) # print("t:%s" % t) if c <= 0: # no intersection ! p0 = line.lerp(t) else: # center is past start of line if t > 0: p0 = line.lerp(t) - line.v.normalized() * sqrt(c) else: p0 = line.lerp(t) + line.v.normalized() * sqrt(c) # compute da of arc u = last.p0 - last.c v = p0 - last.c da = self.signed_angle(u, v) # da is ccw if last.ccw: # da is cw if da < 0: # so take inverse da = 2 * pi + da elif da > 0: # da is ccw da = 2 * pi - da last.da = da line.p0 = p0 else: # intersect line / line # 1 line -> 2 line c = line.cross_z d = last.v.dot(c) if d == 0: return line v = line.p - last.p t = c.dot(v) / d c2 = last.cross_z u = c2.dot(v) / d # intersect past this segment end # or before last segment start # print("u:%s t:%s" % (u, t)) if u > 1 or t < 0: return line p = last.lerp(t) line.p0 = p last.p1 = p return line @property def pts(self): return [self.p0.to_3d(), self.p1.to_3d()]
class Turtle(object): axis_x = Vector([1, 0, 0]) axis_y = Vector([0, 1, 0]) axis_z = Vector([0, 0, 1]) line = False def __init__(self, angle, other): if other is None: self.set_quaternions(angle) self.at = Vector([0, 0, 0]) self.quaternion = Quaternion(self.at) else: self.at = other.at.copy() self.quaternion = other.quaternion.copy() self.yaw_add = other.yaw_add self.yaw_sub = other.yaw_sub self.roll_add = other.roll_add self.roll_sub = other.roll_sub self.pitch_add = other.pitch_add self.pitch_sub = other.pitch_sub def set_quaternions(self, angle): angle = angle * math.pi / 180 v = Vector([0, 0, 0]) self.yaw_add = Quaternion(v) self.yaw_add = self.set_angle(self.axis_z, angle, self.yaw_add) self.yaw_sub = Quaternion(v) self.yaw_sub = self.set_angle(self.axis_z, -angle, self.yaw_sub) self.roll_add = Quaternion(v) self.roll_add = self.set_angle(self.axis_y, angle, self.roll_add) self.roll_sub = Quaternion(v) self.roll_sub = self.set_angle(self.axis_y, -angle, self.roll_sub) self.pitch_add = Quaternion(v) self.pitch_add = self.set_angle(self.axis_x, angle, self.pitch_add) self.pitch_sub = Quaternion(v) self.pitch_sub = self.set_angle(self.axis_x, -angle, self.pitch_sub) @staticmethod def set_angle(axis, angle, quaternion): half_angle = angle / 2 s = sin(half_angle) quaternion.x = axis.x * s quaternion.y = axis.y * s quaternion.z = axis.z * s quaternion.w = cos(half_angle) return quaternion def get_direct_vector(self): v = Vector([0, 1, 0]) v = self.apply_quaternion(v, self.quaternion) return v @staticmethod def apply_quaternion(v, q): x = v.x y = v.y z = v.z qx = q.x qy = q.y qz = q.z qw = q.w ix = qw * x + qy * z - qz * y iy = qw * y + qz * x - qx * z iz = qw * z + qx * y - qy * x iw = - qx * x - qy * y - qz * z v.x = ix * qw + iw * - qx + iy * - qz - iz * - qy v.y = iy * qw + iw * - qy + iz * - qx - ix * - qz v.z = iz * qw + iw * - qz + ix * - qy - iy * - qx return v.copy() def extrude(self): self.at = self.at + self.get_direct_vector() return self.at.copy() def yaw_up(self): self.quaternion = self.mul_quaternions(self.quaternion, self.yaw_add) self.line = False def ywa_down(self): self.quaternion = self.mul_quaternions(self.quaternion, self.yaw_sub) self.line = False def roll_up(self): self.quaternion = self.mul_quaternions(self.quaternion, self.roll_add) def roll_down(self): self.quaternion = self.mul_quaternions(self.quaternion, self.roll_sub) def pitch_up(self): self.quaternion = self.mul_quaternions(self.quaternion, self.pitch_add) self.line = False def pitch_down(self): self.quaternion = self.mul_quaternions(self.quaternion, self.pitch_sub) self.line = False def get(self): return self.at.copy() @staticmethod def mul_quaternions(a, b): qax = a.x qay = a.y qaz = a.z qaw = a.w qbx = b.x qby = b.y qbz = b.z qbw = b.w a.x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby a.y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz a.z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx a.w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz return a
class BoneInfo(ID): """ The purpose of this class is to abstract bpy.types.Bone, bpy.types.PoseBone and bpy.types.EditBone into a single concept. This class does not concern itself with posing the bone, only creating and rigging it. Eg, it does not store pose bone transformations such as loc/rot/scale. """ def __init__(self, container, name="Bone", source=None, bone_group=None, **kwargs): """ container: Need a reference to what BoneInfoContainer this BoneInfo belongs to. source: Bone to take transforms from (head, tail, roll, bbone_x, bbone_z). kwargs: Allow setting arbitrary bone properties at initialization. """ self.container = container ### The following dictionaries store pure information, never references to the real thing. ### # PoseBone custom properties. self.custom_props = {} # EditBone custom properties. self.custom_props_edit = {} # data_path:Driver dictionary, where data_path is from the bone. Only for drivers that are directly on a bone property! Not a sub-ID like constraints. self.drivers = {} self.bone_drivers = {} # List of (Type, attribs{}) tuples where attribs{} is a dictionary with the attributes of the constraint. # "drivers" is a valid attribute which expects the same content as self.drivers, and it holds the constraints for constraint properties. # TODO: Implement a proper container for constraints. self.constraints = [] ### Edit Bone properties self.parent = None # Blender expects bpy.types.EditBone, but we store definitions.bone.BoneInfo. str is also supported for now, but should be avoided. self.head = Vector((0,0,0)) self.tail = Vector((0,1,0)) self.roll = 0 # NOTE: For these bbone properties, we are referring only to edit bone versions of the values. self.bbone_curveinx = 0 self.bbone_curveiny = 0 self.bbone_curveoutx = 0 self.bbone_curveouty = 0 self.bbone_easein = 1 self.bbone_easeout = 1 self.bbone_scaleinx = 1 self.bbone_scaleiny = 1 self.bbone_scaleoutx = 1 self.bbone_scaleouty = 1 ### Bone properties self.name = name self.layers = [l==0 for l in range(32)] # 32 bools where only the first one is True. self.rotation_mode = 'QUATERNION' self.hide_select = False self.hide = False self.use_connect = False self.use_deform = False self.show_wire = False self.use_endroll_as_inroll = False self._bbone_x = 0.1 # NOTE: These two are wrapped by bbone_width @property. self._bbone_z = 0.1 self.bbone_segments = 1 self.bbone_handle_type_start = "AUTO" self.bbone_handle_type_end = "AUTO" self.bbone_custom_handle_start = "" # Blender expects bpy.types.Bone, but we store str. TODO: We should store BoneInfo here as well!! self.bbone_custom_handle_end = "" # Blender expects bpy.types.Bone, but we store str. self.envelope_distance = 0.25 self.envelope_weight = 1.0 self.use_envelope_multiply = False self.head_radius = 0.1 self.tail_radius = 0.1 self.use_inherit_rotation = True self.inherit_scale = "FULL" self.use_local_location = True self.use_relative_parent = False ### Pose Mode Only self._bone_group = None # Blender expects bpy.types.BoneGroup, we store definitions.bone_group.BoneGroup. It is also wrapped by bone_group @property. self.custom_shape = None # Blender expects bpy.types.Object, we store bpy.types.Object. self.custom_shape_transform = None # Blender expects bpy.types.PoseBone, we store definitions.bone.BoneInfo. self.custom_shape_scale = 1.0 self.use_custom_shape_bone_size = False self.lock_location = [False, False, False] self.lock_rotation = [False, False, False] self.lock_rotation_w = False self.lock_scale = [False, False, False] # Apply container's defaults for key, value in self.container.defaults.items(): setattr(self, key, value) if source: self.head = source.head.copy() self.tail = source.tail.copy() self.roll = source.roll self.envelope_distance = source.envelope_distance self.envelope_weight = source.envelope_weight self.use_envelope_multiply = source.use_envelope_multiply self.head_radius = source.head_radius self.tail_radius = source.tail_radius if type(source)==BoneInfo: self._bone_group = source._bone_group self.bbone_width = source.bbone_width else: self._bbone_x = source.bbone_x self._bbone_z = source.bbone_z if source.parent: if type(source)==bpy.types.EditBone: self.parent = source.parent.name else: self.parent = source.parent if type(bone_group) != str: self.bone_group = bone_group # Apply property values from arbitrary keyword arguments if any were passed. for key, value in kwargs.items(): setattr(self, key, value) def clone(self, new_name=None): """Return a clone of self.""" custom_ob_backup = self.custom_object # This would fail to deepcopy since it's a bpy.types.Object. self.custom_object = None my_clone = copy.deepcopy(self) my_clone.name = self.name + ".001" if new_name: my_clone.name = new_name my_clone.custom_object = custom_ob_backup return my_clone def __str__(self): return self.name @property def bbone_width(self): return self._bbone_x / self.container.scale @bbone_width.setter def bbone_width(self, value): """Set BBone width relative to the rig's scale.""" self._bbone_x = value * self.container.scale self._bbone_z = value * self.container.scale self.envelope_distance = value * self.container.scale self.head_radius = value * self.container.scale self.tail_radius = value * self.container.scale @property def bone_group(self): return self._bone_group @bone_group.setter def bone_group(self, bg): # bg is expected to be a cloudrig.definitions.bone_group.Bone_Group object. # Bone Group assignment is handled directly by the Bone Group object. if bg: bg.assign_bone(self) elif self._bone_group: self._bone_group.remove_bone(self) @property def vec(self): """Vector pointing from head to tail.""" return self.tail-self.head @vec.setter def vec(self, value): self.tail = self.head + value def scale_width(self, value): """Set bbone width relative to current.""" self.bbone_width *= value def scale_length(self, value): """Set bone length relative to its current length.""" self.tail = self.head + self.vec * value @property def length(self): return (self.tail-self.head).length @length.setter def length(self, value): assert value > 0, "Length cannot be 0!" self.tail = self.head + self.vec.normalized() * value @property def center(self): return self.head + self.vec/2 def set_layers(self, layerlist, additive=False): cloud_utils.set_layers(self, layerlist, additive) def put(self, loc, length=None, width=None, scale_length=None, scale_width=None): offset = loc-self.head self.head = loc self.tail = loc+offset if length: self.length=length if width: self.bbone_width = width if scale_length: self.scale_length(scale_length) if scale_width: self.scale_width(scale_width) def flatten(self): self.vec = cloud_utils.flat(self.vec) from math import pi deg = self.roll*180/pi # Round to nearest 90 degrees. rounded = round(deg/90)*90 self.roll = pi/180*rounded def disown(self, new_parent): """ Parent all children of this bone to a new parent. """ for b in self.container.bones: if b.parent==self or b.parent==self.name: b.parent = new_parent def add_constraint(self, armature, contype, true_defaults=False, prepend=False, **kwargs): """Add a constraint to this bone. contype: Type of constraint, eg. 'STRETCH_TO'. props: Dictionary of properties and values. true_defaults: When False, we use a set of arbitrary default values that I consider better than Blender's defaults. """ props = kwargs # Override defaults with better ones. if not true_defaults: new_props = get_defaults(contype, armature) for key, value in kwargs.items(): new_props[key] = value props = new_props if prepend: self.constraints.insert(0, (contype, props)) else: self.constraints.append((contype, props)) return props def clear_constraints(self): self.constraints = [] def write_edit_data(self, armature, edit_bone): """Write relevant data into an EditBone.""" assert armature.mode == 'EDIT', "Error: Armature must be in Edit Mode when writing edit bone data." # Check for 0-length bones. if (self.head - self.tail).length == 0: # Warn and force length. print("WARNING: Had to force 0-length bone to have some length: " + self.name) self.tail = self.head+Vector((0, 0.1, 0)) ### Edit Bone properties eb = edit_bone eb.use_connect = False # NOTE: Without this, ORG- bones' Copy Transforms constraints can't work properly. if self.parent: if type(self.parent)==str: eb.parent = armature.data.edit_bones.get(self.parent) else: eb.parent = armature.data.edit_bones.get(self.parent.name) eb.head = self.head.copy() eb.tail = self.tail.copy() eb.roll = self.roll eb.bbone_curveinx = self.bbone_curveinx eb.bbone_curveiny = self.bbone_curveiny eb.bbone_curveoutx = self.bbone_curveoutx eb.bbone_curveouty = self.bbone_curveouty eb.bbone_easein = self.bbone_easein eb.bbone_easeout = self.bbone_easeout eb.bbone_scaleinx = self.bbone_scaleinx eb.bbone_scaleiny = self.bbone_scaleiny eb.bbone_scaleoutx = self.bbone_scaleoutx eb.bbone_scaleouty = self.bbone_scaleouty # Custom Properties. for key, prop in self.custom_props_edit.items(): prop.make_real(edit_bone) def write_pose_data(self, pose_bone): """Write relevant data into a PoseBone.""" armature = pose_bone.id_data assert armature.mode != 'EDIT', "Armature cannot be in Edit Mode when writing pose data" # Pose bone data pb = pose_bone pb.custom_shape = self.custom_shape pb.custom_shape_scale = self.custom_shape_scale if self.custom_shape_transform: pb.custom_shape_transform = armature.pose.bones.get(self.custom_shape_transform.name) pb.use_custom_shape_bone_size = self.use_custom_shape_bone_size pb.lock_location = self.lock_location pb.lock_rotation = self.lock_rotation pb.lock_rotation_w = self.lock_rotation_w pb.lock_scale = self.lock_scale pb.rotation_mode = self.rotation_mode # Bone data b = pb.bone b.layers = self.layers[:] b.use_deform = self.use_deform b.bbone_x = self._bbone_x b.bbone_z = self._bbone_z b.bbone_segments = self.bbone_segments b.bbone_handle_type_start = self.bbone_handle_type_start b.bbone_handle_type_end = self.bbone_handle_type_end b.bbone_custom_handle_start = armature.data.bones.get(self.bbone_custom_handle_start or "") b.bbone_custom_handle_end = armature.data.bones.get(self.bbone_custom_handle_end or "") b.show_wire = self.show_wire b.use_endroll_as_inroll = self.use_endroll_as_inroll b.hide_select = self.hide_select b.hide = self.hide b.use_inherit_rotation = self.use_inherit_rotation b.inherit_scale = self.inherit_scale b.use_local_location = self.use_local_location b.use_relative_parent = self.use_relative_parent b.envelope_distance = self.envelope_distance b.envelope_weight = self.envelope_weight b.use_envelope_multiply = self.use_envelope_multiply b.head_radius = self.head_radius b.tail_radius = self.tail_radius # Constraints. for cd in self.constraints: con_type = cd[0] cinfo = cd[1] c = pose_bone.constraints.new(con_type) if 'name' in cinfo: c.name = cinfo['name'] for key, value in cinfo.items(): if con_type == 'ARMATURE' and key=='targets': # Armature constraint targets need special treatment. D'oh! # We assume the value of "targets" is a list of dictionaries describing a target. for tinfo in value: # For each of those dictionaries target = c.targets.new() # Create a target # Set armature as the target by default so we don't have to always specify it. target.target = armature # Copy just these three values. copy = ['weight', 'target', 'subtarget'] for prop in copy: if prop in tinfo: setattr_safe(target, prop, tinfo[prop]) elif(hasattr(c, key)): setattr_safe(c, key, value) # Fix stretch constraints if c.type == 'STRETCH_TO': c.rest_length = 0 # Custom Properties. for key, prop in self.custom_props.items(): prop.make_real(pose_bone) # Pose Bone Property Drivers. for path, d in self.drivers.items(): data_path = f'pose.bones["{pose_bone.name}"].{path}' d.make_real(pose_bone.id_data, data_path) # Data Bone Property Drivers. for path, d in self.bone_drivers.items(): #HACK: If we want to add drivers to bone properties that are shared between pose and edit mode, they aren't stored under armature.pose.bones[0].property but instead armature.bones[0].property... The entire way we handle drivers should be scrapped tbh. :P # But scrapping that requires scrapping the way we handle bones, so... just keep making it work. data_path = f'bones["{pose_bone.name}"].{path}' d.make_real(pose_bone.id_data.data, data_path) def get_real(self, armature): """If a bone with the name in this BoneInfo exists in the passed armature, return it.""" if armature.mode == 'EDIT': return armature.data.edit_bones.get(self.name) else: return armature.pose.bones.get(self.name)
class Turtle(object): def __init__(self, tropism=(0, 0, 0), tropismsize=0, pitch_angle=radians(30), yaw_angle=radians(30), roll_angle=radians(30), radius=0.2, iseed=42): self.tropism = Vector(tropism).normalized() self.magnitude = tropismsize self.forward = Vector((1, 0, 0)) self.up = Vector((0, 0, 1)) self.right = self.forward.cross(self.up) self.stack = [] self.stack_curly = [] self.position = Vector((0, 0, 0)) self.pitch_angle = pitch_angle self.yaw_angle = yaw_angle self.roll_angle = roll_angle self.radius = radius self.__init_terminals() seed(iseed) def __init_terminals(self): """ Initialize a map of predefined terminals. """ self.terminals = { '+': self.term_plus, '-': self.term_minus, '[': self.term_push, ']': self.term_pop, '(': self.term_push_curly, ')': self.term_pop_curly, '/': self.term_slash, '\\': self.term_backslash, '<': self.term_less, '>': self.term_greater, '&': self.term_amp, '!': self.term_expand, '@': self.term_shrink, '#': self.term_fatten, '%': self.term_slink, '^': self.term_expand_g, '*': self.term_shrink_g, '=': self.term_fatten_g, '|': self.term_slink_g, 'F': self.term_edge, 'Q': self.term_quad, # '{': self.term_object } def apply_tropism(self): # tropism is a normalized vector t = self.tropism * self.magnitude tf = self.forward + t tf.normalize() q = tf.rotation_difference(self.forward) self.forward.rotate(q) self.up.rotate(q) self.right.rotate(q) def term_plus(self, value=None): val = radians(value) if not value is None else self.pitch_angle r = Matrix.Rotation(val, 4, self.right) self.forward.rotate(r) self.up.rotate(r) def term_minus(self, value=None): val = radians(value) if not value is None else self.pitch_angle r = Matrix.Rotation(-val, 4, self.right) self.forward.rotate(r) self.up.rotate(r) def term_amp(self, value=30): k = (random() - 0.5) * value self.term_plus(value=k) k = (random() - 0.5) * value self.term_slash(value=k) def term_slash(self, value=None): r = Matrix.Rotation(radians(value) if not value is None else self.yaw_angle, 4, self.up) self.forward.rotate(r) self.right.rotate(r) def term_backslash(self, value=None): r = Matrix.Rotation(-radians(value) if not value is None else -self.yaw_angle, 4, self.up) self.forward.rotate(r) self.right.rotate(r) def term_less(self, value=None): r = Matrix.Rotation(radians(value) if not value is None else self.roll_angle, 4, self.forward) self.up.rotate(r) self.right.rotate(r) def term_greater(self, value=None): r = Matrix.Rotation(-radians(value) if not value is None else -self.roll_angle, 4, self.forward) self.up.rotate(r) self.right.rotate(r) def term_pop(self, value=None): t = self.stack.pop() (self.forward, self.up, self.right, self.position, self.radius) = t def term_push(self, value=None): t = (self.forward.copy(), self.up.copy(), self.right.copy(), self.position.copy(), self.radius) self.stack.append(t) def term_pop_curly(self, value=None): t = self.stack_curly.pop() (self.forward, self.up, self.right, self.position, self.radius) = t def term_push_curly(self, value=None): t = (self.forward.copy(), self.up.copy(), self.right.copy(), self.position.copy(), self.radius) self.stack_curly.append(t) expand_shrink_factor = 0.1 fatten_slink_factor = 0.045 expand_shrink_factor_g = 0.2 fatten_slink_factor_g = 0.48 def term_expand(self, value=1 + expand_shrink_factor): self.forward *= value self.up *= value self.right *= value def term_shrink(self, value=1 - expand_shrink_factor): self.forward *= value self.up *= value self.right *= value def term_fatten(self, value=1 + fatten_slink_factor): self.radius *= value def term_slink(self, value=1 - fatten_slink_factor): self.radius *= value def term_expand_g(self, value=1 + expand_shrink_factor_g): self.term_expand(value) def term_shrink_g(self, value=1 - expand_shrink_factor_g): self.term_shrink(value) def term_fatten_g(self, value=1 + fatten_slink_factor_g): self.term_fatten(value) def term_slink_g(self, value=1 - fatten_slink_factor_g): self.term_slink(value) def term_edge(self, value=None): s = self.position.copy() self.apply_tropism() self.position += self.forward e = self.position.copy() return Edge(start=s, end=e, radius=self.radius) def term_quad(self, value=0.5): return Quad(pos=self.position, right=self.right, up=self.up, forward=self.forward) def term_object(self, value=None, name=None): s = self.position.copy() self.apply_tropism() self.position += self.forward return BObject(name=name, pos=s, right=self.right, up=self.up, forward=self.forward) def interpret(self, s): """ interpret the iterable s, yield Quad, Edge or Object named tuples. """ print('interpret:', s) name = '' for c in s: t = None #print(c,name) if c == '}': t = self.term_object(name=name[1:]) name = '' elif c == '{' or name != '': name += c continue elif name != '': continue elif c in self.terminals: t = self.terminals[c]() #print('yield',t) if not t is None: yield t
def widget_iter_pose_translate(context, mpr, ob, fmap, fmap_target): from mathutils import ( Vector, ) # generic initialize if USE_VERBOSE: print("(iter-init)") context.area.header_text_set("Translating face-map: {}".format(fmap.name)) # invoke() # ... if USE_VERBOSE: print("(iter-invoke)") event = yield tweak = set() # modal(), first step mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) mval = mval_init.copy() # impl vars pose_bone = fmap_target del fmap_target tweak_attr = "location" tweak_attr_lock = "lock_location" # Could use face-map center too # Don't update these while interacting bone_matrix_init = pose_bone.matrix.copy() depth_location = bone_matrix_init.to_translation() world_to_local_3x3 = pose_bone_calc_transform_orientation(pose_bone) loc_init = pose_bone.location.copy() # Keep this loop fast! runs on mouse-movement. while True: event, tweak_next = yield if event in {True, False}: break if event.type == 'INBETWEEN_MOUSEMOVE': continue tweak = tweak_next if USE_VERBOSE: print("(iter-modal)", event, tweak) mval = Vector((event.mouse_region_x, event.mouse_region_y)) co_init, co = coords_to_loc_3d(context, (mval_init, mval), depth_location) loc_delta = world_to_local_3x3 @ (co - co_init) input_scale = 1.0 is_precise = 'PRECISE' in tweak if is_precise: input_scale /= 10.0 loc_delta *= input_scale # relative snap if 'SNAP' in tweak: loc_delta[:] = [ round(v, 2 if is_precise else 1) for v in loc_delta ] final_value = loc_init + loc_delta pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, final_value) # exit() if USE_VERBOSE: print("(iter-exit)", event) if event is True: # cancel pose_bone.location = loc_init else: pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock) context.area.header_text_set(None)
def widget_iter_pose_translate(context, mpr, ob, fmap, fmap_target): from mathutils import ( Vector, ) # generic initialize if USE_VERBOSE: print("(iter-init)") context.area.header_text_set("Translating face-map: {}".format(fmap.name)) # invoke() # ... if USE_VERBOSE: print("(iter-invoke)") event = yield tweak = set() # modal(), first step mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) mval = mval_init.copy() # impl vars pose_bone = fmap_target del fmap_target tweak_attr = "location" tweak_attr_lock = "lock_location" # Could use face-map center too # Don't update these while interacting bone_matrix_init = pose_bone.matrix.copy() depth_location = bone_matrix_init.to_translation() world_to_local_3x3 = pose_bone_calc_transform_orientation(pose_bone) loc_init = pose_bone.location.copy() # Keep this loop fast! runs on mouse-movement. while True: event, tweak_next = yield if event in {True, False}: break tweak = tweak_next if USE_VERBOSE: print("(iter-modal)", event, tweak) mval = Vector((event.mouse_region_x, event.mouse_region_y)) co_init, co = coords_to_loc_3d(context, (mval_init, mval), depth_location) loc_delta = world_to_local_3x3 * (co - co_init) input_scale = 1.0 is_precise = 'PRECISE' in tweak if is_precise: input_scale /= 10.0 loc_delta *= input_scale # relative snap if 'SNAP' in tweak: loc_delta[:] = [round(v, 2 if is_precise else 1) for v in loc_delta] final_value = loc_init + loc_delta pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, final_value) # exit() if USE_VERBOSE: print("(iter-exit)", event) if event is True: # cancel pose_bone.location = loc_init else: pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock) context.area.header_text_set()
def widget_iter_pose_scale(context, mpr, ob, fmap, fmap_target): from mathutils import ( Vector, ) # generic initialize if USE_VERBOSE: print("(iter-init)") context.area.header_text_set("Scale face-map: {}".format(fmap.name)) # invoke() # ... if USE_VERBOSE: print("(iter-invoke)") event = yield tweak = set() # modal(), first step mval_init = Vector((event.mouse_region_x, event.mouse_region_y)) mval = mval_init.copy() # impl vars pose_bone = fmap_target del fmap_target tweak_attr = "scale" tweak_attr_lock = "lock_scale" scale_init = pose_bone.scale.copy() # Keep this loop fast! runs on mouse-movement. while True: event, tweak_next = yield if event in {True, False}: break if event.type == 'INBETWEEN_MOUSEMOVE': continue tweak = tweak_next if USE_VERBOSE: print("(iter-modal)", event, tweak) mval = Vector((event.mouse_region_x, event.mouse_region_y)) input_scale = 1.0 is_precise = 'PRECISE' in tweak if is_precise: input_scale /= 10.0 scale_factor = ((mval.y - mval_init.y) / 200.0) * input_scale if 'SNAP' in tweak: # relative scale_factor = round(scale_factor, 2 if is_precise else 1) final_value = scale_init * (1.0 + scale_factor) pose_bone_set_attr_with_locks(pose_bone, tweak_attr, tweak_attr_lock, final_value) # exit() if USE_VERBOSE: print("(iter-exit)", event) if event is True: # cancel pose_bone.scale = scale_init else: pose_bone_autokey(pose_bone, tweak_attr, tweak_attr_lock) context.area.header_text_set(None)
class UVTranslate(bpy.types.Operator): """Translate UVs in the 3D Viewport""" bl_idname = "uv.brm_uvtranslate" bl_label = "BRM UVTranslate" bl_options = {"GRAB_CURSOR", "UNDO", "BLOCKING"} first_mouse_x = None first_mouse_y = None first_value = None mesh = None bm = None bm2 = None bm_orig = None shiftreset = False delta = 0 xlock = False ylock = False stateswitch = False mousetestx = False constrainttest = False pixel_steps = None do_pixel_snap = False def invoke(self, context, event): self.shiftreset = False self.xlock = False self.ylock = False self.constrainttest = False self.stateswitch = False self.mousetestx = False self.pixel_steps = None self.do_pixel_snap = False # object->edit switch seems to "lock" the data. Ugly but hey it works bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='EDIT') if context.object: print("UV Translate") self.first_mouse_x = event.mouse_x self.first_mouse_y = event.mouse_y self.mesh = bpy.context.object.data self.bm = bmesh.from_edit_mesh(self.mesh) # save original for reference self.bm2 = bmesh.new() self.bm2.from_mesh(self.mesh) self.bm_orig = bmesh.new() self.bm_orig.from_mesh(self.mesh) # have to do this for some reason self.bm.faces.ensure_lookup_table() self.bm2.faces.ensure_lookup_table() self.bm_orig.faces.ensure_lookup_table() # Get refrerence to addon preference to get snap setting module_name = __name__.split('.')[0] addon_prefs = context.user_preferences.addons[ module_name].preferences self.do_pixel_snap = addon_prefs.pixel_snap # Precalculate data before going into modal self.pixel_steps = {} for i, face in enumerate(self.bm.faces): if face.select is False: continue # Find pixel steps per face here to look up in future translations if self.do_pixel_snap: pixel_step = BRM_Utils.get_face_pixel_step(context, face) if pixel_step is not None: self.pixel_steps[face.index] = pixel_step context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} else: self.report({'WARNING'}, "No active object") return {'CANCELLED'} def modal(self, context, event): context.area.header_text_set( "BRM UVTranslate: X/Y - contrain along X/Y Axis, MMB drag - alternative axis contrain method, SHIFT - precision mode, CTRL - stepped mode, CTRL + SHIFT - stepped with smaller increments" ) context.area.tag_redraw() # setup constraints first if event.type == 'X': self.stateswitch = True self.xlock = False self.ylock = True if event.type == 'Y': self.stateswitch = True self.xlock = True self.ylock = False # test is middle mouse held down if event.type == 'MIDDLEMOUSE' and event.value == 'PRESS': self.constrainttest = True if event.type == 'MIDDLEMOUSE' and event.value == 'RELEASE': self.constrainttest = False # test if mouse is in the right quadrant for X or Y movement if self.constrainttest: mouseangle = math.atan2(event.mouse_y - self.first_mouse_y, event.mouse_x - self.first_mouse_x) mousetestx = False if (mouseangle < 0.785 and mouseangle > -0.785) or (mouseangle > 2.355 or mouseangle < -2.355): mousetestx = True if mousetestx: self.xlock = False self.ylock = True else: self.xlock = True self.ylock = False if mousetestx is not self.mousetestx: self.stateswitch = True self.mousetestx = not self.mousetestx if self.stateswitch: self.stateswitch = False # reset to start editing from start position for i, face in enumerate(self.bm.faces): if face.select: for o, vert in enumerate(face.loops): reset_uv = self.bm2.faces[i].loops[o][ self.bm2.loops.layers.uv.active].uv vert[self.bm.loops.layers.uv.active].uv = reset_uv if event.type == 'MOUSEMOVE': self.delta = ((self.first_mouse_x - event.mouse_x), (self.first_mouse_y - event.mouse_y)) sensitivity = 0.001 if not self.do_pixel_snap else 0.1 self.delta = Vector(self.delta) * sensitivity if self.do_pixel_snap: self.delta.x = int(round(self.delta.x)) self.delta.y = int(round(self.delta.y)) if event.shift and not event.ctrl: self.delta *= .1 # reset origin position to shift into precision mode if not self.shiftreset: self.shiftreset = True self.first_mouse_x = event.mouse_x self.first_mouse_y = event.mouse_y for i, face in enumerate(self.bm.faces): if face.select: for o, vert in enumerate(face.loops): reset_uv = vert[ self.bm.loops.layers.uv.active].uv self.bm2.faces[i].loops[o][ self.bm2.loops.layers.uv. active].uv = reset_uv self.delta = (0, 0) self.delta = Vector(self.delta) else: # reset origin position to shift into normal mode if self.shiftreset: self.shiftreset = False self.first_mouse_x = event.mouse_x self.first_mouse_y = event.mouse_y for i, face in enumerate(self.bm.faces): if face.select: for o, vert in enumerate(face.loops): reset_uv = vert[ self.bm.loops.layers.uv.active].uv self.bm2.faces[i].loops[o][ self.bm2.loops.layers.uv. active].uv = reset_uv self.delta = (0, 0) self.delta = Vector(self.delta) if event.ctrl and not event.shift: self.delta.x = math.floor(self.delta.x * 4) / 4 self.delta.y = math.floor(self.delta.y * 4) / 4 if event.ctrl and event.shift: self.delta.x = math.floor(self.delta.x * 16) / 16 self.delta.y = math.floor(self.delta.y * 16) / 16 # loop through every selected face and move the uv's using original uv as reference for i, face in enumerate(self.bm.faces): if face.select is False: continue local_delta = self.delta.copy() if self.do_pixel_snap and face.index in self.pixel_steps.keys( ): pixel_step = self.pixel_steps[face.index] local_delta.x *= pixel_step.x local_delta.y *= pixel_step.y uv_x_axis = Vector((1.0, 0.0)) uv_y_axis = Vector((0.0, 1.0)) if self.xlock: uv_x_axis = Vector((0, 0)) if self.ylock: uv_y_axis = Vector((0, 0)) for o, vert in enumerate(face.loops): origin_uv = self.bm2.faces[i].loops[o][ self.bm2.loops.layers.uv.active].uv uv_offset = local_delta.x * uv_x_axis + local_delta.y * uv_y_axis vert[self.bm.loops.layers.uv. active].uv = origin_uv + uv_offset # update mesh bmesh.update_edit_mesh(self.mesh, False, False) elif event.type == 'LEFTMOUSE': context.area.header_text_set() # finish up and make sure changes are locked in place bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='EDIT') return {'FINISHED'} elif event.type in {'RIGHTMOUSE', 'ESC'}: context.area.header_text_set() # reset all uvs to reference for i, face in enumerate(self.bm.faces): if face.select: for o, vert in enumerate(face.loops): reset_uv = self.bm_orig.faces[i].loops[o][ self.bm_orig.loops.layers.uv.active].uv vert[self.bm.loops.layers.uv.active].uv = reset_uv # update mesh bmesh.update_edit_mesh(self.mesh, False, False) return {'CANCELLED'} return {'RUNNING_MODAL'}
class RayCaster(): ''' This class is an extension of a mesh object's ray cast method to make it more convenient, specifically for the purpose of casting a ray from region space coordinates such as the mouse cursor. ''' def __init__(self): self.coordinate_system = 'OBJECT' self.mesh_object = None self.ray_origin = Vector() self.ray_target = Vector() def set_ray_from_region(self, x, y): context = bpy.context mesh_object = self.mesh_object region = context.region region_co = Vector((x, y)) rv3d = context.region_data sv3d = context.space_data # Determine the view's clipping distances. if rv3d.view_perspective == 'CAMERA': camera_data = sv3d.camera.data clip_start = camera_data.clip_start clip_end = camera_data.clip_end else: clip_start = sv3d.clip_start clip_end = sv3d.clip_end # Determine the ray's direction in world space. ray_direction = view3d_utils.region_2d_to_vector_3d( region, rv3d, region_co ) ray_direction.normalize() # For orthographic projections in Blender versions prior to 2.72, the # ray's direction needs to be inverted to point into the scene. if bpy.app.version < (2, 72, 0): if rv3d.view_perspective == 'ORTHO' or ( rv3d.view_perspective == 'CAMERA' and sv3d.camera.data.type == 'ORTHO' ): ray_direction *= -1 # Determine the ray's origin in world space. ray_origin =\ view3d_utils.region_2d_to_origin_3d(region, rv3d, region_co) # Determine the ray's target in world space. ray_target = ray_origin + clip_end * ray_direction # If the view is an orthographic projection, the ray's origin may exist # behind the mesh object. Therefore, it is necessary to move the ray's # origin a sufficient distance antiparallel to the ray's direction to # ensure that the ray's origin is in front of the mesh object. if rv3d.view_perspective == 'ORTHO': ray_origin -= 1000 * ray_direction # Otherwise, if the view is a perspective projection or projected from # a camera then advance the ray's origin to the near clipping plane. else: ray_origin += clip_start * ray_direction # Convert the ray's origin and target from world space to object space, # if necessary. if self.coordinate_system == 'OBJECT': inverse_model_matrix = mesh_object.matrix_world.inverted() for co in ray_origin, ray_target: co.xyz = inverse_model_matrix * co # Set the ray caster object's ray attributes. self.ray_origin = ray_origin self.ray_target = ray_target def ray_cast(self): mesh_object = self.mesh_object polygons = mesh_object.data.polygons # The mesh object's ray cast method is valid only if polygons are # present. if not polygons: raise Exception(( "'{0}' has no polygons available for ray intersection " + "testing." ).format(mesh_object.name) ) # The mesh object's ray cast data is not accessible in Edit mode. if mesh_object.mode == 'EDIT': bpy.ops.object.mode_set(mode = 'OBJECT') # Convert the ray's origin and target from world space to object space, # if necessary. if self.coordinate_system == 'WORLD': inverse_model_matrix = mesh_object.matrix_world.inverted() ray_origin = self.ray_origin.copy() ray_target = self.ray_target.copy() for co in ray_origin, ray_target: co.xyz = inverse_model_matrix * co else: ray_origin = self.ray_origin ray_target = self.ray_target # Perform the ray cast intersection test. if bpy.app.version < (2, 76, 9): location, normal, face_index =\ mesh_object.ray_cast(ray_origin, ray_target) else: hit, location, normal, face_index =\ mesh_object.ray_cast(ray_origin, ray_target) # Convert the object space intersection information to world space, if # necessary. if self.coordinate_system == 'WORLD': model_matrix = mesh_object.matrix_world for co in location, normal: co.xyz = model_matrix * co normal = (normal - mesh_object.location).normalized() # Return the intersection information. return (location, normal, face_index)
def __init__(self, origin: mathutils.Vector, vector: mathutils.Vector): self.origin = origin.copy() self.vector = vector self.vector.normalize()
def main(): # time logging global start_time start_time = time.time() import argparse # parse commandline arguments log_message(sys.argv) parser = argparse.ArgumentParser(description='Generate synth dataset images.') parser.add_argument('--idx', type=int, help='idx of the requested sequence') parser.add_argument('--ishape', type=int, help='requested cut, according to the stride') parser.add_argument('--stride', type=int, help='stride amount, default 50') args = parser.parse_args(sys.argv[sys.argv.index("--") + 1:]) idx = args.idx ishape = args.ishape stride = args.stride log_message("input idx: %d" % idx) log_message("input ishape: %d" % ishape) log_message("input stride: %d" % stride) if idx == None: exit(1) if ishape == None: exit(1) if stride == None: log_message("WARNING: stride not specified, using default value 50") stride = 50 # import idx info (name, split) idx_info = load(open("pkl/idx_info.pickle", 'rb')) idx_info = [x for x in idx_info if x['name'][:4] != 'h36m'] # get runpass (runpass, idx) = divmod(idx, len(idx_info)) log_message("runpass: %d" % runpass) log_message("output idx: %d" % idx) idx_info = idx_info[idx] log_message("sequence: %s" % idx_info['name']) log_message("nb_frames: %f" % idx_info['nb_frames']) log_message("use_split: %s" % idx_info['use_split']) # import configuration log_message("Importing configuration") import config params = config.load_file('config', 'SYNTH_DATA') smpl_data_folder = params['smpl_data_folder'] smpl_data_filename = params['smpl_data_filename'] bg_path = params['bg_path'] resy = params['resy'] resx = params['resx'] clothing_option = params['clothing_option'] # grey, nongrey or all tmp_path = params['tmp_path'] output_path = params['output_path'] output_types = params['output_types'] stepsize = params['stepsize'] clipsize = params['clipsize'] openexr_py2_path = params['openexr_py2_path'] # compute number of cuts nb_ishape = max(1, int(np.ceil((idx_info['nb_frames'] - (clipsize - stride))/stride))) log_message("Max ishape: %d" % (nb_ishape - 1)) if ishape == None: exit(1) assert(ishape < nb_ishape) # name is set given idx name = idx_info['name'] output_path = join(output_path, 'run%d' % runpass, name.replace(" ", "")) params['output_path'] = output_path tmp_path = join(tmp_path, 'run%d_%s_c%04d' % (runpass, name.replace(" ", ""), (ishape + 1))) params['tmp_path'] = tmp_path # check if already computed # + clean up existing tmp folders if any if exists(tmp_path) and tmp_path != "" and tmp_path != "/": os.system('rm -rf %s' % tmp_path) rgb_vid_filename = "%s_c%04d.mp4" % (join(output_path, name.replace(' ', '')), (ishape + 1)) #if os.path.isfile(rgb_vid_filename): # log_message("ALREADY COMPUTED - existing: %s" % rgb_vid_filename) # return 0 # create tmp directory if not exists(tmp_path): mkdir_safe(tmp_path) # >> don't use random generator before this point << # initialize RNG with seeds from sequence id import hashlib s = "synth_data:%d:%d:%d" % (idx, runpass,ishape) seed_number = int(hashlib.sha1(s.encode('utf-8')).hexdigest(), 16) % (10 ** 8) log_message("GENERATED SEED %d from string '%s'" % (seed_number, s)) random.seed(seed_number) np.random.seed(seed_number) if(output_types['vblur']): vblur_factor = np.random.normal(0.5, 0.5) params['vblur_factor'] = vblur_factor log_message("Setup Blender") # create copy-spher.harm. directory if not exists sh_dir = join(tmp_path, 'spher_harm') if not exists(sh_dir): mkdir_safe(sh_dir) sh_dst = join(sh_dir, 'sh_%02d_%05d.osl' % (runpass, idx)) os.system('cp spher_harm/sh.osl %s' % sh_dst) genders = {0: 'female', 1: 'male'} # pick random gender gender = choice(genders) scene = bpy.data.scenes['Scene'] scene.render.engine = 'CYCLES' bpy.data.materials['Material'].use_nodes = True scene.cycles.shading_system = True scene.use_nodes = True log_message("Listing background images") bg_names = join(bg_path, '%s_img.txt' % idx_info['use_split']) nh_txt_paths = [] with open(bg_names) as f: for line in f: nh_txt_paths.append(join(bg_path, line)) # grab clothing names log_message("clothing: %s" % clothing_option) with open( join(smpl_data_folder, 'textures', '%s_%s.txt' % ( gender, idx_info['use_split'] ) ) ) as f: txt_paths = f.read().splitlines() # if using only one source of clothing if clothing_option == 'nongrey': txt_paths = [k for k in txt_paths if 'nongrey' in k] elif clothing_option == 'grey': txt_paths = [k for k in txt_paths if 'nongrey' not in k] # random clothing texture cloth_img_name = choice(txt_paths) cloth_img_name = join(smpl_data_folder, cloth_img_name) cloth_img = bpy.data.images.load(cloth_img_name) # random background bg_img_name = choice(nh_txt_paths)[:-1] bg_img = bpy.data.images.load(bg_img_name) log_message("Loading parts segmentation") beta_stds = np.load(join(smpl_data_folder, ('%s_beta_stds.npy' % gender))) log_message("Building materials tree") mat_tree = bpy.data.materials['Material'].node_tree create_sh_material(mat_tree, sh_dst, cloth_img) res_paths = create_composite_nodes(scene.node_tree, params, img=bg_img, idx=idx) log_message("Loading smpl data") smpl_data = np.load(join(smpl_data_folder, smpl_data_filename)) log_message("Initializing scene") camera_distance = np.random.normal(8.0, 1) params['camera_distance'] = camera_distance ob, obname, arm_ob, cam_ob = init_scene(scene, params, gender) setState0() ob.select = True bpy.context.scene.objects.active = ob segmented_materials = True #True: 0-24, False: expected to have 0-1 bg/fg log_message("Creating materials segmentation") # create material segmentation if segmented_materials: materials = create_segmentation(ob, params) prob_dressed = {'leftLeg':.5, 'leftArm':.9, 'leftHandIndex1':.01, 'rightShoulder':.8, 'rightHand':.01, 'neck':.01, 'rightToeBase':.9, 'leftShoulder':.8, 'leftToeBase':.9, 'rightForeArm':.5, 'leftHand':.01, 'spine':.9, 'leftFoot':.9, 'leftUpLeg':.9, 'rightUpLeg':.9, 'rightFoot':.9, 'head':.01, 'leftForeArm':.5, 'rightArm':.5, 'spine1':.9, 'hips':.9, 'rightHandIndex1':.01, 'spine2':.9, 'rightLeg':.5} else: materials = {'FullBody': bpy.data.materials['Material']} prob_dressed = {'FullBody': .6} orig_pelvis_loc = (arm_ob.matrix_world.copy() * arm_ob.pose.bones[obname+'_Pelvis'].head.copy()) - Vector((-1., 1., 1.)) orig_cam_loc = cam_ob.location.copy() # unblocking both the pose and the blendshape limits for k in ob.data.shape_keys.key_blocks.keys(): bpy.data.shape_keys["Key"].key_blocks[k].slider_min = -10 bpy.data.shape_keys["Key"].key_blocks[k].slider_max = 10 log_message("Loading body data") cmu_parms, fshapes, name = load_body_data(smpl_data, ob, obname, idx=idx, gender=gender) log_message("Loaded body data for %s" % name) nb_fshapes = len(fshapes) if idx_info['use_split'] == 'train': fshapes = fshapes[:int(nb_fshapes*0.8)] elif idx_info['use_split'] == 'test': fshapes = fshapes[int(nb_fshapes*0.8):] # pick random real body shape shape = choice(fshapes) #+random_shape(.5) can add noise #shape = random_shape(3.) # random body shape # example shapes #shape = np.zeros(10) #average #shape = np.array([ 2.25176191, -3.7883464 , 0.46747496, 3.89178988, 2.20098416, 0.26102114, -3.07428093, 0.55708514, -3.94442258, -2.88552087]) #fat #shape = np.array([-2.26781107, 0.88158132, -0.93788176, -0.23480508, 1.17088298, 1.55550789, 0.44383225, 0.37688275, -0.27983086, 1.77102953]) #thin #shape = np.array([ 0.00404852, 0.8084637 , 0.32332591, -1.33163664, 1.05008727, 1.60955275, 0.22372946, -0.10738459, 0.89456312, -1.22231216]) #short #shape = np.array([ 3.63453289, 1.20836171, 3.15674431, -0.78646793, -1.93847355, -0.32129994, -0.97771656, 0.94531640, 0.52825811, -0.99324327]) #tall ndofs = 10 scene.objects.active = arm_ob orig_trans = np.asarray(arm_ob.pose.bones[obname+'_Pelvis'].location).copy() # create output directory if not exists(output_path): mkdir_safe(output_path) # spherical harmonics material needs a script to be loaded and compiled scs = [] for mname, material in materials.items(): scs.append(material.node_tree.nodes['Script']) scs[-1].filepath = sh_dst scs[-1].update() rgb_dirname = name.replace(" ", "") + '_c%04d.mp4' % (ishape + 1) rgb_path = join(tmp_path, rgb_dirname) data = cmu_parms[name] fbegin = ishape*stepsize*stride fend = min(ishape*stepsize*stride + stepsize*clipsize, len(data['poses'])) log_message("Computing how many frames to allocate") N = len(data['poses'][fbegin:fend:stepsize]) log_message("Allocating %d frames in mat file" % N) # force recomputation of joint angles unless shape is all zeros curr_shape = np.zeros_like(shape) nframes = len(data['poses'][::stepsize]) matfile_info = join(output_path, name.replace(" ", "") + "_c%04d_info.mat" % (ishape+1)) log_message('Working on %s' % matfile_info) # allocate dict_info = {} dict_info['bg'] = np.zeros((N,), dtype=np.object) # background image path dict_info['camLoc'] = np.empty(3) # (1, 3) dict_info['clipNo'] = ishape +1 dict_info['cloth'] = np.zeros((N,), dtype=np.object) # clothing texture image path dict_info['gender'] = np.empty(N, dtype='uint8') # 0 for male, 1 for female dict_info['joints2D'] = np.empty((2, 24, N), dtype='float32') # 2D joint positions in pixel space dict_info['joints3D'] = np.empty((3, 24, N), dtype='float32') # 3D joint positions in world coordinates dict_info['light'] = np.empty((9, N), dtype='float32') dict_info['pose'] = np.empty((data['poses'][0].size, N), dtype='float32') # joint angles from SMPL (CMU) dict_info['sequence'] = name.replace(" ", "") + "_c%04d" % (ishape + 1) dict_info['shape'] = np.empty((ndofs, N), dtype='float32') dict_info['zrot'] = np.empty(N, dtype='float32') dict_info['camDist'] = camera_distance dict_info['stride'] = stride if name.replace(" ", "").startswith('h36m'): dict_info['source'] = 'h36m' else: dict_info['source'] = 'cmu' if(output_types['vblur']): dict_info['vblur_factor'] = np.empty(N, dtype='float32') # for each clipsize'th frame in the sequence get_real_frame = lambda ifr: ifr random_zrot = 0 reset_loc = False batch_it = 0 curr_shape = reset_joint_positions(orig_trans, shape, ob, arm_ob, obname, scene, cam_ob, smpl_data['regression_verts'], smpl_data['joint_regressor']) random_zrot = 2*np.pi*np.random.rand() arm_ob.animation_data_clear() cam_ob.animation_data_clear() arm_ob.rotation_euler.x -= math.pi / 2 # create a keyframe animation with pose, translation, blendshapes and camera motion # LOOP TO CREATE 3D ANIMATION for seq_frame, (pose, trans) in enumerate(zip(data['poses'][fbegin:fend:stepsize], data['trans'][fbegin:fend:stepsize])): iframe = seq_frame scene.frame_set(get_real_frame(seq_frame)) # apply the translation, pose and shape to the character apply_trans_pose_shape(Vector(trans), pose, shape, ob, arm_ob, obname, scene, cam_ob, get_real_frame(seq_frame)) dict_info['shape'][:, iframe] = shape[:ndofs] dict_info['pose'][:, iframe] = pose dict_info['gender'][iframe] = list(genders)[list(genders.values()).index(gender)] if(output_types['vblur']): dict_info['vblur_factor'][iframe] = vblur_factor arm_ob.pose.bones[obname+'_root'].rotation_quaternion = Quaternion(Euler((0, 0, random_zrot), 'XYZ')) arm_ob.pose.bones[obname+'_root'].keyframe_insert('rotation_quaternion', frame=get_real_frame(seq_frame)) dict_info['zrot'][iframe] = random_zrot scene.update() # Bodies centered only in each minibatch of clipsize frames if seq_frame == 0 or reset_loc: reset_loc = False new_pelvis_loc = arm_ob.matrix_world.copy() * arm_ob.pose.bones[obname+'_Pelvis'].head.copy() rotated_orig = Vector([orig_pelvis_loc.copy()[0], orig_pelvis_loc.copy()[2], -orig_pelvis_loc.copy()[1]]) cam_ob.location = orig_cam_loc.copy() + (new_pelvis_loc.copy() - rotated_orig.copy()) cam_ob.keyframe_insert('location', frame=get_real_frame(seq_frame)) dict_info['camLoc'] = np.array(cam_ob.location) scene.node_tree.nodes['Image'].image = bg_img for part, material in materials.items(): material.node_tree.nodes['Vector Math'].inputs[1].default_value[:2] = (0, 0) # random light sh_coeffs = .7 * (2 * np.random.rand(9) - 1) sh_coeffs[0] = .5 + .9 * np.random.rand() # Ambient light (first coeff) needs a minimum is ambient. Rest is uniformly distributed, higher means brighter. sh_coeffs[1] = -.7 * np.random.rand() for ish, coeff in enumerate(sh_coeffs): for sc in scs: sc.inputs[ish+1].default_value = coeff # iterate over the keyframes and render # LOOP TO RENDER for seq_frame, (pose, trans) in enumerate(zip(data['poses'][fbegin:fend:stepsize], data['trans'][fbegin:fend:stepsize])): scene.frame_set(get_real_frame(seq_frame)) iframe = seq_frame dict_info['bg'][iframe] = bg_img_name dict_info['cloth'][iframe] = cloth_img_name dict_info['light'][:, iframe] = sh_coeffs scene.render.use_antialiasing = False scene.render.filepath = join(rgb_path, 'Image%04d.png' % get_real_frame(seq_frame)) log_message("Rendering frame %d" % seq_frame) # disable render output logfile = '/dev/null' open(logfile, 'a').close() old = os.dup(1) sys.stdout.flush() os.close(1) os.open(logfile, os.O_WRONLY) # Render bpy.ops.render.render(write_still=True) # disable output redirection os.close(1) os.dup(old) os.close(old) # NOTE: # ideally, pixels should be readable from a viewer node, but I get only zeros # --> https://ammous88.wordpress.com/2015/01/16/blender-access-render-results-pixels-directly-from-python-2/ # len(np.asarray(bpy.data.images['Render Result'].pixels) is 0 # Therefore we write them to temporary files and read with OpenEXR library (available for python2) in main_part2.py # Alternatively, if you don't want to use OpenEXR library, the following commented code does loading with Blender functions, but it can cause memory leak. # If you want to use it, copy necessary lines from main_part2.py such as definitions of dict_normal, matfile_normal... #for k, folder in res_paths.items(): # if not k== 'vblur' and not k=='fg': # path = join(folder, 'Image%04d.exr' % get_real_frame(seq_frame)) # render_img = bpy.data.images.load(path) # # render_img.pixels size is width * height * 4 (rgba) # arr = np.array(render_img.pixels[:]).reshape(resx, resy, 4)[::-1,:, :] # images are vertically flipped # if k == 'normal':# 3 channels, original order # mat = arr[:,:, :3] # dict_normal['normal_%d' % (iframe + 1)] = mat.astype(np.float32, copy=False) # elif k == 'gtflow': # mat = arr[:,:, 1:3] # dict_gtflow['gtflow_%d' % (iframe + 1)] = mat.astype(np.float32, copy=False) # elif k == 'depth': # mat = arr[:,:, 0] # dict_depth['depth_%d' % (iframe + 1)] = mat.astype(np.float32, copy=False) # elif k == 'segm': # mat = arr[:,:,0] # dict_segm['segm_%d' % (iframe + 1)] = mat.astype(np.uint8, copy=False) # # # remove the image to release memory, object handles, etc. # render_img.user_clear() # bpy.data.images.remove(render_img) # bone locations should be saved after rendering so that the bones are updated bone_locs_2D, bone_locs_3D = get_bone_locs(obname, arm_ob, scene, cam_ob) dict_info['joints2D'][:, :, iframe] = np.transpose(bone_locs_2D) dict_info['joints3D'][:, :, iframe] = np.transpose(bone_locs_3D) reset_loc = (bone_locs_2D.max(axis=-1) > 256).any() or (bone_locs_2D.min(axis=0) < 0).any() arm_ob.pose.bones[obname+'_root'].rotation_quaternion = Quaternion((1, 0, 0, 0)) # save a .blend file for debugging: # bpy.ops.wm.save_as_mainfile(filepath=join(tmp_path, 'pre.blend')) # save RGB data with ffmpeg (if you don't have h264 codec, you can replace with another one and control the quality with something like -q:v 3) cmd_ffmpeg = 'ffmpeg -y -r 30 -i ''%s'' -c:v h264 -pix_fmt yuv420p -crf 23 ''%s_c%04d.mp4''' % (join(rgb_path, 'Image%04d.png'), join(output_path, name.replace(' ', '')), (ishape + 1)) log_message("Generating RGB video (%s)" % cmd_ffmpeg) os.system(cmd_ffmpeg) if(output_types['vblur']): cmd_ffmpeg_vblur = 'ffmpeg -y -r 30 -i ''%s'' -c:v h264 -pix_fmt yuv420p -crf 23 -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" ''%s_c%04d.mp4''' % (join(res_paths['vblur'], 'Image%04d.png'), join(output_path, name.replace(' ', '')+'_vblur'), (ishape + 1)) log_message("Generating vblur video (%s)" % cmd_ffmpeg_vblur) os.system(cmd_ffmpeg_vblur) if(output_types['fg']): cmd_ffmpeg_fg = 'ffmpeg -y -r 30 -i ''%s'' -c:v h264 -pix_fmt yuv420p -crf 23 ''%s_c%04d.mp4''' % (join(res_paths['fg'], 'Image%04d.png'), join(output_path, name.replace(' ', '')+'_fg'), (ishape + 1)) log_message("Generating fg video (%s)" % cmd_ffmpeg_fg) os.system(cmd_ffmpeg_fg) cmd_tar = 'tar -czvf %s/%s.tar.gz -C %s %s' % (output_path, rgb_dirname, tmp_path, rgb_dirname) log_message("Tarballing the images (%s)" % cmd_tar) os.system(cmd_tar) # save annotation excluding png/exr data to _info.mat file import scipy.io scipy.io.savemat(matfile_info, dict_info, do_compression=True)
def execute(self, context): influenceamount = abs(context.window_manager.vn_bendingratio) showselected = context.window_manager.vn_editselection selectByFace = context.window_manager.vn_editbyface maxdist = context.window_manager.normtrans_maxdist influencemult = 1.0 if ( context.window_manager.vn_bendingratio > 0.0 ) else -1.0 selectByFace = context.window_manager.vn_editbyface editselection = context.window_manager.vn_editselection if maxdist <= 0.0: maxdist = 8192.0 if influenceamount > 0.0: destobj = context.active_object.data destdata = [] newnormals = [] destobj.calc_normals_split() vertslist = [[(v.co).copy(), v.select] for v in destobj.vertices] loopnorms_raw = [(l.normal).copy() for l in destobj.loops] loopcount = 0 dfcount = 0 for f in destobj.polygons: fvn = [] fvco = [] fvsel = [] newnormals.append([]) for v in f.vertices: fvco.append(vertslist[v][0].copy()) fvn.append(loopnorms_raw[loopcount].copy()) if showselected: if selectByFace: fvsel.append(f.select) else: fvsel.append(vertslist[v][1]) else: fvsel.append(True) loopcount += 1 newnormals[dfcount].append([]) destdata.append([fvco,fvn,fvsel]) dfcount += 1 destobj.free_normals_split() del vertslist[:] del loopnorms_raw[:] selobjects = [obj.data for obj in context.selected_objects if obj.type == 'MESH'] sourceverts = [] foundobj = (len(selobjects) > 1) for objmesh in selobjects: if objmesh != destobj: fcount = 0 lastdist = maxdist curdistv = Vector((0.0,0.0,0.0)) tempv = Vector((0.0,0.0,0.0)) curdist = 0.0 if objmesh.use_auto_smooth: sourceverts = [] objmesh.calc_normals_split() vertslist = [[(v.co).copy(), v.select] for v in objmesh.vertices] loopnorms_raw = [(l.normal).copy() for l in objmesh.loops] loopcount = 0 for f in objmesh.polygons: fvn = [] fvco = [] fvsel = [] for v in f.vertices: fvco.append(vertslist[v][0].copy()) fvn.append(loopnorms_raw[loopcount].copy()) if showselected: if selectByFace: fvsel.append(f.select) else: fvsel.append(vertslist[v][1]) else: fvsel.append(True) loopcount += 1 sourceverts.append([fvco,fvn,fvsel]) objmesh.free_normals_split() del vertslist[:] del loopnorms_raw[:] if len(sourceverts) > 0: for f in destdata: for i in range(len(f[0])): lastdist = maxdist nearest = f[1][i].copy() if f[2][i]: for df in sourceverts: for j in range(len(df[0])): curdistv = f[0][i] - df[0][j] curdist = curdistv.magnitude if curdist < maxdist: if curdist < lastdist: nearest = df[1][j].copy() lastdist = curdist tempv = ( ((f[1][i] * (1.0 - influenceamount)) + (nearest * influenceamount)) * influencemult ).normalized() newnormals[fcount][i].append(tempv.copy()) else: newnormals[fcount][i].append(f[1][i].copy()) fcount += 1 del sourceverts[:] else: sourceverts = [[],[],[]] for v in objmesh.vertices: sourceverts[0].append((v.co).copy()) sourceverts[1].append((v.normal).copy()) if showselected: sourceverts[2].append(v.select) else: sourceverts[2].append(True) if len(sourceverts) > 0: for f in destdata: for i in range(len(f[0])): lastdist = maxdist nearest = f[1][i].copy() if f[2][i]: for j in range(len(sourceverts[0])): curdistv = f[0][i] - sourceverts[0][j] curdist = curdistv.magnitude if curdist < maxdist: if curdist < lastdist: nearest = sourceverts[1][j].copy() lastdist = curdist tempv = ( ((f[1][i] * (1.0 - influenceamount)) + (nearest * influenceamount)) * influencemult ).normalized() newnormals[fcount][i].append(tempv.copy()) else: newnormals[fcount][i].append(f[1][i].copy()) fcount += 1 del sourceverts[:] del destdata[:] del selobjects[:] if foundobj: procnormslist = [] for i in range(len(newnormals)): procnormslist.append([]) for j in range(len(newnormals[i])): tempv = Vector((0.0,0.0,0.0)) if len(newnormals[i][j]) > 0: for v in newnormals[i][j]: tempv = tempv + v procnormslist[i].append(tempv.normalized()) if (update_customnormals(destobj, procnormslist)): context.area.tag_redraw() context.scene.update() else: print('Need more than one object') del newnormals[:] else: print('No influence') return {'FINISHED'}
class StatisticsDrawer(bpy.types.Operator): bl_idname = "an.statistics_drawer" bl_label = "Statistics Drawer" @classmethod def poll(cls, context): return context.area.type == "NODE_EDITOR" and not statisticsViewIsActive def invoke(self, context, event): global statisticsViewIsActive statisticsViewIsActive = True args = () self.drawHandler = bpy.types.SpaceNodeEditor.draw_handler_add(self.drawCallback, args, "WINDOW", "POST_PIXEL") context.window_manager.modal_handler_add(self) dpiFactor = getDpiFactor() self.drawOffset = Vector((20 * dpiFactor, context.region.height - 40 * dpiFactor)) self.lastMousePosition = Vector((event.mouse_region_x, event.mouse_region_y)) self.enableViewDrag = False self.updateStatistics() return {"RUNNING_MODAL"} def updateStatistics(self): self.statistics = NodeStatistics(getAnimationNodeTrees()) self.nodeTreeTable = createNodeTreeTable(self.statistics) self.mostUsedNodesTable = createMostUsedNodesTable(self.statistics) def modal(self, context, event): if context.area is not None: context.area.tag_redraw() if event.type in {"RIGHTMOUSE", "ESC"}: return self.finish() mousePosition = Vector((event.mouse_region_x, event.mouse_region_y)) if "CTRL" in event.type: if event.value == "PRESS": self.enableViewDrag = True if event.value == "RELEASE": self.enableViewDrag = False if self.enableViewDrag: self.drawOffset += mousePosition - self.lastMousePosition self.lastMousePosition = mousePosition if self.enableViewDrag: return {"RUNNING_MODAL"} return {"PASS_THROUGH"} def finish(self): bpy.types.SpaceNodeEditor.draw_handler_remove(self.drawHandler, "WINDOW") global statisticsViewIsActive statisticsViewIsActive = False return {"FINISHED"} def drawCallback(self): self.updateStatistics() region = bpy.context.region dpiFactor = getDpiFactor() bg = Rectangle.fromRegionDimensions(region) bg.color = (1, 1, 1, 0.5) bg.draw() setTextDrawingDpi(getDpi()) text = "Hold CTRL to drag the statistics - Press ESC or RMB to exit this view" drawText(text, 10 * dpiFactor, region.height - 20 * dpiFactor, color = (0, 0, 0, 0.5), size = 11) offset = self.drawOffset.copy() self.drawNodeTreeTable(offset, dpiFactor) offset.x += 500 * dpiFactor self.drawMostUsedNodesTable(offset, dpiFactor) def drawNodeTreeTable(self, location, dpiFactor): table = self.nodeTreeTable table.clearColumns() table.newColumn("Tree", 200 * dpiFactor, "CENTER", font = 0) table.newColumn("Nodes", 80 * dpiFactor, "RIGHT", font = 1) table.newColumn("Links", 80 * dpiFactor, "RIGHT", font = 1) table.newColumn("Subprograms", 110 * dpiFactor, "RIGHT", font = 1) table.rowHeight = 22 * dpiFactor table.headerRowHeight = 30 * dpiFactor table.lineThickness = 1 * dpiFactor table.cellPadding = 5 * dpiFactor table.dataFontSize = 11 table.headerFontSize = 14 table.draw(location) def drawMostUsedNodesTable(self, location, dpiFactor): table = self.mostUsedNodesTable table.clearColumns() table.newColumn("#", 30 * dpiFactor, "RIGHT", font = 1) table.newColumn("Node", 170 * dpiFactor, "LEFT", font = 0) table.newColumn("Amount", 80 * dpiFactor, "RIGHT", font = 1) table.rowHeight = 22 * dpiFactor table.headerRowHeight = 30 * dpiFactor table.lineThickness = 1 * dpiFactor table.cellPadding = 5 * dpiFactor table.dataFontSize = 11 table.headerFontSize = 14 table.draw(location)