def execute(self, context):
        bpy.ops.object.editmode_toggle()
        bm = bmesh.new()
        bm.from_mesh(context.active_object.data)

        # For easy access to verts, edges, and faces:
        bVerts = bm.verts
        bEdges = bm.edges
        bFaces = bm.faces

        fVerts = []
        normal = None

        # Find the selected face.  This will provide the plane to project onto:
        for f in bFaces:
            if f.select:
                for v in f.verts:
                    fVerts.append(v)
                f.normal_update()
                normal = f.normal
                f.select = False
                break

        for e in bEdges:
            if e.select:
                v1 = e.verts[0]
                v2 = e.verts[1]
                if v1 in fVerts and v2 in fVerts:
                    e.select = False
                    continue
                intersection = intersect_line_plane(v1.co, v2.co, fVerts[0].co, normal)
                if intersection != None:
                    d1 = distance_point_to_plane(v1.co, fVerts[0].co, normal)
                    d2 = distance_point_to_plane(v2.co, fVerts[0].co, normal)
                    # If they have different signs, then the edge crosses the plane:
                    if abs(d1 + d2) < abs(d1 - d2):
                        # Make the first vertice the positive vertice:
                        if d1 < d2:
                            v2, v1 = v1, v2
                        new = list(bmesh.utils.edge_split(e, v1, 0.5))
                        new[1].co = intersection
                        e.select = False
                        new[0].select = False
                        if self.pos:
                            bEdges.remove(new[0])
                        if self.neg:
                            bEdges.remove(e)

        bm.to_mesh(context.active_object.data)
        bpy.ops.object.editmode_toggle()
##        bpy.ops.mesh.remove_doubles()
        return {'FINISHED'}
Exemple #2
0
    def execute(self, context):
        bpy.ops.object.editmode_toggle()
        bm = bmesh.new()
        bm.from_mesh(context.active_object.data)

        # For easy access to verts, edges, and faces:
        bVerts = bm.verts
        bEdges = bm.edges
        bFaces = bm.faces

        fVerts = []
        normal = None

        # Find the selected face.  This will provide the plane to project onto:
        for f in bFaces:
            if f.select:
                for v in f.verts:
                    fVerts.append(v)
                f.normal_update()
                normal = f.normal
                f.select = False
                break

        for e in bEdges:
            if e.select:
                v1 = e.verts[0]
                v2 = e.verts[1]
                if v1 in fVerts and v2 in fVerts:
                    e.select = False
                    continue
                intersection = intersect_line_plane(v1.co, v2.co, fVerts[0].co, normal)
                if intersection != None:
                    d1 = distance_point_to_plane(v1.co, fVerts[0].co, normal)
                    d2 = distance_point_to_plane(v2.co, fVerts[0].co, normal)
                    # If they have different signs, then the edge crosses the plane:
                    if abs(d1 + d2) < abs(d1 - d2):
                        # Make the first vertice the positive vertice:
                        if d1 < d2:
                            v2, v1 = v1, v2
                        new = list(bmesh.utils.edge_split(e, v1, 0.5))
                        new[1].co = intersection
                        e.select = False
                        new[0].select = False
                        if self.pos:
                            bEdges.remove(new[0])
                        if self.neg:
                            bEdges.remove(e)

        bm.to_mesh(context.active_object.data)
        bpy.ops.object.editmode_toggle()
##        bpy.ops.mesh.remove_doubles()
        return {'FINISHED'}
Exemple #3
0
def setup_anchors(points, size, max_distance=3):
    n = len(points)
    neighbours_length = 0

    tries = 0
    while neighbours_length < 20 and tries < 5:
        random_start_point = points[randint(0, len(points) - 1)]
        neighbours = [
            p for p in points if (p - random_start_point).length < max_distance
        ]
        neighbours_length = len(neighbours)
        tries += 1
    if tries < 5:
        random_indices = sample([i for i in range(len(neighbours))], 20)
        random_selection = [neighbours[i] for i in random_indices]
        plane_co, plane_normal = get_plane_from_points(random_selection)
        new_points = []
        barycenter = Vector()
        shuffle(points)
        for i, point in enumerate(neighbours):
            barycenter += point
            if (point - barycenter / (i + 1)).length < max_distance and abs(
                    geometry.distance_point_to_plane(
                        point, plane_co, plane_normal)) < .6 * size:
                new_points.append(point)

        new_points = convex_indexing(new_points, plane_normal)
        return new_points, plane_normal

    else:
        return [], Vector()
