Example #1
0
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
Example #3
0
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
Example #4
0
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
Example #5
0
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
Example #6
0
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]
Example #8
0
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 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
Example #10
0
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')
Example #11
0
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')
Example #12
0
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
Example #13
0
    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
Example #14
0
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()
Example #16
0
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
Example #17
0
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
Example #18
0
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
Example #19
0
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
Example #20
0
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
Example #21
0
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()
Example #24
0
    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 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'}
Example #26
0
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 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
Example #29
0
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()
Example #32
0
    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()
Example #35
0
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)
Example #36
0
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()
Example #39
0
    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)
Example #42
0
    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
Example #43
0
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
Example #44
0
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
Example #46
0
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)
Example #53
0
 def __init__(self, origin: mathutils.Vector, vector: mathutils.Vector):
     self.origin = origin.copy()
     self.vector = vector
     self.vector.normalize()
Example #54
0
    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
Example #55
0
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'}
Example #57
0
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)