Exemple #4
0
def is_coplanar_stroke(s, tol=0.0002, verbose=False) -> bool:
    '''
    Get a GP stroke object and tell if all points are coplanar (with a tolerance).
    '''

    if len(s.points) < 4:
        # less than 4 points is necessarily coplanar
        return True 

    obj = bpy.context.object
    mat = obj.matrix_world
    pct = len(s.points)
    a = mat @ s.points[0].co
    b = mat @ s.points[pct//3].co
    c = mat @ s.points[pct//3*2].co

    ab = b-a
    ac = c-a

    # get normal (perpendicular Vector)
    plane_no = ab.cross(ac)#.normalized()

    for i, p in enumerate(s.points):
        ## let a tolerance value of at least 0.0002
        # if abs(geometry.distance_point_to_plane(p.co, a, plane_no)) > tol:
        if abs(geometry.distance_point_to_plane(mat @ p.co, a, plane_no)) > tol:
            if verbose:
                print(f'point{i} is not co-planar') # (distance to plane {geometry.distance_point_to_plane(p.co, a, plane_no)})
            return False
    return True
Exemple #5
0
    def select_geometry(self, bm):
        for f in bm.faces:
            f.select = False

        bm.select_flush(False)

        if self.select_type == "NON-MANIFOLD":
            edges = [e for e in bm.edges if not e.is_manifold]

            for e in edges:
                e.select = True

        elif self.select_type == "NON-PLANAR":
            faces = [f for f in bm.faces if len(f.verts) > 3]

            for f in faces:
                distances = [distance_point_to_plane(v.co, f.calc_center_median(), f.normal) for v in f.verts]

                if any([d for d in distances if abs(d) > self.planar_threshold]):
                    f.select_set(True)

        elif self.select_type == "TRIS":
            faces = [f for f in bm.faces if len(f.verts) == 3]

            for f in faces:
                f.select = True

        elif self.select_type == "NGONS":
            faces = [f for f in bm.faces if len(f.verts) > 4]

            for f in faces:
                f.select = True
Exemple #6
0
    def execute(self, context):
        C = context
        D = bpy.data
        ob = C.active_object

        #if bpy.app.debug != True:
        #    bpy.app.debug = True
        #    if C.active_object.show_extra_indices != True:
        #        C.active_object.show_extra_indices = True
        if ob.mode == 'OBJECT':
            me = C.object.data
            bm = bmesh.new()
            bm.from_mesh(me)
        else:
            obj = C.edit_object
            me = obj.data
            bm = bmesh.from_edit_mesh(me)
        bm.select_history.validate()
        if len(bm.select_history) < 3:
            self.report({'INFO'}, 'Pick three vertices first')
            return {'CANCELLED'}

        points3Index = []
        points3 = []
        _ordering = bm.select_history if self.ref_order == "first" else list(
            bm.select_history)[::-1]
        for i in _ordering:
            if len(points3) >= 3:
                break
            elif isinstance(i, bmesh.types.BMVert):
                points3.append(i.co)
                points3Index.append(i.index)
        print(points3Index)
        if len(points3) < 3:
            self.report({'INFO'},
                        'At least three vertices are needed being selected')
            return {'CANCELLED'}

        points3Normal = normal(*points3)
        for v in bm.verts:
            if v.select and v.index not in points3Index:
                _move = True
                if self.filter_distance > 0.0:
                    _move = abs(
                        distance_point_to_plane(
                            v.co, points3[0],
                            points3Normal)) < self.filter_distance
                if _move == True:
                    v.co = intersect_line_plane(v.co, v.co + points3Normal,
                                                points3[0], points3Normal)
        if ob.mode == 'OBJECT':
            bm.to_mesh(me)
            bm.free()
        else:
            bmesh.update_edit_mesh(me, True)

        return {'FINISHED'}
Exemple #7
0
    def __prepare_terrain_points__(self):
        """Reverses the order of terrain points if last point is closer to the node in it's forward direction.
        """

        for variant_index in self.__tp_per_variant:

            # no terrain points for given variant, nothing to order reverse
            if len(self.__tp_per_variant[variant_index]) <= 0:
                continue

            # now if tail is closer to node on it's forward axis, we reverse list
            plane_co = Vector(self.__position)
            plane_no = Vector(self.__direction)

            head_distance = distance_point_to_plane(self.__tp_per_variant[variant_index][0][0], plane_co, plane_no)
            tail_distance = distance_point_to_plane(self.__tp_per_variant[variant_index][-1][0], plane_co, plane_no)

            if not (head_distance <= tail_distance - 0.001):  # 0.001 taken from Maya exporter
                self.__tp_per_variant[variant_index].reverse()
def compute_point_tri_dist(p, plane_origin, plane_a, plane_b, norm, tolerance):

    dist = distance_point_to_plane(p, plane_origin, norm)
    closest = p - norm * dist
    side = dist > 0
    dist_abs = abs(dist)
    is_in_plane = dist_abs < tolerance
    closest_in_plane =  intersect_point_tri(closest, plane_origin , plane_a, plane_b)
    is_in_segment = is_in_plane and closest_in_plane
    return dist_abs, is_in_segment, is_in_plane, list(closest), bool(closest_in_plane), side
def compute_point_tri_dist(p, plane_origin, plane_a, plane_b, norm, tolerance):

    dist = distance_point_to_plane(p, plane_origin, norm)
    closest = p - norm * dist
    side = dist > 0
    dist_abs = abs(dist)
    is_in_plane = dist_abs < tolerance
    closest_in_plane =  intersect_point_tri(closest, plane_origin , plane_a, plane_b)
    is_in_segment = is_in_plane and closest_in_plane
    return dist_abs, is_in_segment, is_in_plane, list(closest), bool(closest_in_plane), side
Exemple #10
0
    def execute(self, context):
        """ method called from ui """

        # init local
        catt_export = context.scene.catt_export

        # check for non flat faces
        if self.arg == 'check_nonflat_faces':

            # discard if no object selected
            sel_objects = bpy.context.selected_objects
            if( len(sel_objects) == 0 ):
                self.report({'INFO'}, 'No object selected.')
                return {'CANCELLED'}

            # switch to edit mode
            bpy.ops.object.mode_set(mode = 'EDIT')

            # context = bpy.context
            # obj = context.edit_object
            mesh = bpy.context.edit_object.data

            # select None
            bpy.ops.mesh.select_all(action='DESELECT')
            bm = bmesh.from_edit_mesh(mesh)
            ngons = [f for f in bm.faces if len(f.verts) > 3]

            # init locals
            has_non_flat_faces = False
            planar_tolerance = 1e-6

            # loop over faces
            for ngon in ngons:

                # define a plane from first 3 points
                co = ngon.verts[0].co
                norm = normal([v.co for v in ngon.verts[:3]])

                # set face selected
                ngon.select = not all( [abs(distance_point_to_plane(v.co, co, norm)) < planar_tolerance for v in ngon.verts[3:]] )

                # flag at least one non flat face detected
                if ngon.select:
                    has_non_flat_faces = True

            # update mesh
            bmesh.update_edit_mesh(mesh)

            # disable edit mode if no non-flat face detected
            if not has_non_flat_faces:
                bpy.ops.object.mode_set(mode = 'OBJECT')
                self.report({'INFO'}, 'No non-flat face detected.')

            return {'FINISHED'}
Exemple #11
0
def point_inside_loop_almost3D(pt, verts, no, p_pt = None, threshold = .01, debug = False):
    '''
    http://blenderartists.org/forum/showthread.php?259085-Brainstorming-for-Virtual-Buttons&highlight=point+inside+loop
    args:
       pt - 3d point to test of type Mathutils.Vector
       verts - 3d points representing the loop  
               TODO:  verts[0] == verts[-1] or implied?
               list with elements of type Mathutils.Vector
       no - plane normal
       plane_pt - a point on the plane.
                  if None, COM of verts will be used
       threshold - maximum distance to consider pt "coplanar"
                   default = .01
                   
       debug - Bool, default False.  Will print performance if True
                   
    return: Bool True if point is inside the loop
    '''
    if debug:
        start = time.time()
    #sanity checks
    if len(verts) < 3:
        print('loop must have 3 verts to be a loop and even then its sketchy')
        return False
    
    if no.length == 0:
        print('normal vector must be non zero')
        return False
    
    if not p_pt:
        p_pt = get_com(verts)
    
    if distance_point_to_plane(pt, p_pt, no) > threshold:
        return False
    
    (X_prime, Y_prime) = generic_axes_from_plane_normal(p_pt, no)
    
    verts_prime = []
    
    for v in verts:
        v_trans = v - p_pt
        vx = v_trans.dot(X_prime)
        vy = v_trans.dot(Y_prime)
        verts_prime.append(Vector((vx, vy)))
                           
    #transform the test point into the new plane x,y space
    pt_trans = pt - p_pt
    pt_prime = Vector((pt_trans.dot(X_prime), pt_trans.dot(Y_prime)))
                      
    pt_in_loop = point_inside_loop2d(verts_prime, pt_prime)
    
    return pt_in_loop
Exemple #12
0
def _calc_lattice_axis_length(vertex_coords, plane_co, plane_no):
    max_dist = 0
    min_dist = 0

    for v in vertex_coords:
        dist = distance_point_to_plane(v, plane_co, plane_no)
        if dist > max_dist:
            max_dist = dist
        elif dist < min_dist:
            min_dist = dist

    length = max_dist + abs(min_dist)
    return length
Exemple #13
0
def volume(obj):
    """
    Compute the volume of the object `obj`. The mesh has to be manifold, and
    face normals coherently oriented towards the outside.
    """
    bpy.ops.object.mode_set(mode='OBJECT')
    volume = 0
    orig = Vector((0, 0, 0))
    for face in obj.data.polygons:
        if face.normal.length == 0.0:
            continue
        distance = distance_point_to_plane(orig, face.center, face.normal)
        # minus sign, assuming normals are directed towards the outside
        volume -= face.area * distance / 3
    return volume
Exemple #14
0
def get_coplanar_stroke_vector(s, tol=0.0002, verbose=False):
    '''
    Get a GP stroke object and tell if all points are coplanar (with a tolerance).
    return normal vector if coplanar else None
    '''

    if len(s.points) < 4:
        # less than 4 points is necessarily coplanar
        # but not "evaluable" so return None
        if verbose:
            print('less than 4 points')
        return

    obj = bpy.context.object
    mat = obj.matrix_world
    pct = len(s.points)
    a = mat @ s.points[0].co
    b = mat @ s.points[pct//3].co
    c = mat @ s.points[pct//3*2].co

    """
    a = s.points[0].co
    b = s.points[1].co
    c = s.points[-2].co
    """
    ab = b-a
    ac = c-a

    #get normal (perpendicular Vector)
    plane_no = ab.cross(ac)#.normalized()
    val = plane_no

    # print('plane_no: ', plane_no)
    for i, p in enumerate(s.points):
        ## let a tolerance value of at least 0.0002 maybe more
        # if abs(geometry.distance_point_to_plane(p.co, a, plane_no)) > tol:
        if abs(geometry.distance_point_to_plane(mat @ p.co, a, plane_no)) > tol:
            if verbose:
                print(f'point{i} is not co-planar') # (distance to plane {geometry.distance_point_to_plane(p.co, a, plane_no)})
            return
            # val = None
    return val
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()
Exemple #16
0
def _calc_lattice_axis_midpoint_location(vertex_coords, plane_co, plane_no):
    max_dist = 0
    min_dist = 0

    max_vert_co = Vector((0, 0, 0))
    min_vert_co = Vector((0, 0, 0))

    for v in vertex_coords:
        dist = distance_point_to_plane(v, plane_co, plane_no)
        if dist > max_dist:
            max_dist = dist
            max_vert_co = v
        elif dist < min_dist:
            min_dist = dist
            min_vert_co = v

    midpoint_co = (max_vert_co + min_vert_co) / 2
    if midpoint_co == Vector((0, 0, 0)):
        midpoint_co = plane_co
    return midpoint_co
Exemple #17
0
def sync_cameras(ainode, camera, depsgraph):
    ob_eval = camera.evaluated_get(depsgraph)
    data = ob_eval.data

    # Basic stuff
    AiNodeSetStr(ainode, "name", ob_eval.name)
    AiNodeSetMatrix(ainode, "matrix", generate_aimatrix(ob_eval.matrix_world))

    # Lens data
    fov = calc_horizontal_fov(ob_eval)
    AiNodeSetFlt(ainode, "fov", math.degrees(fov))
    AiNodeSetFlt(ainode, "exposure", data.arnold.exposure)

    # DOF
    if data.dof.focus_object:
        distance = geometry.distance_point_to_plane(
            ob_eval.matrix_world.to_translation(),
            data.dof_object.matrix_world.to_translation(),
            ob_eval.matrix_world.col[2][:3])
    else:
        distance = data.dof.focus_distance

    AiNodeSetFlt(ainode, "aperture_size", data.arnold.aperture_size)
    AiNodeSetInt(ainode, "aperture_blades", data.arnold.aperture_blades)
    AiNodeSetFlt(ainode, "aperture_rotation", data.arnold.aperture_rotation)
    AiNodeSetFlt(ainode, "aperture_blade_curvature",
                 data.arnold.aperture_blade_curvature)
    AiNodeSetFlt(ainode, "aperture_aspect_ratio",
                 data.arnold.aperture_aspect_ratio)

    # Clipping
    AiNodeSetFlt(ainode, "near_clip", data.clip_start)
    AiNodeSetFlt(ainode, "far_clip", data.clip_end)

    # Shutter
    AiNodeSetFlt(ainode, "shutter_start", data.arnold.shutter_start)
    AiNodeSetFlt(ainode, "shutter_end", data.arnold.shutter_end)
    AiNodeSetStr(ainode, "shutter_type", data.arnold.shutter_type)
    AiNodeSetStr(ainode, "rolling_shutter", data.arnold.rolling_shutter)
    AiNodeSetFlt(ainode, "rolling_shutter_duration",
                 data.arnold.rolling_shutter_duration)
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()
Exemple #19
0
    def execute(self, context):
        bpy.ops.object.editmode_toggle()

        bm = bmesh.new()
        bm.from_mesh(context.active_object.data)

        bFaces = bm.faces
        bEdges = bm.edges
        bVerts = bm.verts

        fVerts = []

        # Find the selected face.  This will provide the plane to project onto:
        for f in bFaces:
            if f.select:
                for v in f.verts:
                    fVerts.append(v)
                f.normal_update()
                normal = f.normal
                f.select = False
                break

        for v in bVerts:
            if v.select:
                if v in fVerts:
                    v.select = False
                    continue
                d = distance_point_to_plane(v.co, fVerts[0].co, normal)
                if self.make_copy:
                    temp = v
                    v = bVerts.new()
                    v.co = temp.co
                vector = normal
                vector.length = abs(d)
                v.co = v.co - (vector * sign(d))
                v.select = False

        bm.to_mesh(context.active_object.data)
        bpy.ops.object.editmode_toggle()
        return {'FINISHED'}
    def execute(self, context):
        bpy.ops.object.editmode_toggle()

        bm = bmesh.new()
        bm.from_mesh(context.active_object.data)

        bFaces = bm.faces
        bEdges = bm.edges
        bVerts = bm.verts

        fVerts = []

        # Find the selected face.  This will provide the plane to project onto:
        for f in bFaces:
            if f.select:
                for v in f.verts:
                    fVerts.append(v)
                f.normal_update()
                normal = f.normal
                f.select = False
                break

        for v in bVerts:
            if v.select:
                if v in fVerts:
                    v.select = False
                    continue
                d = distance_point_to_plane(v.co, fVerts[0].co, normal)
                if self.make_copy:
                    temp = v
                    v = bVerts.new()
                    v.co = temp.co
                vector = normal
                vector.length = abs(d)
                v.co = v.co - (vector * sign(d))
                v.select = False

        bm.to_mesh(context.active_object.data)
        bpy.ops.object.editmode_toggle()
        return {'FINISHED'}
Exemple #21
0
def center_of_gravity(obj):
    """
    Return a Vector with the coordinates of the center of gravity of the
    object, relatively to the object location. It assumes a uniform
    distribution of mass.  It can only work with triangular and quad meshes.
    """
    x = y = z = 0
    orig = Vector((0, 0, 0))
    for face in obj.data.polygons:
        if face.normal.length == 0.0:
            continue
        distance = distance_point_to_plane(orig, face.center, face.normal)

        nb_edges = len(face.vertices)
        v = [obj.data.vertices[f].co for f in face.vertices]
        if nb_edges == 3:
            face_vol = - face.area * distance / 3
            x += face_vol * (v[0][0] + v[1][0] + v[2][0]) / 4
            y += face_vol * (v[0][1] + v[1][1] + v[2][1]) / 4
            z += face_vol * (v[0][2] + v[1][2] + v[2][2]) / 4
        elif nb_edges == 4:
            # let's cut the quad in triangles!
            # triangle 1: vertices 0, 1, 2
            area1 = area_tri(v[0], v[1], v[2])
            face_vol1 = - area1 * distance / 3
            x += face_vol1 * (v[0][0] + v[1][0] + v[2][0]) / 4
            y += face_vol1 * (v[0][1] + v[1][1] + v[2][1]) / 4
            z += face_vol1 * (v[0][2] + v[1][2] + v[2][2]) / 4
            # triangle 2: vertices 2, 3, 0
            area2 = area_tri(v[2], v[3], v[0])
            face_vol2 = - area2 * distance / 3
            x += face_vol2 * (v[0][0] + v[2][0] + v[3][0]) / 4
            y += face_vol2 * (v[0][1] + v[2][1] + v[3][1]) / 4
            z += face_vol2 * (v[0][2] + v[2][2] + v[3][2]) / 4
        else:
            raise OrientationException

    return Vector((x, y, z))
def main(self, context):
    obj = context.active_object
    me = obj.data
    bm = bmesh.from_edit_mesh(me)

    face_sets = []

    faces = []

    force_orthogonal = self.properties.force_orthogonal
    method = self.properties.method

    # store data
    for f in bm.faces:
        if f.select:
            if method == 'AVERAGE':
                faces.append(f)

            elif method == 'INDIVIDUAL':
                face_sets.append([f])

    if method == 'AVERAGE':
        face_sets.append(faces)

    for fs in face_sets:

        normal = Vector()
        center = Vector()

        for f in fs:
            normal += f.normal
            center += f.calc_center_median_weighted()

        normal.normalize()
        center /= len(fs)

        if force_orthogonal:
            x = abs(normal.x)
            y = abs(normal.y)
            z = abs(normal.z)

            if x > y and x > z:
                normal.x /= x
                normal.y = 0.0
                normal.z = 0.0

            if y > x and y > z:
                normal.x = 0.0
                normal.y /= y
                normal.z = 0.0

            if z > y and z > x:
                normal.x = 0.0
                normal.y = 0.0
                normal.z /= z

        for f in fs:
            for v in f.verts:
                d = geometry.distance_point_to_plane(v.co, center, normal)
                v.co -= normal * d

    bmesh.update_edit_mesh(me)
Exemple #23
0
def cross_section_seed(bme, mx, point, normal, seed_index, debug = True):
    '''
    Takes a mesh and associated world matrix of the object and returns a cross secion in local
    space.
    
    Args:
        bme: Blender BMesh
        mx:   World matrix (type Mathutils.Matrix)
        point: any point on the cut plane in world coords (type Mathutils.Vector)
        normal:  plane normal direction (type Mathutisl.Vector)
        seed: face index, typically achieved by raycast
        exclude_edges: list of edge indices (usually already tested from previous iterations)
    '''
    
    times = []
    times.append(time.time())
    #bme = bmesh.new()
    #bme.from_mesh(me)
    #bme.normal_update()
    
    #if debug:
        #n = len(times)
        #times.append(time.time())
        #print('succesfully created bmesh in %f sec' % (times[n]-times[n-1]))
    verts =[]
    eds = []
    
    #convert point and normal into local coords
    #in the mesh into world space.This saves 2*(Nverts -1) matrix multiplications
    imx = mx.inverted()
    pt = imx * point
    no = imx.to_3x3() * normal  #local normal
    
    edge_mapping = {}  #perhaps we should use bmesh becaus it stores the great cycles..answer yup
    
    #first initial search around seeded face.
    #if none, we may go back to brute force
    #but prolly not :-)
    seed_edge = None
    seed_search = 0
    prev_eds = []
    seeds =[]
    
    if seed_index > len(bme.faces) - 1:
        print('looks like we hit an Ngon, tentative support')
    
        #perhaps this should be done before we pass bme to this op?
        #we may perhaps need to re raycast the new faces?    
        ngons = []
        for f in bme.faces:
            if len(f.verts) >  4:
                ngons.append(f)
        
        #we should never get to this point because we are pre
        #triangulating the ngons before this function in the
        #final work flow but this leaves not chance and keeps
        #options to reuse this for later.        
        if len(ngons):
            new_geom = bmesh.ops.triangulate(bme, faces = ngons, use_beauty = True)
            new_faces = new_geom['faces']
            
            #now we must find a new seed index since we have added new geometry
            for f in new_faces:
                if point_in_tri(pt, f.verts[0].co, f.verts[1].co, f.verts[2].co):
                    print('found the point inthe tri')
                    if distance_point_to_plane(pt, f.verts[0].co, f.normal) < .001:
                        seed_index = f.index
                        print('found a new index to start with')
                        break
            
            
    #if len(bme.faces[seed_index].edges) > 4:
        #print('no NGon Support for initial seed yet! try again')
        #return None
    
    for ed in bme.faces[seed_index].edges:
        seed_search += 1        
        prev_eds.append(ed.index)
        
        A = ed.verts[0].co
        B = ed.verts[1].co
        result = cross_edge(A, B, pt, no)
        if result[0] == 'CROSS':
            #add the point, add the mapping move forward
            edge_mapping[len(verts)] = [f.index for f in ed.link_faces]
            verts.append(result[1])
            potential_faces = [face for face in ed.link_faces if face.index != seed_index]
            if len(potential_faces):
                seeds.append(potential_faces[0])
                seed_edge = True
            else:
                seed_edge = False
        
    if not seed_edge:
        print('failed to find a good face to start with, cancelling until your programmer gets smarter')
        return None    
        
    #we have found one edge that crosses, now, baring any terrible disconnections in the mesh,
    #we traverse through the link faces, wandering our way through....removing edges from our list

    total_tests = 0
    
    #by the way we append the verts in the first face...we find A then B then start at A... so there is a little  reverse in teh vert order at the middle.
    verts.reverse()
    for element in seeds: #this will go both ways if they dont meet up.
        element_tests = 0
        while element and total_tests < 10000:
            total_tests += 1
            element_tests += 1
            #first, we know that this face is not coplanar..that's good
            #if new_face.no.cross(no) == 0:
                #print('coplanar face, stopping calcs until your programmer gets smarter')
                #return None
            if type(element) == bmesh.types.BMFace:
                element = face_cycle(element, pt, no, prev_eds, verts, edge_mapping)
            
            elif type(element) == bmesh.types.BMVert:
                element = vert_cycle(element, pt, no, prev_eds, verts, edge_mapping)
                
        print('completed %i tests in this seed search' % element_tests)
        print('%i vertices found so far' % len(verts))
        
 
    #The following tests for a closed loop
    #if the loop found itself on the first go round, the last test
    #will only get one try, and find no new crosses
    #trivially, mast make sure that the first seed we found wasn't
    #on a non manifold edge, which should never happen
    closed_loop = element_tests == 1 and len(seeds) == 2
    
    print('walked around cross section in %i tests' % total_tests)
    print('found this many vertices: %i' % len(verts))       
                
    if debug:
        n = len(times)
        times.append(time.time())
        print('calced intersections %f sec' % (times[n]-times[n-1]))
       
    #iterate through smartly to create edge keys
    #no longer have to do this...verts are created in order
    
    if closed_loop:        
        for i in range(0,len(verts)-1):
            eds.append((i,i+1))
        
        #the edge loop closure
        eds.append((i+1,0))
        
    else:
        #two more verts found than total tests
        #one vert per element test in the last loop
        
        
        #split the loop into the verts into the first seed and 2nd seed
        seed_1_verts = verts[:len(verts)-(element_tests)] #yikes maybe this index math is right
        seed_2_verts = verts[len(verts)-(element_tests):]
        seed_2_verts.reverse()
        
        seed_2_verts.extend(seed_1_verts)
        
        
        for i in range(0,len(seed_1_verts)-1):
            eds.append((i,i+1))
    
        verts = seed_2_verts
    if debug:
        n = len(times)
        times.append(time.time())
        print('calced connectivity %f sec' % (times[n]-times[n-1]))
        
    if len(verts):
        #new_me = bpy.data.meshes.new('Cross Section')
        #new_me.from_pydata(verts,eds,[])
        
    
        #if debug:
            #n = len(times)
            #times.append(time.time())
            #print('Total Time: %f sec' % (times[-1]-times[0]))
            
        return (verts, eds)
    else:
        return None
Exemple #24
0
 def key_func(v):
     return abs(geom.distance_point_to_plane(v.co, v1.co, normal))
def point_inside_loop_almost3D(pt, verts, no, p_pt = None, threshold = .01, debug = False):
    '''
    http://blenderartists.org/forum/showthread.php?259085-Brainstorming-for-Virtual-Buttons&highlight=point+inside+loop
    args:
       pt - 3d point to test of type Mathutils.Vector
       verts - 3d points representing the loop  
               TODO:  verts[0] == verts[-1] or implied?
               list with elements of type Mathutils.Vector
       no - plane normal
       plane_pt - a point on the plane.
                  if None, COM of verts will be used
       threshold - maximum distance to consider pt "coplanar"
                   default = .01
                   
       debug - Bool, default False.  Will print performance if True
                   
    return: Bool True if point is inside the loop
    '''
    if debug:
        start = time.time()
    #sanity checks
    if len(verts) < 3:
        print('loop must have 3 verts to be a loop and even then its sketchy')
        return False
    
    if no.length == 0:
        print('normal vector must be non zero')
        return False
    
    if not p_pt:
        p_pt = get_com(verts)
    
    if distance_point_to_plane(pt, p_pt, no) > threshold:
        return False
    
    #get the equation of a plane ax + by + cz = D
    #Given point P, normal N ...any point R in plane satisfies
    # Nx * (Rx - Px) + Ny * (Ry - Py) + Nz * (Rz - Pz) = 0
    #now pick any xy, yz or xz and solve for the other point
    
    a = no[0]
    b = no[1]
    c = no[2]
    
    Px = p_pt[0]
    Py = p_pt[1]
    Pz = p_pt[2]
    
    D = a * Px + b * Py + c * Pz
    
    #generate a randomply perturbed R from the known p_pt
    R = p_pt + Vector((random.random(), random.random(), random.random()))
    
    #z = D/c - a/c * x - b/c * y
    if c != 0:
       Rz =  D/c - a/c * R[0] - b/c * R[1]
       R[2] = Rz
       
    #y = D/b - a/b * x - c/b * z 
    elif b!= 0:
        Ry = D/b - a/b * R[0] - c/b * R[2] 
        R[1] = Ry
    #x = D/a - b/a * y - c/a * z
    elif a != 0:
        Rx = D/a - b/a * R[1] - c/a * R[2]
        R[0] = Rz
    else:
        print('undefined plane you wanker!')
        return(False)
    
    #no R represents any other point in the plane
    #we will use this to edefin an arbitrary local
    #x' y' and z'
    X_prime = R - p_pt
    X_prime.normalize()
    
    Y_prime = no.cross(X_prime)
    Y_prime.normalize()
    
    verts_prime = []
    
    for v in verts:
        v_trans = v - p_pt
        vx = v_trans.dot(X_prime)
        vy = v_trans.dot(Y_prime)
        verts_prime.append(Vector((vx, vy)))
                           
    #transform the test point into the new plane x,y space
    pt_trans = pt - p_pt
    pt_prime = Vector((pt_trans.dot(X_prime), pt_trans.dot(Y_prime)))
                      
    pt_in_loop = point_inside_loop2d(verts_prime, pt_prime)
    
    return pt_in_loop
def cross_section_seed(bme, mx, point, normal, seed_index, debug = True):
    '''
    Takes a mesh and associated world matrix of the object and returns a cross secion in local
    space.
    
    Args:
        bme: Blender BMesh
        mx:   World matrix (type Mathutils.Matrix)
        point: any point on the cut plane in world coords (type Mathutils.Vector)
        normal:  plane normal direction (type Mathutisl.Vector)
        seed: face index, typically achieved by raycast
        exclude_edges: list of edge indices (usually already tested from previous iterations)
    '''
    
    times = []
    times.append(time.time())
    #bme = bmesh.new()
    #bme.from_mesh(me)
    #bme.normal_update()
    
    #if debug:
        #n = len(times)
        #times.append(time.time())
        #print('succesfully created bmesh in %f sec' % (times[n]-times[n-1]))
    verts =[]
    eds = []
    
    #convert point and normal into local coords
    #in the mesh into world space.This saves 2*(Nverts -1) matrix multiplications
    imx = mx.inverted()
    pt = imx * point
    no = imx.to_3x3() * normal  #local normal
    
    edge_mapping = {}  #perhaps we should use bmesh becaus it stores the great cycles..answer yup
    
    #first initial search around seeded face.
    #if none, we may go back to brute force
    #but prolly not :-)
    seed_edge = None
    seed_search = 0
    prev_eds = []
    seeds =[]
    
    if seed_index > len(bme.faces) - 1:
        print('looks like we hit an Ngon, tentative support')
    
        #perhaps this should be done before we pass bme to this op?
        #we may perhaps need to re raycast the new faces?    
        ngons = []
        for f in bme.faces:
            if len(f.verts) >  4:
                ngons.append(f)
        
        #we should never get to this point because we are pre
        #triangulating the ngons before this function in the
        #final work flow but this leaves not chance and keeps
        #options to reuse this for later.        
        if len(ngons):
            new_geom = bmesh.ops.triangulate(bme, faces = ngons, use_beauty = True)
            new_faces = new_geom['faces']
            
            #now we must find a new seed index since we have added new geometry
            for f in new_faces:
                if point_in_tri(pt, f.verts[0].co, f.verts[1].co, f.verts[2].co):
                    print('found the point inthe tri')
                    if distance_point_to_plane(pt, f.verts[0].co, f.normal) < .001:
                        seed_index = f.index
                        print('found a new index to start with')
                        break
            
            
    #if len(bme.faces[seed_index].edges) > 4:
        #print('no NGon Support for initial seed yet! try again')
        #return None
    
    for ed in bme.faces[seed_index].edges:
        seed_search += 1        
        prev_eds.append(ed.index)
        
        A = ed.verts[0].co
        B = ed.verts[1].co
        result = cross_edge(A, B, pt, no)
        if result[0] == 'CROSS':
            #add the point, add the mapping move forward
            edge_mapping[len(verts)] = [f.index for f in ed.link_faces]
            verts.append(result[1])
            potential_faces = [face for face in ed.link_faces if face.index != seed_index]
            if len(potential_faces):
                seeds.append(potential_faces[0])
                seed_edge = True
            else:
                seed_edge = False
        
    if not seed_edge:
        print('failed to find a good face to start with, cancelling until your programmer gets smarter')
        return None    
        
    #we have found one edge that crosses, now, baring any terrible disconnections in the mesh,
    #we traverse through the link faces, wandering our way through....removing edges from our list

    total_tests = 0
    
    #by the way we append the verts in the first face...we find A then B then start at A... so there is a little  reverse in teh vert order at the middle.
    verts.reverse()
    for element in seeds: #this will go both ways if they dont meet up.
        element_tests = 0
        while element and total_tests < 10000:
            total_tests += 1
            element_tests += 1
            #first, we know that this face is not coplanar..that's good
            #if new_face.no.cross(no) == 0:
                #print('coplanar face, stopping calcs until your programmer gets smarter')
                #return None
            if type(element) == bmesh.types.BMFace:
                element = face_cycle(element, pt, no, prev_eds, verts, edge_mapping)
            
            elif type(element) == bmesh.types.BMVert:
                element = vert_cycle(element, pt, no, prev_eds, verts, edge_mapping)
                
        print('completed %i tests in this seed search' % element_tests)
        print('%i vertices found so far' % len(verts))
        
 
    #The following tests for a closed loop
    #if the loop found itself on the first go round, the last test
    #will only get one try, and find no new crosses
    #trivially, mast make sure that the first seed we found wasn't
    #on a non manifold edge, which should never happen
    closed_loop = element_tests == 1 and len(seeds) == 2
    
    print('walked around cross section in %i tests' % total_tests)
    print('found this many vertices: %i' % len(verts))       
                
    if debug:
        n = len(times)
        times.append(time.time())
        print('calced intersections %f sec' % (times[n]-times[n-1]))
       
    #iterate through smartly to create edge keys
    #no longer have to do this...verts are created in order
    
    if closed_loop:        
        for i in range(0,len(verts)-1):
            eds.append((i,i+1))
        
        #the edge loop closure
        eds.append((i+1,0))
        
    else:
        #two more verts found than total tests
        #one vert per element test in the last loop
        
        
        #split the loop into the verts into the first seed and 2nd seed
        seed_1_verts = verts[:len(verts)-(element_tests)] #yikes maybe this index math is right
        seed_2_verts = verts[len(verts)-(element_tests):]
        seed_2_verts.reverse()
        
        seed_2_verts.extend(seed_1_verts)
        
        
        for i in range(0,len(seed_1_verts)-1):
            eds.append((i,i+1))
    
        verts = seed_2_verts
    if debug:
        n = len(times)
        times.append(time.time())
        print('calced connectivity %f sec' % (times[n]-times[n-1]))
        
    if len(verts):
        #new_me = bpy.data.meshes.new('Cross Section')
        #new_me.from_pydata(verts,eds,[])
        
    
        #if debug:
            #n = len(times)
            #times.append(time.time())
            #print('Total Time: %f sec' % (times[-1]-times[0]))
            
        return (verts, eds)
    else:
        return None
Exemple #27
0
def cross_section_walker_endpoints(bme, pt, no, f_ind_from, e_ind_from, co_from, f_ind_to, co_to, epsilon, limit_set = None, max_iters = 10000):
    '''
    bme -  bmesh
    pt  - a point on cutting plane: mathutils.Vector
    no  - the normal of the cutting plane: mathutils.Vector
    f_ind_from - index of face which we are walking from: Int
    e_ind_from - index of the edge which we are stepping over: Int
    co_from - location of intersectino of e_ind_from edge and cutting plane:  mathutils.Vector
    f_end_to - index of face which we are walking toward
    co_to  - location of end point, not necessarily and edge intersection, often a racy_cast result in middle of a face
    
    returns tuple (verts,ed_inds, looped, found) by walking around a bmesh near the given plane
    verts: List of intersections of edges and cutting plane (in order) mathutils.Vector  co_2 is excluded
    eds crossed:  lost of the edges which were intersected(in orter)  Int.  
    looped is bool indicating if walk wrapped around bmesh, only true if did not find other face
    found is a bool indicating if the walk was able to find face f_ind_to  at point co_to
    '''
    bme.verts.ensure_lookup_table()
    bme.edges.ensure_lookup_table()
    bme.faces.ensure_lookup_table()
    
    # returned values
    verts = [co_from]
    eds_crossed = [bme.edges[e_ind_from]]
    faces_crossed = [bme.faces[f_ind_from]]
    looped = False
    found = False
    error = None
    
    #verify that the points are coplanar to the cut plane
    d0 = distance_point_to_plane(co_from, pt, no)
    df = distance_point_to_plane(co_to, pt, no)
    
    if d0 > epsilon or df > epsilon:
        print('not coplanar by epsilons standdards')
        print((d0, df, epsilon))
        return ([co_from, co_to],[], [],False, False, 'EPSILON')
    
    # track what faces we've seen
    f_inds_dict = {f_ind_from: 0}
    #track what edges we've seen  (more important with Ngons, we may traverse a face multiple times)
    e_inds_dict = {e_ind_from: 0}
    
    # get blender version
    bver = '%03d.%03d.%03d' % (bpy.app.version[0],bpy.app.version[1],bpy.app.version[2])
    if bver > '002.072.000':
        bme.edges.ensure_lookup_table();

    f_cur = next(f for f in bme.edges[e_ind_from].link_faces if f.index != f_ind_from) #There is occasionally error here
    find_current = f_cur.index
    faces_crossed += [f_cur]
    
    #find the edges we might cross at the end, make sure where we are headed is valid
    #co_end will be bweteen edges 0,1  2,3  3,4 even if f_end is a concve ngon sith 4,6,8... intersections
    valid_end = False
    f_end = bme.faces[f_ind_to]
    eds_is = find_bmedges_crossing_plane(pt, no, f_end.edges, epsilon)
    for i in range(0,int(len(eds_is)/2)):
        p0 = eds_is[2*i][1]
        p1 = eds_is[2*i + 1][1]
        
        line_loc, line_pct = intersect_point_line(co_to, p0, p1)
        if line_pct >= 0 and line_pct <= 1: #we have found the 2 edges which co_to lies between!
            end_edges_dict = {eds_is[2*i][0].index: 0}
            end_edges_dict[eds_is[2*i+1][0].index] = 1
            valid_end = True
    
    if not valid_end:
        print('co_to is not close to f_ind_to or not between 2 edge intersections of f_to and cut plane')
        return ([co_from, co_to],[], [], False, False, 'END_POINT')
    
    
    while True:
        # find edges in the face that cross the plane
        cross_eds = find_sorted_bmedges_crossing_plane(pt, no, f_cur.edges, epsilon, e_ind_from, co_from)
        edge, i = cross_eds[0]
        verts += [i]
        eds_crossed += [edge]
        
        if len(edge.link_faces) == 1: 
            error = 'NON_MANIFOLD'
            break  #end of non manifold mesh
        
        if edge.index in end_edges_dict:  #we found an edge in the ending face.
            #print('found an edge in the ending face')
            #verts += [co_to]  #tack on the final point?
            found = True
            error = None
            break
        
        # get next face, edge, co
        f_next = next(f for f in edge.link_faces if f.index != find_current)
        faces_crossed += [f_next]
        find_next = f_next.index
        eind_next = edge.index
        co_next   = i
        
        if find_next in f_inds_dict:   #we might have looped, ngons can be crossed multiple times
            print('we have seen the next face before')
            if len(bme.faces[find_next].edges) <= 4: #can only cross a quad or tri onces
                print('quad or tri, we have looped to where we started')
                looped = True
                if f_inds_dict[find_next] != 0:
                    # loop is P-shaped (loop with a tail)
                    print('p shaped loop len %i, clipping tail %i' % (len(verts), f_inds_dict[find_next]))
                    verts = verts[f_inds_dict[find_next]:]      # clip off tail
                    error = 'P_LOOP'
                    break
            
            elif len(bme.faces[find_next].edges) > 4 and f_inds_dict[find_next] == 0:
                print('more than 4 edges, and the first face we started with')
                next_crosses = find_sorted_bmedges_crossing_plane(pt, no, f_next.edges, epsilon, eind_next, co_next)
                if all(e.index in e_inds_dict for e, i in next_crosses[1:]):  #all the other edges of the face have been seen, we have looped
                    print('looped, all the other edges in the ngon has been tested, and it was the first ngon we tested')
                    looped = True
                    error = 'NGON_SPECIAL'
                    break
            
            elif eind_next in e_inds_dict:
                print('looped when found an already crossed edge')
                looped = True
                verts.pop()
                error = 'NGON_SPECIAL'
                break
        
        elif limit_set and f_next not in limit_set:
            error = 'LIMIT_SET'
            break
            
        else:
            # leave breadcrumb if find_next not in the dict, we may cross the face mutliple
            #times so we don't want to add it repeatedly
            f_inds_dict[find_next] = len(f_inds_dict)
        

        #always record the tested edges, allows us to find out if we have looped on an arm
        #of an extruded E shaped NGon
        e_inds_dict[eind_next] = len(e_inds_dict)
        
        f_ind_from = find_current
        e_ind_from = eind_next
        co_from   = co_next
        f_cur = f_next
        find_current = find_next
    
    return (verts,eds_crossed, faces_crossed, looped, found, error)
Exemple #28
0
    def construct_face(self,
                       context,
                       grid_coord,
                       tile_xy,
                       origin_xy,
                       grid_up,
                       grid_right,
                       up_vector,
                       right_vector,
                       plane_normal,
                       face_index=None,
                       check_exists=True,
                       shift_vec=None,
                       threshold=None,
                       add_cursor=True):
        """
        Create a new face at grid_coord or remap the existing face
        :param context:
        :param grid_coord:
        :param tile_xy:
        :param origin_xy:
        :param grid_up:
        :param grid_right:
        :param up_vector:
        :param right_vector:
        :param plane_normal:
        :param face_index:
        :param check_exists:
        :param shift_vec:
        :param threshold:
        :param add_cursor:
        :return:
        """
        scene = context.scene
        data = scene.sprytile_data
        hit_loc = None
        hit_normal = None
        if check_exists:
            hit_loc, hit_normal, face_index, hit_dist = self.raycast_grid_coord(
                context, grid_coord[0], grid_coord[1], grid_up, grid_right,
                plane_normal)

        did_build = False
        # No face index, assume build face
        if face_index is None or face_index < 0:
            face_position = scene.cursor_location + grid_coord[
                0] * grid_right + grid_coord[1] * grid_up
            face_verts = self.get_build_vertices(face_position, grid_right,
                                                 grid_up, up_vector,
                                                 right_vector)
            face_index = self.create_face(context, face_verts)
            did_build = True
        # Face index was given and check exists flag is off, check for actual face
        elif check_exists is False:
            hit_loc, hit_normal, face_index, hit_dist = self.raycast_grid_coord(
                context, grid_coord[0], grid_coord[1], grid_up, grid_right,
                plane_normal)

        if face_index is None or face_index < 0:
            return None

        # Didn't create face, only want to remap face. Check for coplanarity and dot
        if did_build is False:
            check_dot = abs(plane_normal.dot(hit_normal))
            check_dot -= 1
            check_coplanar = distance_point_to_plane(hit_loc,
                                                     scene.cursor_location,
                                                     plane_normal)

            check_coplanar = abs(check_coplanar) < 0.05
            check_dot = abs(check_dot) < 0.05
            # Can't remap face
            if not check_coplanar or not check_dot:
                return None

        sprytile_uv.uv_map_face(context, up_vector, right_vector, tile_xy,
                                origin_xy, face_index, self.bmesh)

        if did_build and data.auto_merge:
            if threshold is None:
                threshold = (1 / data.world_pixels) * 1.25

            face = self.bmesh.faces[face_index]

            face_position += grid_right * 0.5 + grid_up * 0.5
            face_position += plane_normal * 0.01
            face_index = self.merge_doubles(context, face, face_position,
                                            -plane_normal, threshold)

        # Auto merge refreshes the mesh automatically
        self.refresh_mesh = not data.auto_merge

        if add_cursor and hit_loc is not None:
            self.add_virtual_cursor(hit_loc)
        return face_index
Exemple #29
0
def create_capsule(mesh, diameter=0.5, length=2, use_rot=False):
    length = length if diameter < length else diameter
    if diameter < 0:
        raise ValueError("Cannot create capsule with a diameter less than 0!")

    bm = bmesh.new()

    kwargs = {}
    if USE_LEGACY:
        kwargs["diameter"] = diameter
    else:
        kwargs["radius"] = diameter

    bmesh.ops.create_uvsphere(bm, u_segments=32, v_segments=16, **kwargs)
    bm.to_mesh(mesh)

    center = Vector()
    axis = Vector((0, 0, 1))

    # Get top and bottom halves of vertices
    top = []
    top_faces = []
    bottom = []
    bottom_faces = []

    amount = (length - diameter) * 2
    vec = Vector((0, 0, amount))

    for v in bm.verts:
        if distance_point_to_plane(v.co, center, axis) >= 0:
            top.append(v.co)
            for face in v.link_faces:
                if not face in top_faces:
                    top_faces.append(face)
        elif distance_point_to_plane(v.co, center, axis) <= 0:
            bottom.append(v.co)
            for face in v.link_faces:
                if not face in bottom_faces and face not in top_faces:
                    bottom_faces.append(face)

    # Extrude top half
    ret = bmesh.ops.extrude_face_region(bm, geom=top_faces)
    extruded = ret["geom"]
    del ret
    translate_verts = [
        v for v in extruded if isinstance(v, bmesh.types.BMVert)
    ]
    bmesh.ops.translate(bm, vec=vec / 2, verts=translate_verts)

    # Extrude bottom half
    ret = bmesh.ops.extrude_face_region(bm, geom=bottom_faces)
    extruded = ret["geom"]
    del ret
    translate_verts = [
        v for v in extruded if isinstance(v, bmesh.types.BMVert)
    ]
    bmesh.ops.translate(bm, vec=-vec / 2, verts=translate_verts)

    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)

    bm.to_mesh(mesh)
    bm.free()

    if use_rot:
        mesh.transform(Matrix.Rotation(radians(90.0), 4, "X"))

    return mesh
def cross_section_walker_endpoints(bme, pt, no, f_ind_from, e_ind_from, co_from, f_ind_to, co_to, epsilon, limit_set = None, max_iters = 10000):
    '''
    bme -  bmesh
    pt  - a point on cutting plane: mathutils.Vector
    no  - the normal of the cutting plane: mathutils.Vector
    f_ind_from - index of face which we are walking from: Int
    e_ind_from - index of the edge which we are stepping over: Int
    co_from - location of intersectino of e_ind_from edge and cutting plane:  mathutils.Vector
    f_end_to - index of face which we are walking toward
    co_to  - location of end point, not necessarily and edge intersection, often a racy_cast result in middle of a face
    
    returns tuple (verts,ed_inds, looped, found) by walking around a bmesh near the given plane
    verts: List of intersections of edges and cutting plane (in order) mathutils.Vector  co_2 is excluded
    eds crossed:  lost of the edges which were intersected(in orter)  Int.  
    looped is bool indicating if walk wrapped around bmesh, only true if did not find other face
    found is a bool indicating if the walk was able to find face f_ind_to  at point co_to
    '''
    bme.verts.ensure_lookup_table()
    bme.edges.ensure_lookup_table()
    bme.faces.ensure_lookup_table()
    
    # returned values
    verts = [co_from]
    eds_crossed = [bme.edges[e_ind_from]]
    faces_crossed = [bme.faces[f_ind_from]]
    looped = False
    found = False
    error = None
    
    #verify that the points are coplanar to the cut plane
    d0 = distance_point_to_plane(co_from, pt, no)
    df = distance_point_to_plane(co_to, pt, no)
    
    if d0 > epsilon or df > epsilon:
        print('not coplanar by epsilons standdards')
        print((d0, df, epsilon))
        return ([co_from, co_to],[], [],False, False, 'EPSILON')
    
    # track what faces we've seen
    f_inds_dict = {f_ind_from: 0}
    #track what edges we've seen  (more important with Ngons, we may traverse a face multiple times)
    e_inds_dict = {e_ind_from: 0}
    
    # get blender version
    bver = '%03d.%03d.%03d' % (bpy.app.version[0],bpy.app.version[1],bpy.app.version[2])
    if bver > '002.072.000':
        bme.edges.ensure_lookup_table();

    f_cur = next(f for f in bme.edges[e_ind_from].link_faces if f.index != f_ind_from)
    find_current = f_cur.index
    
    #find the edges we might cross at the end, make sure where we are headed is valid
    #co_end will be bweteen edges 0,1  2,3  3,4 even if f_end is a concve ngon sith 4,6,8... intersections
    valid_end = False
    f_end = bme.faces[f_ind_to]
    eds_is = find_bmedges_crossing_plane(pt, no, f_end.edges, epsilon)
    for i in range(0,int(len(eds_is)/2)):
        p0 = eds_is[2*i][1]
        p1 = eds_is[2*i + 1][1]
        
        line_loc, line_pct = intersect_point_line(co_to, p0, p1)
        if line_pct >= 0 and line_pct <= 1: #we have found the 2 edges which co_to lies between!
            end_edges_dict = {eds_is[2*i][0].index: 0}
            end_edges_dict[eds_is[2*i+1][0].index] = 1
            valid_end = True
    
    if not valid_end:
        print('co_to is not close to f_ind_to or not between 2 edge intersections of f_to and cut plane')
        return ([co_from, co_to],[], [], False, False, 'END_POINT')
    
    
    while True:
        # find edges in the face that cross the plane
        cross_eds = find_sorted_bmedges_crossing_plane(pt, no, f_cur.edges, epsilon, e_ind_from, co_from)
        edge, i = cross_eds[0]
        verts += [i]
        eds_crossed += [edge]
        
        if len(edge.link_faces) == 1: 
            error = 'NON_MANIFOLD'
            break  #end of non manifold mesh
        
        if edge.index in end_edges_dict:  #we found an edge in the ending face.
            #print('found an edge in the ending face')
            #verts += [co_to]  #tack on the final point?
            found = True
            error = None
            break
        
        # get next face, edge, co
        f_next = next(f for f in edge.link_faces if f.index != find_current)
        faces_crossed += [f_next]
        find_next = f_next.index
        eind_next = edge.index
        co_next   = i
        
        if find_next in f_inds_dict:   #we might have looped, ngons can be crossed multiple times
            print('we have seen the next face before')
            if len(bme.faces[find_next].edges) <= 4: #can only cross a quad or tri onces
                print('quad or tri, we have looped to where we started')
                looped = True
                if f_inds_dict[find_next] != 0:
                    # loop is P-shaped (loop with a tail)
                    print('p shaped loop len %i, clipping tail %i' % (len(verts), f_inds_dict[find_next]))
                    verts = verts[f_inds_dict[find_next]:]      # clip off tail
                    error = 'P_LOOP'
                    break
            
            elif len(bme.faces[find_next].edges) > 4 and f_inds_dict[find_next] == 0:
                print('more than 4 edges, and the first face we started with')
                next_crosses = find_sorted_bmedges_crossing_plane(pt, no, f_next.edges, epsilon, eind_next, co_next)
                if all(e.index in e_inds_dict for e, i in next_crosses[1:]):  #all the other edges of the face have been seen, we have looped
                    print('looped, all the other edges in the ngon has been tested, and it was the first ngon we tested')
                    looped = True
                    error = 'NGON_SPECIAL'
                    break
            
            elif eind_next in e_inds_dict:
                print('looped when found an already crossed edge')
                looped = True
                verts.pop()
                error = 'NGON_SPECIAL'
                break
        
        elif limit_set and f_next not in limit_set:
            error = 'LIMIT_SET'
            break
            
        else:
            # leave breadcrumb if find_next not in the dict, we may cross the face mutliple
            #times so we don't want to add it repeatedly
            f_inds_dict[find_next] = len(f_inds_dict)
        

        #always record the tested edges, allows us to find out if we have looped on an arm
        #of an extruded E shaped NGon
        e_inds_dict[eind_next] = len(e_inds_dict)
        
        f_ind_from = find_current
        e_ind_from = eind_next
        co_from   = co_next
        f_cur = f_next
        find_current = find_next
    
    return (verts,eds_crossed, faces_crossed, looped, found, error)
Exemple #31
0
def convex_hull_3d(vecs, eps:'距離がこれ以下なら同一平面と見做す'=1e-6):
    """三次元又は二次元の凸包を求める"""
    if len(vecs) <= 1:
        return list(range(len(vecs)))

    verts = [_Vert(i, v) for i, v in enumerate(vecs)]

    # なるべく離れている二頂点を求める
    medium = reduce(lambda a, b: a + b, vecs) / len(vecs)
    v1 = max(verts, key=lambda v: (v.co - medium).length)
    v2 = max(verts, key=lambda v: (v.co - v1.co).length)
    line = v2.co - v1.co
    if line.length <= eps:
        # 全ての頂点が重なる
        return [0]

    verts.remove(v1)
    verts.remove(v2)
    if not verts:
        return [v1.index, v2.index]

    # 三角形を構成する為の頂点を求める
    v3 = max(verts, key=lambda v: line.cross(v.co - v1.co).length)
    if line.normalized().cross(v3.co - v1.co).length <= eps:
        # 全ての頂点が同一線上にある
        return [v1.index, v2.index]
    
    verts.remove(v3)
    if not verts:
        return [v1.index, v2.index, v3.index]

    # 四面体を構成する為の頂点を求める
    normal = geom.normal(v1.co, v2.co, v3.co)
    def key_func(v):
        return abs(geom.distance_point_to_plane(v.co, v1.co, normal))
    v4 = max(verts, key=key_func)
    if key_func(v4) <= eps:
        # 全ての頂点が平面上にある
        quat = normal.rotation_difference(Vector((0, 0, 1)))
        vecs_2d = [(quat * v).to_2d() for v in vecs]
        return convex_hull_2d(vecs_2d, eps)

    verts.remove(v4)

    # 四面体作成
    #       ^ normal
    #    v3 |
    #     / |\
    # v1 /____\v2
    #    \    /
    #     \  /
    #     v4

    if geom.distance_point_to_plane(v4.co, v1.co, normal) < 0.0:
        faces = [_Face(v1, v2, v3),
                 _Face(v1, v4, v2), _Face(v2, v4, v3), _Face(v3, v4, v1)]
    else:
        faces = [_Face(v1, v3, v2),
                 _Face(v1, v2, v4), _Face(v2, v3, v4), _Face(v3, v1, v4)]

    # 残りの頂点を各面に分配
    _divide_outer_verts(faces, verts, eps)

    # edge_faces作成
    edge_faces = defaultdict(list)
    for face in faces:
        for ekey in face.edge_keys:
            edge_faces[ekey].append(face)

    while True:
        added = False
        for i in range(len(faces)):
            try:
                face = faces[i]
            except:
                break
            if not face.outer_verts:
                continue

            v1 = max(face.outer_verts, key=lambda v: face.distance(v.co))
            if face.distance(v1.co) > eps:
                # 凸包になるようにv1から放射状に面を貼る
                added = True

                # 隠れて不要となる面を求める
                remove_faces = set()
                _find_remove_faces_re(remove_faces, v1.co, face, edge_faces,
                                      eps)

                # remove_facesを多面体から除去して穴を開ける
                for f in remove_faces:
                    for ekey in f.edge_keys:
                        edge_faces[ekey].remove(f)
                    faces.remove(f)

                # 穴に面を貼る
                new_faces = []
                ekey_count = defaultdict(int)
                for f in remove_faces:
                    for ekey in f.edge_keys:
                        ekey_count[ekey] += 1
                for ekey, cnt in ekey_count.items():
                    if cnt != 1:
                        continue
                    linkface = edge_faces[ekey][0]
                    v2, v3 = ekey
                    if linkface.verts[linkface.verts.index(v2) - 1] != v3:
                        v2, v3 = v3, v2
                    new_face = _Face(v1, v2, v3)
                    for key in new_face.edge_keys:
                        edge_faces[key].append(new_face)
                    new_faces.append(new_face)
                faces.extend(new_faces)

                # 頂点の再分配
                outer_verts = reduce(lambda a, b: a + b,
                                     (f.outer_verts for f in remove_faces))
                if v1 in outer_verts:
                    outer_verts.remove(v1)
                _divide_outer_verts(new_faces, outer_verts, eps)

            else:
                face.outer_verts = []

        if not added:
            break

    return [[v.index for v in f.verts] for f in faces]
Exemple #32
0
 def distance(self, v):
     return geom.distance_point_to_plane(v, self.verts[0].co, self.normal)
Exemple #33
0
def OBB(vecs, r_indices=None, eps=1e-6):
    """Convex hull を用いたOBBを返す。
    Z->Y->Xの順で長さが最少となる軸を求める。
    :param vecs: list of Vector
    :type vecs: list | tuple
    :param r_indices: listを渡すとconvexhullの結果を格納する
    :type r_indices: None | list
    :param eps: 種々の計算の閾値
    :return:
        (matrix, obb_size)
        matrix:
            type: Matrx
            OBBの回転と中心を表す。vecsが二次元ベクトルの場合は3x3, 三次元なら4x4。
        obb_size:
            type: Vector
            OBBの各軸の長さ。vecsと同じ次元。
    :rtype: (Matrix, Vector)
    """

    if not vecs:
        return None, None

    # 2D ----------------------------------------------------------------------
    if len(vecs[0]) == 2:
        mat = Matrix.Identity(3)
        bb_size = Vector((0, 0))

        indices = convex_hull_2d(vecs, eps)
        if r_indices:
            r_indices[:] = indices

        if len(indices) == 1:
            mat.col[2][:2] = vecs[0]
        elif len(indices) == 2:
            v1 = vecs[indices[0]]
            v2 = vecs[indices[1]]
            xaxis = (v2 - v1).normalized()
            angle = math.atan2(xaxis[1], xaxis[0])
            mat2 = Matrix.Rotation(angle, 2)
            mat.col[0][:2] = mat2.col[0]
            mat.col[1][:2] = mat2.col[1]
            mat.col[2][:2] = (v1 + v2) / 2
            bb_size[0] = (v2 - v1).length
        else:
            yaxis = _closest_axis_on_plane(vecs, indices)
            angle = math.atan2(yaxis[1], yaxis[0]) - math.pi / 2  # X軸
            mat2 = Matrix.Rotation(angle, 2)
            imat2 = Matrix.Rotation(-angle, 2)
            rotvecs = [imat2 * v for v in vecs]
            loc = Vector((0, 0))
            for i in range(2):
                rotvecs.sort(key=lambda v: v[i])
                bb_size[i] = rotvecs[-1][i] - rotvecs[0][i]
                loc[i] = (rotvecs[0][i] + rotvecs[-1][i]) / 2
            mat.col[0][:2] = mat2.col[0]
            mat.col[1][:2] = mat2.col[1]
            mat.col[2][:2] = mat2 * loc
        return mat, bb_size

    # 3D ----------------------------------------------------------------------
    mat = Matrix.Identity(4)
    bb_size = Vector((0, 0, 0))

    indices = convex_hull(vecs, eps)

    if r_indices:
        r_indices[:] = indices

    if isinstance(indices[0], int):  # 2d
        if len(indices) == 1:
            mat.col[3][:3] = vecs[0]
            return mat, bb_size

        elif len(indices) == 2:
            # 同一線上
            v1 = vecs[indices[0]]
            v2 = vecs[indices[1]]
            xaxis = (v2 - v1).normalized()
            quat = Vector((1, 0, 0)).rotation_difference(xaxis)
            mat = quat.to_matrix().to_4x4()
            mat.col[3][:3] = (v1 + v2) / 2
            bb_size[0] = (v2 - v1).length
            return mat, bb_size

        else:
            # 同一平面上
            medium = reduce(lambda a, b: a + b, vecs) / len(vecs)
            v1 = max(vecs, key=lambda v: (v - medium).length)
            v2 = max(vecs, key=lambda v: (v - v1).length)
            line = v2 - v1
            v3 = max(vecs, key=lambda v: line.cross(v - v1).length)
            zaxis = geom.normal(v1, v2, v3)
            if zaxis[2] < 0.0:
                zaxis.negate()

            quat = zaxis.rotation_difference(Vector((0, 0, 1)))
            rotvecs = [quat * v for v in vecs]
            indices_2d = indices

    else:  # 3d
        indices_set = set(chain(*indices))
        zaxis = None
        dist = 0.0
        # 最も距離の近い面(平面)と頂点を求める
        for tri in indices:
            v1, v2, v3 = [vecs[i] for i in tri]
            normal = geom.normal(v1, v2, v3)
            d = 0.0
            for v4 in (vecs[i] for i in indices_set if i not in tri):
                f = abs(geom.distance_point_to_plane(v4, v1, normal))
                d = max(f, d)
            if zaxis is None or d < dist:
                zaxis = -normal
                dist = d

        quat = zaxis.rotation_difference(Vector((0, 0, 1)))
        rotvecs = [(quat * v).to_2d() for v in vecs]
        indices_2d = convex_hull_2d(rotvecs, eps)

    yaxis = _closest_axis_on_plane(rotvecs, indices_2d)
    yaxis = quat.inverted() * yaxis.to_3d()

    xaxis = yaxis.cross(zaxis)
    xaxis.normalize()  # 不要?

    mat.col[0][:3] = xaxis
    mat.col[1][:3] = yaxis
    mat.col[2][:3] = zaxis

    # OBBの大きさと中心を求める
    imat = mat.inverted()
    rotvecs = [imat * v for v in vecs]
    loc = Vector()
    for i in range(3):
        rotvecs.sort(key=lambda v: v[i])
        bb_size[i] = rotvecs[-1][i] - rotvecs[0][i]
        loc[i] = (rotvecs[0][i] + rotvecs[-1][i]) / 2
    mat.col[3][:3] = mat * loc
    return mat, bb_size
Exemple #34
0
    def execute(self, context, scene, ray_origin, ray_vector, is_start):
        data = scene.sprytile_data
        grid = sprytile_utils.get_grid(context, context.object.sprytile_gridid)
        tile_xy = (grid.tile_selection[0], grid.tile_selection[1])

        # Get vectors for grid, without rotation
        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(
            scene,
            with_rotation=False
        )
        # Rotate the vectors
        rotation = Quaternion(plane_normal, data.mesh_rotate)
        up_vector = rotation * up_vector
        right_vector = rotation * right_vector

        # Used to move raycast slightly along ray vector
        shift_vec = ray_vector.normalized() * 0.001

        # raycast grid to get the grid position under the mouse
        grid_coord, grid_right, grid_up, plane_pos = sprytile_utils.raycast_grid(
            scene, context,
            up_vector, right_vector, plane_normal,
            ray_origin, ray_vector,
            as_coord=True
        )

        # Record starting position of stroke
        if is_start:
            self.start_coord = grid_coord
        # Not starting, filter out when can build
        elif self.start_coord is not None:
            tolerance_min = (floor(grid.tile_selection[2] * 0.25),
                             floor(grid.tile_selection[3] * 0.25))
            coord_offset = (
                (grid_coord[0] - self.start_coord[0]) % grid.tile_selection[2],
                (grid_coord[1] - self.start_coord[1]) % grid.tile_selection[3]
            )
            if coord_offset[0] > 0 or coord_offset[1] > 0:
                return

        # Get the area to build
        offset_tile_id, offset_grid, coord_min, coord_max = sprytile_utils.get_grid_area(
            grid.tile_selection[2],
            grid.tile_selection[3],
            data.uv_flip_x, data.uv_flip_y
        )

        # Check if joining multi tile faces
        grid_no_spacing = sprytile_utils.grid_no_spacing(grid)
        is_single_pixel = sprytile_utils.grid_is_single_pixel(grid)
        do_join = is_single_pixel
        if do_join is False:
            do_join = grid_no_spacing and data.auto_join

        # Raycast under mouse
        hit_loc, hit_normal, hit_face_idx, hit_dist = self.modal.raycast_object(
            context.object, ray_origin, ray_vector)

        # Hit a face
        if hit_face_idx is not None:
            # Determine if face can be painted
            plane_hit = intersect_line_plane(ray_origin, ray_origin + ray_vector,
                                             scene.cursor_location, plane_normal)
            plane_dist = (plane_hit - ray_origin).magnitude
            difference = abs(hit_dist - plane_dist)
            # If valid for painting instead of creating…
            if difference < 0.01 or hit_dist < plane_dist:
                if do_join is False:
                    return
                check_dot = abs(plane_normal.dot(hit_normal))
                check_dot -= 1
                check_coplanar = distance_point_to_plane(hit_loc, scene.cursor_location, plane_normal)

                check_coplanar = abs(check_coplanar) < 0.05
                check_dot = abs(check_dot) < 0.05
                # Paint if coplanar and dot angle checks out
                if check_coplanar and check_dot:
                    face, verts, uvs, target_grid, data, target_img, tile_xy = tool_paint.ToolPaint.process_preview(
                                                    self.modal, context,
                                                    scene, hit_face_idx)
                    sprytile_uv.apply_uvs(context, face, uvs, target_grid,
                                          self.modal.bmesh, data, target_img,
                                          tile_xy, origin_xy=tile_xy)
                # Hit a face, that could have been painted, don't do anything else
                return

        # Build mode with auto join
        if do_join:
            origin_coord = scene.cursor_location + \
                           (grid_coord[0] + coord_min[0]) * grid_right + \
                           (grid_coord[1] + coord_min[1]) * grid_up
            self.modal.add_virtual_cursor(origin_coord)

            size_x = (coord_max[0] - coord_min[0]) + 1
            size_y = (coord_max[1] - coord_min[1]) + 1

            size_x *= grid.grid[0]
            size_y *= grid.grid[1]

            grid_right *= size_x / grid.grid[0]
            grid_up *= size_y / grid.grid[1]

            verts = self.modal.get_build_vertices(origin_coord,
                                                  grid_right, grid_up,
                                                  up_vector, right_vector)
            face_index = self.modal.create_face(context, verts)
            if face_index is None:
                return
            vtx_center = Vector((0, 0, 0))
            for vtx in verts:
                vtx_center += vtx
            vtx_center /= len(verts)

            origin_xy = (grid.tile_selection[0],
                         grid.tile_selection[1])

            target_img = sprytile_utils.get_grid_texture(context.object, grid)

            uvs = sprytile_uv.get_uv_pos_size(data, target_img.size, grid,
                                              origin_xy, size_x, size_y,
                                              up_vector, right_vector,
                                              verts, vtx_center)
            sprytile_uv.apply_uvs(context, self.modal.bmesh.faces[face_index],
                                  uvs, grid, self.modal.bmesh,
                                  data, target_img, origin_xy)

            if data.auto_merge:
                threshold = (1 / data.world_pixels) * min(2, grid.grid[0], grid.grid[1])
                face = self.modal.bmesh.faces[face_index]
                self.modal.merge_doubles(context, face, vtx_center, -plane_normal, threshold)
        # Build mode without auto join
        else:
            virtual_cursor = scene.cursor_location + \
                             (grid_coord[0] * grid_right) + \
                             (grid_coord[1] * grid_up)
            self.modal.add_virtual_cursor(virtual_cursor)
            # Loop through grid coordinates to build
            for i in range(len(offset_grid)):
                grid_offset = offset_grid[i]
                tile_offset = offset_tile_id[i]

                grid_pos = [grid_coord[0] + grid_offset[0], grid_coord[1] + grid_offset[1]]
                tile_pos = [tile_xy[0] + tile_offset[0], tile_xy[1] + tile_offset[1]]

                self.modal.construct_face(context, grid_pos,
                                          tile_pos, tile_xy,
                                          grid_up, grid_right,
                                          up_vector, right_vector, plane_normal,
                                          shift_vec=shift_vec)
Exemple #35
0
    def execute(self, context, scene, ray_origin, ray_vector):
        grid = sprytile_utils.get_grid(context, context.object.sprytile_gridid)
        tile_xy = (grid.tile_selection[0], grid.tile_selection[1])

        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(
            scene)
        hit_loc, hit_normal, face_index, hit_dist = self.modal.raycast_object(
            context.object, ray_origin, ray_vector)

        # Used to move raycast slightly along ray vector
        shift_vec = ray_vector.normalized() * 0.001

        # If raycast hit the mesh...
        if face_index is not None:
            # The face is valid for painting if hit face
            # is facing same way as plane normal and is coplanar to target plane
            check_dot = abs(plane_normal.dot(hit_normal))
            check_dot -= 1
            check_coplanar = distance_point_to_plane(hit_loc,
                                                     scene.cursor_location,
                                                     plane_normal)

            check_coplanar = abs(check_coplanar) < 0.05
            check_dot = abs(check_dot) < 0.05
            # Hit a face that is valid for painting
            if check_dot and check_coplanar:
                self.modal.add_virtual_cursor(hit_loc)
                # Change UV of this face instead
                face_up, face_right = self.modal.get_face_up_vector(
                    context, face_index)
                if face_up is not None and face_up.dot(up_vector) < 0.95:
                    data = context.scene.sprytile_data
                    rotate_matrix = Matrix.Rotation(data.mesh_rotate, 4,
                                                    hit_normal)
                    up_vector = rotate_matrix * face_up
                    right_vector = rotate_matrix * face_right
                sprytile_uv.uv_map_face(context, up_vector, right_vector,
                                        tile_xy, face_index, self.modal.bmesh)
                if scene.sprytile_data.cursor_flow:
                    self.modal.flow_cursor(context, face_index, hit_loc)
                return

        # Raycast did not hit the mesh, raycast to the virtual grid
        face_position, x_vector, y_vector, plane_cursor = sprytile_utils.raycast_grid(
            scene, context, up_vector, right_vector, plane_normal, ray_origin,
            ray_vector)
        # Failed to hit the grid
        if face_position is None:
            return

        # If raycast hit mesh, compare distance of grid hit and mesh hit
        if hit_loc is not None:
            grid_hit_dist = (face_position - ray_origin).magnitude
            # Mesh hit closer than grid hit, don't do anything
            if hit_dist < grid_hit_dist:
                return

        # store plane_cursor, for deciding where to move actual cursor if auto cursor mode is on
        self.modal.add_virtual_cursor(plane_cursor)
        # Build face and UV map it
        face_vertices = self.modal.get_build_vertices(face_position, x_vector,
                                                      y_vector, up_vector,
                                                      right_vector)
        face_index = self.modal.create_face(context, face_vertices)

        face_up, face_right = self.modal.get_face_up_vector(
            context, face_index)
        if face_up is not None and face_up.dot(up_vector) < 0.95:
            data = context.scene.sprytile_data
            rotate_matrix = Matrix.Rotation(data.mesh_rotate, 4, plane_normal)
            up_vector = rotate_matrix * face_up
            right_vector = rotate_matrix * face_right

        sprytile_uv.uv_map_face(context, up_vector, right_vector, tile_xy,
                                face_index, self.modal.bmesh)

        if scene.sprytile_data.auto_merge:
            face = self.modal.bmesh.faces[face_index]
            face.select = True
            # Find the face center, to raycast from later
            face_center = context.object.matrix_world * face.calc_center_bounds(
            )
            # Move face center back a little for ray casting
            face_center -= shift_vec

            threshold = (1 / context.scene.sprytile_data.world_pixels) * 2
            bpy.ops.mesh.remove_doubles(threshold=threshold,
                                        use_unselected=True)

            for el in [
                    self.modal.bmesh.faces, self.modal.bmesh.verts,
                    self.modal.bmesh.edges
            ]:
                el.index_update()
                el.ensure_lookup_table()

            # Modified the mesh, refresh and raycast to find the new face index
            self.modal.update_bmesh_tree(context)
            loc, norm, new_face_idx, hit_dist = self.modal.raycast_object(
                context.object, face_center, ray_vector)
            if new_face_idx is not None:
                self.modal.bmesh.faces[new_face_idx].select = False
                face_index = new_face_idx
            else:
                face_index = -1

        # Auto merge refreshes the mesh automatically
        self.modal.refresh_mesh = not scene.sprytile_data.auto_merge

        if scene.sprytile_data.cursor_flow and face_index is not None and face_index > -1:
            self.modal.flow_cursor(context, face_index, plane_cursor)
Exemple #36
0
    def build_preview(self, context, scene, ray_origin, ray_vector):
        obj = context.object
        data = scene.sprytile_data

        grid_id = obj.sprytile_gridid
        target_grid = sprytile_utils.get_grid(context, grid_id)

        # Reset can build flag
        self.can_build = False

        target_img = sprytile_utils.get_grid_texture(obj, target_grid)
        if target_img is None:
            self.modal.clear_preview_data()
            return

        # If building on base layer, get from current virtual grid
        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(
            scene, False)
        # Building on decal layer, get from face under mouse
        if data.work_layer == 'DECAL_1' and data.lock_normal is False:
            location, hit_normal, face_index, distance = self.modal.raycast_object(
                context.object, ray_origin, ray_vector)
            # For decals, if not hitting the object don't draw preview
            if hit_normal is None:
                self.modal.clear_preview_data()
                return

            # Do a coplanar check between hit location and cursor
            grid_origin = scene.cursor_location.copy()
            grid_origin += hit_normal * data.mesh_decal_offset

            check_coplanar = distance_point_to_plane(location, grid_origin,
                                                     hit_normal)
            check_coplanar = abs(check_coplanar) < 0.05
            if check_coplanar is False:
                self.modal.clear_preview_data()
                return

            face_up, face_right = self.modal.get_face_up_vector(
                context, face_index, 0.4, bias_right=True)
            if face_up is not None and face_right is not None:
                plane_normal = hit_normal
                up_vector = face_up
                right_vector = face_right
            else:
                self.modal.clear_preview_data()
                return

        rotation = Quaternion(plane_normal, data.mesh_rotate)

        up_vector = rotation * up_vector
        right_vector = rotation * right_vector

        # Raycast to the virtual grid
        face_position, x_vector, y_vector, plane_cursor = sprytile_utils.raycast_grid(
            scene, context, up_vector, right_vector, plane_normal, ray_origin,
            ray_vector)

        if face_position is None:
            self.modal.clear_preview_data()
            return

        # Passed can build checks, set flag to true
        self.can_build = True

        offset_tile_id, offset_grid, coord_min, coord_max = sprytile_utils.get_grid_area(
            target_grid.tile_selection[2], target_grid.tile_selection[3],
            data.uv_flip_x, data.uv_flip_y)

        grid_no_spacing = sprytile_utils.grid_no_spacing(target_grid)
        # No spacing in grid, automatically join the preview together
        if grid_no_spacing:
            origin_coord = face_position + coord_min[0] * x_vector + coord_min[
                1] * y_vector

            size_x = (coord_max[0] - coord_min[0]) + 1
            size_y = (coord_max[1] - coord_min[1]) + 1

            size_x *= target_grid.grid[0]
            size_y *= target_grid.grid[1]

            x_vector *= size_x / target_grid.grid[0]
            y_vector *= size_y / target_grid.grid[1]

            preview_verts = self.modal.get_build_vertices(
                origin_coord, x_vector, y_vector, up_vector, right_vector)
            vtx_center = Vector((0, 0, 0))
            for vtx in preview_verts:
                vtx_center += vtx
            vtx_center /= len(preview_verts)

            origin_xy = (target_grid.tile_selection[0],
                         target_grid.tile_selection[1])

            preview_uvs = sprytile_uv.get_uv_pos_size(
                data, target_img.size, target_grid, origin_xy, size_x, size_y,
                up_vector, right_vector, preview_verts, vtx_center)
            self.modal.set_preview_data(preview_verts, preview_uvs)
            return

        # Spaced grids need to be tiled
        preview_verts = []
        preview_uvs = []
        for i in range(len(offset_tile_id)):
            grid_offset = offset_grid[i]
            tile_offset = offset_tile_id[i]

            x_offset = x_vector * grid_offset[0]
            y_offset = y_vector * grid_offset[1]

            coord_position = face_position + x_offset + y_offset
            coord_verts = self.modal.get_build_vertices(
                coord_position, x_vector, y_vector, up_vector, right_vector)
            # Get the center of the preview verts
            vtx_center = Vector((0, 0, 0))
            for vtx in coord_verts:
                vtx_center += vtx
            vtx_center /= len(coord_verts)

            # Calculate the tile with offset
            tile_xy = (target_grid.tile_selection[0] + tile_offset[0],
                       target_grid.tile_selection[1] + tile_offset[1])

            coord_uvs = sprytile_uv.get_uv_positions(data, target_img.size,
                                                     target_grid, up_vector,
                                                     right_vector, tile_xy,
                                                     coord_verts, vtx_center)

            preview_verts.extend(coord_verts)
            preview_uvs.extend(coord_uvs)

        self.modal.set_preview_data(preview_verts, preview_uvs)
Exemple #37
0
 def distance(self, v):
     return geom.distance_point_to_plane(v, self.verts[0].co, self.normal)
Exemple #38
0
def convex_hull_3d(vecs, eps: '距離がこれ以下なら同一平面と見做す' = 1e-6):
    """三次元又は二次元の凸包を求める"""
    if len(vecs) <= 1:
        return list(range(len(vecs)))

    verts = [_Vert(i, v) for i, v in enumerate(vecs)]

    # なるべく離れている二頂点を求める
    medium = reduce(lambda a, b: a + b, vecs) / len(vecs)
    v1 = max(verts, key=lambda v: (v.co - medium).length)
    v2 = max(verts, key=lambda v: (v.co - v1.co).length)
    line = v2.co - v1.co
    if line.length <= eps:
        # 全ての頂点が重なる
        return [0]

    verts.remove(v1)
    verts.remove(v2)
    if not verts:
        return [v1.index, v2.index]

    # 三角形を構成する為の頂点を求める
    v3 = max(verts, key=lambda v: line.cross(v.co - v1.co).length)
    if line.normalized().cross(v3.co - v1.co).length <= eps:
        # 全ての頂点が同一線上にある
        return [v1.index, v2.index]

    verts.remove(v3)
    if not verts:
        return [v1.index, v2.index, v3.index]

    # 四面体を構成する為の頂点を求める
    normal = geom.normal(v1.co, v2.co, v3.co)

    def key_func(v):
        return abs(geom.distance_point_to_plane(v.co, v1.co, normal))

    v4 = max(verts, key=key_func)
    if key_func(v4) <= eps:
        # 全ての頂点が平面上にある
        quat = normal.rotation_difference(Vector((0, 0, 1)))
        vecs_2d = [(quat * v).to_2d() for v in vecs]
        return convex_hull_2d(vecs_2d, eps)

    verts.remove(v4)

    # 四面体作成
    #       ^ normal
    #    v3 |
    #     / |\
    # v1 /____\v2
    #    \    /
    #     \  /
    #     v4

    if geom.distance_point_to_plane(v4.co, v1.co, normal) < 0.0:
        faces = [
            _Face(v1, v2, v3),
            _Face(v1, v4, v2),
            _Face(v2, v4, v3),
            _Face(v3, v4, v1)
        ]
    else:
        faces = [
            _Face(v1, v3, v2),
            _Face(v1, v2, v4),
            _Face(v2, v3, v4),
            _Face(v3, v1, v4)
        ]

    # 残りの頂点を各面に分配
    _divide_outer_verts(faces, verts, eps)

    # edge_faces作成
    edge_faces = defaultdict(list)
    for face in faces:
        for ekey in face.edge_keys:
            edge_faces[ekey].append(face)

    while True:
        added = False
        for i in range(len(faces)):
            try:
                face = faces[i]
            except:
                break
            if not face.outer_verts:
                continue

            v1 = max(face.outer_verts, key=lambda v: face.distance(v.co))
            if face.distance(v1.co) > eps:
                # 凸包になるようにv1から放射状に面を貼る
                added = True

                # 隠れて不要となる面を求める
                remove_faces = set()
                _find_remove_faces_re(remove_faces, v1.co, face, edge_faces,
                                      eps)

                # remove_facesを多面体から除去して穴を開ける
                for f in remove_faces:
                    for ekey in f.edge_keys:
                        edge_faces[ekey].remove(f)
                    faces.remove(f)

                # 穴に面を貼る
                new_faces = []
                ekey_count = defaultdict(int)
                for f in remove_faces:
                    for ekey in f.edge_keys:
                        ekey_count[ekey] += 1
                for ekey, cnt in ekey_count.items():
                    if cnt != 1:
                        continue
                    linkface = edge_faces[ekey][0]
                    v2, v3 = ekey
                    if linkface.verts[linkface.verts.index(v2) - 1] != v3:
                        v2, v3 = v3, v2
                    new_face = _Face(v1, v2, v3)
                    for key in new_face.edge_keys:
                        edge_faces[key].append(new_face)
                    new_faces.append(new_face)
                faces.extend(new_faces)

                # 頂点の再分配
                outer_verts = reduce(lambda a, b: a + b,
                                     (f.outer_verts for f in remove_faces))
                if v1 in outer_verts:
                    outer_verts.remove(v1)
                _divide_outer_verts(new_faces, outer_verts, eps)

            else:
                face.outer_verts = []

        if not added:
            break

    return [[v.index for v in f.verts] for f in faces]
    def construct_face(self,
                       context,
                       grid_coord,
                       grid_size,
                       tile_xy,
                       tile_origin,
                       grid_up,
                       grid_right,
                       up_vector,
                       right_vector,
                       plane_normal,
                       require_base_layer=False,
                       work_layer_mask=0,
                       threshold=None):
        """
        Create a new face at grid_coord or remap the existing face
        :type work_layer_mask: bitmask integer
        :param context:
        :param grid_coord: Grid coordinate to create at
        :param grid_size: Tile unit size of face
        :param tile_xy: Tilegrid coordinate to map
        :param tile_origin: Origin of tilegrid coordinate, for mapping data
        :param grid_up:
        :param grid_right:
        :param up_vector:
        :param right_vector:
        :param plane_normal:
        :param require_base_layer:
        :param threshold:
        :return:
        """
        scene = context.scene
        data = scene.sprytile_data

        # Run a raycast on target work layer mask
        hit_loc, hit_normal, face_index, hit_dist = self.raycast_grid_coord(
            context,
            grid_coord[0],
            grid_coord[1],
            grid_up,
            grid_right,
            plane_normal,
            work_layer_mask=work_layer_mask)

        # Didn't hit target layer, and require base layer
        if face_index is None and require_base_layer:
            # Check if there is a base layer underneath
            base_hit_loc, hit_normal, base_face_index, base_hit_dist = self.raycast_grid_coord(
                context, grid_coord[0], grid_coord[1], grid_up, grid_right,
                plane_normal)
            # Didn't hit required base layer, do nothing
            if base_face_index is None:
                return None

        # Calculate where the origin of the grid is
        grid_origin = scene.cursor_location.copy()
        # If doing mesh decal, offset the grid origin
        if data.work_layer == 'DECAL_1':
            grid_origin += plane_normal * data.mesh_decal_offset

        did_build = False
        # No face index, assume build face
        if face_index is None or face_index < 0:
            face_position = grid_origin + grid_coord[
                0] * grid_right + grid_coord[1] * grid_up

            face_verts = self.get_build_vertices(face_position,
                                                 grid_right * grid_size[0],
                                                 grid_up * grid_size[1],
                                                 up_vector, right_vector)
            face_index = self.create_face(context, face_verts)
            did_build = True

        if face_index is None or face_index < 0:
            return None

        # Didn't create face, only want to remap face. Check for coplanarity and dot
        if did_build is False:
            check_dot = abs(plane_normal.dot(hit_normal))
            check_dot -= 1
            check_coplanar = distance_point_to_plane(hit_loc, grid_origin,
                                                     plane_normal)

            check_coplanar = abs(check_coplanar) < 0.05
            check_dot = abs(check_dot) < 0.05
            # Can't remap face
            if not check_coplanar or not check_dot:
                return None

        sprytile_uv.uv_map_face(context, up_vector, right_vector, tile_xy,
                                tile_origin, face_index, self.bmesh, grid_size)

        if did_build and data.auto_merge:
            if threshold is None:
                threshold = (1 / data.world_pixels) * 1.25

            face = self.bmesh.faces[face_index]

            face_position += grid_right * 0.5 + grid_up * 0.5
            face_position += plane_normal * 0.01
            face_index = self.merge_doubles(context, face, face_position,
                                            -plane_normal, threshold)

        # Auto merge refreshes the mesh automatically
        self.refresh_mesh = not data.auto_merge

        return face_index
Exemple #40
0
    def auto_detect_curve_bounded_plane(self, mesh, tolerance=0.001):
        bm = bmesh.new()
        bm.from_mesh(mesh)
        bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges)

        bm.faces.ensure_lookup_table()
        faces = bm.faces
        normal = faces[0].normal
        point = faces[0].verts[0].co
        for face in faces:
            pt_f = face.verts[0].co
            if (
                normal.dot(face.normal) < 1 - tolerance
                or abs(geometry.distance_point_to_plane(pt_f, point, normal)) > tolerance
            ):
                raise ValueError("All faces must be coplanar and have same orientation")

        loop_edges = set()
        for face in faces:
            potential_edges = set(face.edges)
            for face2 in faces:
                if face == face2:
                    continue
                potential_edges -= set(face2.edges)
            loop_edges |= potential_edges

        # Create loops from edges
        loops = []
        while loop_edges:
            edge = loop_edges.pop()
            loop = [edge]
            has_found_connected_edge = True
            while has_found_connected_edge:
                has_found_connected_edge = False
                for edge in loop_edges.copy():
                    edge_verts = set(edge.verts)
                    if edge_verts & set(loop[0].verts):
                        loop.insert(0, edge)
                        loop_edges.remove(edge)
                        has_found_connected_edge = True
                    elif edge_verts & set(loop[-1].verts):
                        loop.append(edge)
                        loop_edges.remove(edge)
                        has_found_connected_edge = True
            loops.append(loop)

        # Determine outer loop
        max_area = 0
        outer_loop = None
        inner_loops = []

        for loop in loops:
            loop_vertices = []
            total_edges = len(loop)
            for i, edge in enumerate(loop):
                if i + 1 == total_edges and edge.verts[0] in loop[i - 1].verts:
                    loop_vertices.append(edge.verts[0])
                elif i + 1 == total_edges and edge.verts[1] in loop[i - 1].verts:
                    loop_vertices.append(edge.verts[1])
                elif edge.verts[0] in loop[i + 1].verts:
                    loop_vertices.append(edge.verts[1])
                elif edge.verts[1] in loop[i + 1].verts:
                    loop_vertices.append(edge.verts[0])

            loop_bm = bmesh.new()
            for vert in loop_vertices:
                loop_bm.verts.new(vert.co)
            face = loop_bm.faces.new(loop_bm.verts)

            loop_vertex_indices = [v.index for v in loop_vertices]
            face_area = face.calc_area()
            if face_area > max_area:
                max_area = face_area
                outer_loop = loop_vertex_indices
            inner_loops.append(loop_vertex_indices)
            loop_bm.free()

        inner_loops.remove(outer_loop)

        bm.to_mesh(mesh)
        mesh.update()
        bm.free()

        return {"outer_curve": outer_loop, "inner_curves": inner_loops}
def main(self, context):
    obj = context.active_object
    me = obj.data
    bm = bmesh.from_edit_mesh(me)

    face_sets = []

    faces = []

    force_orthogonal = self.properties.force_orthogonal
    method = self.properties.method

    # store data
    for f in bm.faces:
        if f.select:
            if method == 'AVERAGE':
                faces.append (f)

            elif method == 'INDIVIDUAL':
                face_sets.append ([f])

    if method == 'AVERAGE':
        face_sets.append (faces)

    for fs in face_sets:

        normal = Vector ()
        center = Vector ()

        for f in fs:
            normal += f.normal
            center += f.calc_center_median_weighted ()

        normal.normalize ()
        center /= len (fs)

        if force_orthogonal:
            x = abs (normal.x)
            y = abs (normal.y)
            z = abs (normal.z)

            if x > y and x > z:
                normal.x /= x
                normal.y = 0.0
                normal.z = 0.0

            if y > x and y > z:
                normal.x = 0.0
                normal.y /= y
                normal.z = 0.0

            if z > y and z > x:
                normal.x = 0.0
                normal.y = 0.0
                normal.z /= z

        for f in fs:
            for v in f.verts:
                d = geometry.distance_point_to_plane (v.co, center, normal)
                v.co -= normal * d

    bmesh.update_edit_mesh(me)
Exemple #42
0
 def key_func(v):
     return abs(geom.distance_point_to_plane(v.co, v1.co, normal))
Exemple #43
0
    def execute(self, context):
        automerge = False
        self.used_modal = False
        sel_check, place, noloc, place, dupe = False, False, False, False, False
        normal, tangent, place_quat = None, None, None
        mode = bpy.context.mode[:]
        sel_mode = bpy.context.tool_settings.mesh_select_mode[:]
        obj = bpy.context.active_object
        if not obj:
            self.report({"INFO"}, "Unrotator: No Active Object?")
            return {"CANCELLED"}

        if self.ke_unrotator_option == "DUPE":
            dupe, noloc = True, False
        elif self.ke_unrotator_option == "NO_LOC":
            noloc, dupe = True, False

        connect = bpy.context.scene.kekit.unrotator_connect
        nolink = bpy.context.scene.kekit.unrotator_nolink
        nosnap = bpy.context.scene.kekit.unrotator_nosnap
        actual_invert = bpy.context.scene.kekit.unrotator_invert
        self.center = bpy.context.scene.kekit.unrotator_center

        if self.center_override:
            self.center = True

        # Check mouse over target -----------------------------------------------------------------------
        bpy.ops.object.mode_set(mode="OBJECT")
        hit_obj, hit_wloc, hit_normal, hit_face = mouse_raycast(context,
                                                                self.mouse_pos,
                                                                evaluated=True)
        # print("HIT:", hit_obj, hit_face, hit_normal)

        if mode == "OBJECT" and not nosnap:
            self.rot_offset = 0
            self.center = False

        if mode == "EDIT_MESH":
            bpy.ops.object.mode_set(mode="EDIT")

            automerge = bool(context.scene.tool_settings.use_mesh_automerge)
            if automerge:
                context.scene.tool_settings.use_mesh_automerge = False

            # Get selected obj bm
            obj_mtx = obj.matrix_world.copy()
            od = obj.data
            bm = bmesh.from_edit_mesh(od)

            # Check selections
            sel_poly = [p for p in bm.faces if p.select]
            sel_edges = [e for e in bm.edges if e.select]
            sel_verts = [v for v in bm.verts if v.select]

            active_h = bm.select_history.active
            active_point = None

            # Making sure an active element is in the selection
            if sel_mode[1] and sel_edges:
                if active_h not in sel_edges:
                    bm.select_history.add(sel_edges[-1])
                    active_h = bm.select_history.active
                active_point = active_h.verts[0]

            elif sel_mode[0] and sel_verts:
                if active_h not in sel_verts:
                    bm.select_history.add(sel_verts[-1])
                    active_h = bm.select_history.active
                active_point = active_h

            elif sel_mode[2] and sel_poly:
                if active_h not in sel_poly:
                    bm.select_history.add(sel_poly[-1])
                    active_h = bm.select_history.active
                active_point = active_h.verts[0]

            # Verify valid selections
            if len(sel_verts) >= 3 and active_h:
                if sel_mode[0]:
                    sel_check = True
                elif sel_mode[1] and len(sel_edges) >= 2:
                    sel_check = True
                elif sel_mode[2] and len(sel_poly):
                    sel_check = True

            if not sel_check:
                self.report({
                    "INFO"
                }, "Unrotator: Selection Error - Incorrect/No geo Selected? Active Element?"
                            )
                return {"CANCELLED"}

            # Expand Linked, or not ------------------------------------------------------------------
            if connect:
                bpy.ops.mesh.select_linked()
                linked_verts = [v for v in bm.verts if v.select]

            elif not connect:
                linked_verts = sel_verts  # fall-back
                # still calc the linked avg pos
                islands = get_islands(bm, sel_verts)
                for island in islands:
                    if any(v in sel_verts for v in island):
                        linked_verts = island
                        break

            # Linked Selection center pos
            bm.verts.ensure_lookup_table()
            linked_verts_co = [v.co for v in linked_verts]
            avg_pos = obj_mtx @ Vector(average_vector(linked_verts_co))
            sel_cos = [v.co for v in sel_verts]
            avg_sel_pos = obj_mtx @ Vector(average_vector(sel_cos))
            center_d = Vector(avg_sel_pos - avg_pos).normalized()

            # bmdupe
            if dupe:
                bmesh.ops.duplicate(bm,
                                    geom=([v for v in bm.verts if v.select] +
                                          [e for e in bm.edges if e.select] +
                                          [p for p in bm.faces if p.select]))

            # -----------------------------------------------------------------------------------------
            # Check mouse over target - place check
            # -----------------------------------------------------------------------------------------
            if type(hit_face) == int:

                if hit_obj.name == obj.name:
                    bm.faces.ensure_lookup_table()
                    hit_face_verts = bm.faces[hit_face].verts[:]

                    if any(i in hit_face_verts for i in linked_verts):
                        # print("Mouse over same Obj and selected geo - Unrotate only mode")
                        place = False
                    else:
                        # print("Mouse over same Obj but unselected geo - Unrotate & Place in same mesh mode")
                        place = True
                        n, t = self.calc_face_vectors(bm.faces[hit_face],
                                                      obj_mtx, obj,
                                                      len(hit_face_verts))
                        n.negate()
                        place_quat = rotation_from_vector(n, t).to_quaternion()

                        if self.center:
                            bm.faces.ensure_lookup_table()
                            hit_wloc = average_vector(
                                [obj_mtx @ v.co for v in hit_face_verts])

                elif hit_obj.name != obj.name:
                    # print("Mouse over different Object - Unrotate & Place mode")
                    place = True
                    hit_face_verts = hit_obj.data.polygons[
                        hit_face].vertices[:]
                    n, t = self.calc_face_vectors(
                        hit_obj.data.polygons[hit_face], hit_obj.matrix_world,
                        hit_obj, len(hit_face_verts))
                    n.negate()
                    place_quat = rotation_from_vector(n, t).to_quaternion()

                    if self.center:
                        hit_vcos = [
                            hit_obj.matrix_world @ hit_obj.data.vertices[i].co
                            for i in hit_face_verts
                        ]
                        hit_wloc = average_vector(hit_vcos)

            # -----------------------------------------------------------------------------------------
            # Selection processing for normal and tangent vecs
            # -----------------------------------------------------------------------------------------
            if sel_mode[0]:
                # VERT MODE ---------------------------------------------------------------------------
                h = tri_points_order(
                    [sel_verts[0].co, sel_verts[1].co, sel_verts[2].co])
                # Vectors from 3 points, hypoT ignored
                p1, p2, p3 = obj_mtx @ sel_verts[h[0]].co, obj_mtx @ sel_verts[
                    h[1]].co, obj_mtx @ sel_verts[h[2]].co
                v2 = p3 - p1
                v1 = p2 - p1
                normal = v1.cross(v2).normalized()
                tangent = Vector(v1).normalized()
                # Direction flip check against center mass
                d = normal.dot(center_d)
                if d <= 0:
                    normal.negate()

            elif sel_mode[1]:
                # EDGE MODE ---------------------------------------------------------------------------
                bm.edges.ensure_lookup_table()
                # Active edge is tangent
                ev = active_h.verts[:]
                ev1, ev2 = ev[0].co, ev[1].co
                tangent = correct_normal(obj_mtx,
                                         Vector((ev1 - ev2)).normalized())
                # B-tangent is shortest vec to v1
                c = [v for v in sel_verts if v not in ev]
                vecs = [Vector((ev1 - v.co).normalized()) for v in c]
                v2 = correct_normal(obj_mtx, sorted(vecs)[0])
                # Normal is crossp
                normal = tangent.cross(v2).normalized()
                # Direction flip check against center mass
                d = normal.dot(center_d)
                if d <= 0:
                    normal.negate()

            elif sel_mode[2]:
                # POLY MODE ---------------------------------------------------------------------------
                normal, tangent = self.calc_face_vectors(
                    active_h, obj_mtx, obj, len(sel_verts))

            # -----------------------------------------------------------------------------------------
            # Process & Execute transforms!
            # -----------------------------------------------------------------------------------------
            if actual_invert:
                normal.negate()
            if self.inv:
                normal.negate()

            rotm = rotation_from_vector(normal, tangent)
            rq = rotm.to_quaternion()

            if self.rot_offset != 0:
                rq @= Quaternion((0, 0, 1), self.rot_offset)

            if place_quat is not None:
                qd = place_quat @ rq.inverted()
            else:
                # rot to place at bottom
                rq @= Quaternion((1, 0, 0), radians(-180.0))
                qd = rq.rotation_difference(Quaternion())

            rot = qd.to_euler("XYZ")
            rx, ry, rz = rot.x * -1, rot.y * -1, rot.z * -1

            # Old macro method to avoid non-uniform scale issues...slow, but works.
            bpy.ops.transform.rotate(value=rx,
                                     orient_axis='X',
                                     orient_type='GLOBAL',
                                     use_proportional_edit=False,
                                     snap=False,
                                     orient_matrix_type='GLOBAL')
            bpy.ops.transform.rotate(value=ry,
                                     orient_axis='Y',
                                     orient_type='GLOBAL',
                                     use_proportional_edit=False,
                                     snap=False,
                                     orient_matrix_type='GLOBAL')
            bpy.ops.transform.rotate(value=rz,
                                     orient_axis='Z',
                                     orient_type='GLOBAL',
                                     use_proportional_edit=False,
                                     snap=False,
                                     orient_matrix_type='GLOBAL')

            # Calc offset
            nv = [v.co for v in linked_verts]
            npos = obj_mtx @ Vector(average_vector(nv))

            if place and not noloc:
                # Move & offset placement
                d = distance_point_to_plane(obj_mtx @ active_point.co, npos,
                                            hit_normal)
                offset = hit_normal * d
                hit_vec = hit_wloc - (npos + offset)

                bmesh.ops.translate(bm,
                                    vec=hit_vec,
                                    space=obj_mtx,
                                    verts=linked_verts)
                bmesh.update_edit_mesh(obj.data)

            else:
                # Compensate z pos to rot-in-place-ish
                comp = (npos - avg_pos) * -1
                bpy.ops.transform.translate(value=comp)
                bmesh.update_edit_mesh(obj.data)

        elif mode == 'OBJECT':
            # Unrotate only
            if not hit_obj:
                obj.select_set(True)
                obj.rotation_euler = (0, 0, self.rot_offset)

            elif hit_obj.name == obj.name:
                obj.select_set(True)
                obj.rotation_euler = (0, 0, self.rot_offset)

            # Place!
            elif hit_obj.name != obj.name:
                obj.select_set(True)
                self.og_pos = Vector(obj.location)

                if dupe:
                    if nolink:
                        bpy.ops.object.duplicate(linked=False)
                    else:
                        bpy.ops.object.duplicate(linked=True)
                    obj = bpy.context.active_object
                    obj.select_set(True)

                mtx = hit_obj.matrix_world
                eks = hit_obj.data.polygons[hit_face].edge_keys
                vecs = []

                for vp in eks:
                    vc1 = mtx @ hit_obj.data.vertices[vp[0]].co
                    vc2 = mtx @ hit_obj.data.vertices[vp[1]].co
                    vecs.append(vc1 - vc2)

                short_sort = sorted(vecs)
                start_vec = short_sort[0]

                self.setrot = rotation_from_vector(hit_normal,
                                                   start_vec,
                                                   rotate90=True,
                                                   rw=True).to_quaternion()
                self.setrot @= Quaternion((0, 0, 1), self.rot_offset)

                if noloc or nosnap:
                    obj.rotation_euler = self.setrot.to_euler()

                if not noloc:
                    if self.center and nosnap:
                        hit_wloc = hit_obj.matrix_world @ Vector(
                            hit_obj.data.polygons[hit_face].center)
                        obj.location = hit_wloc

                    # no snapping place only
                    obj.location = hit_wloc

                    if not nosnap:
                        obj.rotation_euler = self.setrot.to_euler()
                        self.f_center = hit_obj.matrix_world @ Vector(
                            hit_obj.data.polygons[hit_face].center)
                        self.used_modal = True
                        self.og_snaps = self.get_snap_settings(context)
                        self.set_snap_settings(context, self.modal_snap)
                        context.window_manager.modal_handler_add(self)
                        bpy.ops.transform.translate('INVOKE_DEFAULT')
                        return {'RUNNING_MODAL'}

        # Selection revert for non-face selections
        if not sel_mode[2] and mode != "OBJECT":
            bpy.ops.mesh.select_all(action='DESELECT')

            if sel_mode[1]:
                bm.edges.ensure_lookup_table()
                for v in sel_edges:
                    bm.edges[v.index].select = True
            elif sel_mode[0]:
                bm.verts.ensure_lookup_table()
                for v in sel_verts:
                    bm.verts[v.index].select = True

            bm.select_history.clear()
            bm.select_history.add(active_h)
            bmesh.update_edit_mesh(obj.data)

        if automerge:
            context.scene.tool_settings.use_mesh_automerge = True

        return {"FINISHED"}
Exemple #44
0
    def execute(self, context):
        bpy.ops.object.editmode_toggle()

        bm = bmesh.new()
        bm.from_mesh(context.active_object.data)

        bFaces = bm.faces
        bEdges = bm.edges
        bVerts = bm.verts

        fVerts = []

        # Find the selected face.  This will provide the plane to project onto:
        for f in bFaces:
            if f.select:
                for v in f.verts:
                    fVerts.append(v)
                f.normal_update()
                normal = f.normal
                f.select = False
                break

        for e in bEdges:
            if e.select:
                v1 = e.verts[0]
                v2 = e.verts[1]
                if v1 in fVerts or v2 in fVerts:
                    e.select = False
                    continue
                intersection = intersect_line_plane(v1.co, v2.co, fVerts[0].co, normal)
                if intersection != None:
                    print("Made it this far: intersection != None")
                    # Use abs because we don't care what side of plane we're on:
                    d1 = distance_point_to_plane(v1.co, fVerts[0].co, normal)
                    d2 = distance_point_to_plane(v2.co, fVerts[0].co, normal)
                    # If d1 is closer than we use v1 as our vertice:
                    # "xor" with 'use_force':
                    if (abs(d1) < abs(d2)) is not self.use_force:
                        print("Made it this far: (abs(d1) < abs(d2)) xor force")
                        if self.make_copy:
                            v1 = bVerts.new()
                            v1.co = e.verts[0].co
                        if self.use_normal:
                            vector = normal
                            vector.length = abs(d1)
                            v1.co = v1.co - (vector * sign(d1))
                        else:
                            print("New coordinate", end = ": ")
                            print(intersection)
                            v1.co = intersection
                    else:
                        if self.make_copy:
                            v2 = bVerts.new()
                            v2.co = e.verts[1].co
                        if self.use_normal:
                            vector = normal
                            vector.length = abs(d2)
                            v2.co = v2.co - (vector * sign(d2))
                        else:
                            v2.co = intersection
                e.select = False

        bm.to_mesh(context.active_object.data)
        bpy.ops.object.editmode_toggle()
        return {'FINISHED'}
Exemple #45
0
def OBB(vecs, r_indices=None, eps=1e-6):
    """Convex hull を用いたOBBを返す。
    Z->Y->Xの順で長さが最少となる軸を求める。
    :param vecs: list of Vector
    :type vecs: list | tuple
    :param r_indices: listを渡すとconvexhullの結果を格納する
    :type r_indices: None | list
    :param eps: 種々の計算の閾値
    :return:
        (matrix, obb_size)
        matrix:
            type: Matrx
            OBBの回転と中心を表す。vecsが二次元ベクトルの場合は3x3, 三次元なら4x4。
        obb_size:
            type: Vector
            OBBの各軸の長さ。vecsと同じ次元。
    :rtype: (Matrix, Vector)
    """

    if not vecs:
        return None, None

    # 2D ----------------------------------------------------------------------
    if len(vecs[0]) == 2:
        mat = Matrix.Identity(3)
        bb_size = Vector((0, 0))

        indices = convex_hull_2d(vecs, eps)
        if r_indices:
            r_indices[:] = indices

        if len(indices) == 1:
            mat.col[2][:2] = vecs[0]
        elif len(indices) == 2:
            v1 = vecs[indices[0]]
            v2 = vecs[indices[1]]
            xaxis = (v2 - v1).normalized()
            angle = math.atan2(xaxis[1], xaxis[0])
            mat2 = Matrix.Rotation(angle, 2)
            mat.col[0][:2] = mat2.col[0]
            mat.col[1][:2] = mat2.col[1]
            mat.col[2][:2] = (v1 + v2) / 2
            bb_size[0] = (v2 - v1).length
        else:
            yaxis = _closest_axis_on_plane(vecs, indices)
            angle = math.atan2(yaxis[1], yaxis[0]) - math.pi / 2  # X軸
            mat2 = Matrix.Rotation(angle, 2)
            imat2 = Matrix.Rotation(-angle, 2)
            rotvecs = [imat2 * v for v in vecs]
            loc = Vector((0, 0))
            for i in range(2):
                rotvecs.sort(key=lambda v: v[i])
                bb_size[i] = rotvecs[-1][i] - rotvecs[0][i]
                loc[i] = (rotvecs[0][i] + rotvecs[-1][i]) / 2
            mat.col[0][:2] = mat2.col[0]
            mat.col[1][:2] = mat2.col[1]
            mat.col[2][:2] = mat2 * loc
        return mat, bb_size

    # 3D ----------------------------------------------------------------------
    mat = Matrix.Identity(4)
    bb_size = Vector((0, 0, 0))

    indices = convex_hull(vecs, eps)

    if r_indices:
        r_indices[:] = indices

    if isinstance(indices[0], int):  # 2d
        if len(indices) == 1:
            mat.col[3][:3] = vecs[0]
            return mat, bb_size
        
        elif len(indices) == 2:
            # 同一線上
            v1 = vecs[indices[0]]
            v2 = vecs[indices[1]]
            xaxis = (v2 - v1).normalized()
            quat = Vector((1, 0, 0)).rotation_difference(xaxis)
            mat = quat.to_matrix().to_4x4()
            mat.col[3][:3] = (v1 + v2) / 2
            bb_size[0] = (v2 - v1).length
            return mat, bb_size

        else:
            # 同一平面上
            medium = reduce(lambda a, b: a + b, vecs) / len(vecs)
            v1 = max(vecs, key=lambda v: (v - medium).length)
            v2 = max(vecs, key=lambda v: (v - v1).length)
            line = v2 - v1
            v3 = max(vecs, key=lambda v: line.cross(v - v1).length)
            zaxis = geom.normal(v1, v2, v3)
            if zaxis[2] < 0.0:
                zaxis.negate()

            quat = zaxis.rotation_difference(Vector((0, 0, 1)))
            rotvecs = [quat * v for v in vecs]
            indices_2d = indices

    else:  # 3d
        indices_set = set(chain(*indices))
        zaxis = None
        dist = 0.0
        # 最も距離の近い面(平面)と頂点を求める
        for tri in indices:
            v1, v2, v3 = [vecs[i] for i in tri]
            normal = geom.normal(v1, v2, v3)
            d = 0.0
            for v4 in (vecs[i] for i in indices_set if i not in tri):
                f = abs(geom.distance_point_to_plane(v4, v1, normal))
                d = max(f, d)
            if zaxis is None or d < dist:
                zaxis = -normal
                dist = d

        quat = zaxis.rotation_difference(Vector((0, 0, 1)))
        rotvecs = [(quat * v).to_2d() for v in vecs]
        indices_2d = convex_hull_2d(rotvecs, eps)

    yaxis = _closest_axis_on_plane(rotvecs, indices_2d)
    yaxis = quat.inverted() * yaxis.to_3d()

    xaxis = yaxis.cross(zaxis)
    xaxis.normalize()  # 不要?

    mat.col[0][:3] = xaxis
    mat.col[1][:3] = yaxis
    mat.col[2][:3] = zaxis

    # OBBの大きさと中心を求める
    imat = mat.inverted()
    rotvecs = [imat * v for v in vecs]
    loc = Vector()
    for i in range(3):
        rotvecs.sort(key=lambda v: v[i])
        bb_size[i] = rotvecs[-1][i] - rotvecs[0][i]
        loc[i] = (rotvecs[0][i] + rotvecs[-1][i]) / 2
    mat.col[3][:3] = mat * loc
    return mat, bb_size