Exemple #1
0
    def find_face_tile(self, context, event):
        if self.tree is None or context.scene.sprytile_ui.use_mouse is True:
            return

        # get the context arguments
        region = context.region
        rv3d = context.region_data
        coord = event.mouse_region_x, event.mouse_region_y

        # get the ray from the viewport and mouse
        ray_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
        ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)

        location, normal, face_index, distance = self.raycast_object(context.object, ray_origin, ray_vector)
        if location is None:
            return

        face = self.bmesh.faces[face_index]

        grid_id, tile_packed_id, width, height, origin_id = self.get_face_tiledata(face)
        if None in {grid_id, tile_packed_id}:
            return

        tilegrid = sprytile_utils.get_grid(context, grid_id)
        if tilegrid is None:
            return

        texture = sprytile_utils.get_grid_texture(context.object, tilegrid)
        if texture is None:
            return

        paint_setting_layer = self.bmesh.faces.layers.int.get('paint_settings')
        if paint_setting_layer is not None:
            paint_setting = face[paint_setting_layer]
            sprytile_utils.from_paint_settings(context.scene.sprytile_data, paint_setting)

        row_size = math.ceil(texture.size[0] / tilegrid.grid[0])
        tile_y = math.floor(tile_packed_id / row_size)
        tile_x = tile_packed_id % row_size
        if event.ctrl:
            width = 1
            height = 1
        elif origin_id > -1:
            origin_y = math.floor(origin_id / row_size)
            origin_x = origin_id % row_size
            tile_x = min(origin_x, tile_x)
            tile_y = min(origin_y, tile_y)

        if width == 0:
            width = 1
        if height == 0:
            height = 1

        context.object.sprytile_gridid = grid_id
        tilegrid.tile_selection[0] = tile_x
        tilegrid.tile_selection[1] = tile_y
        tilegrid.tile_selection[2] = width
        tilegrid.tile_selection[3] = height

        bpy.ops.sprytile.build_grid_list()
Exemple #2
0
 def cursor_move_layer(context, direction):
     scene = context.scene
     target_grid = sprytile_utils.get_grid(context, context.object.sprytile_gridid)
     grid_x = target_grid.grid[0]
     grid_y = target_grid.grid[1]
     layer_move = min(grid_x, grid_y)
     layer_move = int(layer_move/2)
     layer_move *= (1 / context.scene.sprytile_data.world_pixels)
     plane_normal = scene.sprytile_data.paint_normal_vector.copy()
     plane_normal *= layer_move * direction
     grid_position = scene.cursor_location + plane_normal
     scene.cursor_location = grid_position
Exemple #3
0
def uv_map_face(context, up_vector, right_vector, tile_xy, origin_xy,
                face_index, mesh):
    """UV map the given face"""
    if mesh is None:
        return None, None

    scene = context.scene
    obj = context.object
    data = scene.sprytile_data

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

    uv_layer = mesh.loops.layers.uv.verify()
    mesh.faces.layers.tex.verify()

    if face_index >= len(mesh.faces):
        return None, None

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

    face = mesh.faces[face_index]
    if face.hide:
        return None, None

    vert_origin = context.object.matrix_world * face.calc_center_bounds()
    verts = []
    for loop in face.loops:
        vert = loop.vert
        verts.append(context.object.matrix_world * vert.co)

    uv_verts = get_uv_positions(data, target_img.size, target_grid, up_vector,
                                right_vector, tile_xy, verts, vert_origin)

    if uv_verts is None:
        return None, None

    apply_uvs(context,
              face,
              uv_verts,
              target_grid,
              mesh,
              data,
              target_img,
              tile_xy,
              origin_xy=origin_xy,
              uv_layer=uv_layer)

    return face.index, target_grid
Exemple #4
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)

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

        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(
            scene, False)

        # 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:
            return

        preview_verts = self.modal.get_build_vertices(face_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 preview_verts:
            vtx_center += vtx
        vtx_center /= len(preview_verts)

        rotate_normal = plane_normal

        rotation = Quaternion(rotate_normal, data.mesh_rotate)
        up_vector = rotation * up_vector
        right_vector = rotation * right_vector

        up_vector.normalize()
        right_vector.normalize()

        tile_xy = (target_grid.tile_selection[0],
                   target_grid.tile_selection[1])
        preview_uvs = sprytile_uv.get_uv_positions(data, target_img.size,
                                                   target_grid, up_vector,
                                                   right_vector, tile_xy,
                                                   preview_verts, vtx_center)

        self.modal.set_preview_data(preview_verts, preview_uvs)
Exemple #5
0
    def call_tool(self, event, left_down, context):
        # Push the event data out through rx_observer for tool observers
        sprytile_data = bpy.context.scene.sprytile_data

        # If the selected object does not own the painting material, add a slot for it here
        if left_down:
            grid = sprytile_utils.get_grid(context,
                                           context.object.sprytile_gridid)
            if grid is not None:
                grid_mat = sprytile_utils.get_grid_material(grid)
                if not sprytile_utils.has_material(context.object, grid_mat):
                    bpy.ops.object.material_slot_add()
                    context.object.active_material = grid_mat

        if self.rx_observer is not None:
            self.rx_observer.on_next(
                DataObjectDict(
                    paint_mode=sprytile_data.paint_mode,
                    event=event,
                    left_down=left_down,
                    build_preview=self.draw_preview,
                ))
Exemple #6
0
    def execute(self, context, scene, ray_origin, ray_vector):
        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(
            context.scene)
        hit_loc, normal, face_index, distance = self.modal.raycast_object(
            context.object, ray_origin, ray_vector, world_normal=True)
        if face_index is None:
            return

        normal.normalize()

        self.modal.add_virtual_cursor(hit_loc)
        # Change the uv of the given face
        grid_id = context.object.sprytile_gridid
        grid = sprytile_utils.get_grid(context, grid_id)
        tile_xy = (grid.tile_selection[0], grid.tile_selection[1])

        face_up, face_right = self.modal.get_face_up_vector(
            context, face_index)
        data = context.scene.sprytile_data

        rotate_normal = plane_normal
        if face_up is not None and face_right is not None:
            rotate_normal = face_up.cross(face_right)

        rotate_matrix = Quaternion(-rotate_normal, data.mesh_rotate)
        if face_up is not None:
            up_vector = rotate_matrix * face_up
        if face_right is not None:
            right_vector = rotate_matrix * face_right

        up_vector.normalize()
        right_vector.normalize()
        # print("Up vector:", up_vector, "Right vector:", right_vector)
        # print("Up mag:", up_vector.magnitude, "Right mag:", right_vector.magnitude)
        sprytile_uv.uv_map_face(context, up_vector, right_vector, tile_xy,
                                face_index, self.modal.bmesh)
Exemple #7
0
    def execute_fill(self, context, scene, ray_origin, ray_vector):
        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(scene, with_rotation=False)

        # Intersect on the virtual plane
        plane_hit = intersect_line_plane(ray_origin, ray_origin + ray_vector, scene.cursor.location, plane_normal)
        # Didn't hit the plane exit
        if plane_hit is None:
            return
        grid = sprytile_utils.get_grid(context, context.object.sprytile_gridid)
        sprytile_data = scene.sprytile_data

        world_pixels = sprytile_data.world_pixels
        grid_x = grid.grid[0]
        grid_y = grid.grid[1]

        # Find the position of the plane hit, in terms of grid coordinates
        hit_coord, grid_right, grid_up = sprytile_utils.get_grid_pos(
            plane_hit, scene.cursor.location,
            right_vector.copy(), up_vector.copy(),
            world_pixels, grid_x, grid_y, as_coord=True
        )

        # Check hit_coord is inside the work plane grid
        plane_size = sprytile_data.fill_plane_size

        grid_min, grid_max = sprytile_utils.get_workplane_area(plane_size[0], plane_size[1])

        x_offset = 1
        if plane_size[0] % 2 == 1:
            grid_min[0] += x_offset
            grid_max[0] += x_offset

        if hit_coord.x < grid_min[0] or hit_coord.x >= grid_max[0]:
            return
        if hit_coord.y < grid_min[1] or hit_coord.y >= grid_max[1]:
            return

        # Build the fill map
        sel_coords, sel_size, sel_ids = sprytile_utils.get_grid_selection_ids(context, grid)
        fill_map, face_idx_array = self.build_fill_map(context, grid_up, grid_right, plane_normal,
                                                       plane_size, grid_min, grid_max, sel_ids)

        # Convert from grid coordinate to map coordinate
        hit_array_coord = [int(hit_coord.x) - grid_min[0],
                           int(hit_coord.y) - grid_min[1]]

        # For getting paint settings later
        paint_setting_layer = self.modal.bmesh.faces.layers.int.get(UvDataLayers.PAINT_SETTINGS)

        # Get vectors again, to apply tile rotations in UV stage
        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(scene)

        # Get the content in hit coordinate
        hit_coord_content = int(fill_map[hit_array_coord[1]][hit_array_coord[0]])
        # Get the coordinates that would be flood filled
        fill_coords = self.flood_fill(fill_map, hit_array_coord, -2, hit_coord_content)

        # If lock transform on, cache the paint settings before doing any operations
        paint_setting_cache = None
        if sprytile_data.fill_lock_transform and paint_setting_layer is not None:
            paint_setting_cache = [len(fill_coords)]
            for idx, cell_coord in fill_coords:
                face_index = face_idx_array[cell_coord[1]][cell_coord[0]]
                if face_index > -1:
                    face = self.modal.faces[face_index]
                    paint_setting_cache[idx] = face[paint_setting_layer]

        # Get the work layer filter, based on layer settings
        work_layer_mask = sprytile_utils.get_work_layer_data(sprytile_data)
        require_base_layer = sprytile_data.work_layer != 'BASE'

        origin_xy = (grid.tile_selection[0], grid.tile_selection[1])
        data = scene.sprytile_data
        # Loop through list of coords to be filled
        for idx, cell_coord in enumerate(fill_coords):
            # Fetch the paint settings from cache
            if paint_setting_cache is not None:
                paint_setting = paint_setting_cache[idx]
                sprytile_utils.from_paint_settings(data, paint_setting)

            # Convert map coord to grid coord
            grid_coord = [grid_min[0] + cell_coord[0],
                          grid_min[1] + cell_coord[1]]

            sub_x = (grid_coord[0] - int(hit_coord.x)) % sel_size[0]
            sub_y = (grid_coord[1] - int(hit_coord.y)) % sel_size[1]
            sub_xy = sel_coords[(sub_y * sel_size[0]) + sub_x]
            self.modal.construct_face(context, grid_coord, [1,1],
                                      sub_xy, origin_xy,
                                      grid_up, grid_right,
                                      up_vector, right_vector,
                                      plane_normal,
                                      require_base_layer=require_base_layer,
                                      work_layer_mask=work_layer_mask)
    def cursor_snap(self, context, event):
        if self.tree is None or context.scene.sprytile_ui.use_mouse is True:
            return

        # get the context arguments
        scene = context.scene
        region = context.region
        rv3d = context.region_data
        coord = event.mouse_region_x, event.mouse_region_y

        # get the ray from the viewport and mouse
        ray_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
        ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)

        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(
            scene)

        if event.type in self.is_keyboard_list and event.shift and event.value == 'PRESS':
            if scene.sprytile_data.cursor_snap == 'GRID':
                scene.sprytile_data.cursor_snap = 'VERTEX'
            else:
                scene.sprytile_data.cursor_snap = 'GRID'

        # Snap cursor, depending on setting
        if scene.sprytile_data.cursor_snap == 'GRID':
            location = intersect_line_plane(ray_origin,
                                            ray_origin + ray_vector,
                                            scene.cursor_location,
                                            plane_normal)
            if location is None:
                return
            world_pixels = scene.sprytile_data.world_pixels
            target_grid = sprytile_utils.get_grid(
                context, context.object.sprytile_gridid)
            grid_x = target_grid.grid[0]
            grid_y = target_grid.grid[1]

            grid_position, x_vector, y_vector = sprytile_utils.get_grid_pos(
                location, scene.cursor_location, right_vector.copy(),
                up_vector.copy(), world_pixels, grid_x, grid_y)
            scene.cursor_location = grid_position

        elif scene.sprytile_data.cursor_snap == 'VERTEX':
            # Get if user is holding down tile picker modifier
            check_modifier = False
            addon_prefs = context.user_preferences.addons[
                __package__].preferences
            if addon_prefs.tile_picker_key == 'Alt':
                check_modifier = event.alt
            if addon_prefs.tile_picker_key == 'Ctrl':
                check_modifier = event.ctrl
            if addon_prefs.tile_picker_key == 'Shift':
                check_modifier = event.shift

            location, normal, face_index, distance = self.raycast_object(
                context.object, ray_origin, ray_vector)
            if location is None:
                if check_modifier:
                    scene.sprytile_data.lock_normal = False
                return
            # Location in world space, convert to object space
            matrix = context.object.matrix_world.copy()
            matrix_inv = matrix.inverted()
            location, normal, face_index, dist = self.tree.find_nearest(
                matrix_inv * location)
            if location is None:
                return

            # Found the nearest face, go to BMesh to find the nearest vertex
            if self.bmesh is None:
                self.refresh_mesh = True
                return
            if face_index >= len(self.bmesh.faces) or face_index < 0:
                return
            face = self.bmesh.faces[face_index]
            closest_vtx = -1
            closest_dist = float('inf')
            # positions are in object space
            for vtx_idx, vertex in enumerate(face.verts):
                test_dist = (location - vertex.co).magnitude
                if test_dist < closest_dist:
                    closest_vtx = vtx_idx
                    closest_dist = test_dist
            # convert back to world space
            if closest_vtx != -1:
                scene.cursor_location = matrix * face.verts[closest_vtx].co

            # If find face tile button pressed, set work plane normal too
            if check_modifier:
                sprytile_data = context.scene.sprytile_data
                # Check if mouse is hitting object
                target_normal = context.object.matrix_world.to_quaternion(
                ) * normal
                face_up_vector, face_right_vector = self.get_face_up_vector(
                    context, face_index, 0.4)
                if face_up_vector is not None:
                    sprytile_data.paint_normal_vector = target_normal
                    sprytile_data.paint_up_vector = face_up_vector
                    sprytile_data.lock_normal = True
Exemple #9
0
def uv_map_face(context, up_vector, right_vector, tile_xy, face_index, mesh):
    """UV map the given face"""
    if mesh is None:
        return None, None

    scene = context.scene
    obj = context.object
    data = scene.sprytile_data

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

    uv_layer = mesh.loops.layers.uv.verify()
    mesh.faces.layers.tex.verify()

    if face_index >= len(mesh.faces):
        return None, None

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

    face = mesh.faces[face_index]
    if face.hide:
        return None, None

    vert_origin = context.object.matrix_world * face.calc_center_bounds()
    verts = []
    for loop in face.loops:
        vert = loop.vert
        verts.append(context.object.matrix_world * vert.co)

    uv_verts = get_uv_positions(data, target_img.size, target_grid, up_vector,
                                right_vector, tile_xy, verts, vert_origin)

    if uv_verts is None:
        return None, None

    # Apply the UV positions on the face verts
    idx = 0
    for loop in face.loops:
        loop[uv_layer].uv = uv_verts[idx].xy
        idx += 1

    # Apply the correct material to the face
    mat_idx = context.object.material_slots.find(target_grid.mat_id)
    if mat_idx > -1:
        face.material_index = mat_idx

    # Save the grid and tile ID to the face
    grid_layer_id = mesh.faces.layers.int.get('grid_index')
    grid_layer_tileid = mesh.faces.layers.int.get('grid_tile_id')
    paint_settings_id = mesh.faces.layers.int.get('paint_settings')

    if grid_layer_id is None:
        grid_layer_id = mesh.faces.layers.int.new('grid_index')
    if grid_layer_tileid is None:
        grid_layer_tileid = mesh.faces.layers.int.new('grid_tile_id')
    if paint_settings_id is None:
        paint_settings_id = mesh.faces.layers.int.new('paint_settings')

    face = mesh.faces[face_index]
    row_size = math.ceil(target_img.size[0] / target_grid.grid[0])
    tile_id = (tile_xy[1] * row_size) + tile_xy[0]

    paint_settings = sprytile_utils.get_paint_settings(data)

    face[grid_layer_id] = grid_id
    face[grid_layer_tileid] = tile_id
    face[paint_settings_id] = paint_settings

    bmesh.update_edit_mesh(obj.data)
    mesh.faces.index_update()
    return face.index, target_grid
Exemple #10
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)

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

        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(
            scene, False)

        # Raycast the object
        hit_loc, hit_normal, face_index, hit_dist = self.modal.raycast_object(
            obj, ray_origin, ray_vector)
        # Didn't hit a face, do nothing
        if face_index is None:
            self.modal.set_preview_data(None, None)
            return

        preview_verts = []

        face = self.modal.bmesh.faces[face_index]
        for loop in face.loops:
            vert = loop.vert
            preview_verts.append(context.object.matrix_world * vert.co)

        # Get the center of the preview verts
        vtx_center = Vector((0, 0, 0))
        for vtx in preview_verts:
            vtx_center += vtx
        vtx_center /= len(preview_verts)

        rotate_normal = plane_normal

        # Recalculate the rotation normal
        face_up, face_right = self.modal.get_face_up_vector(
            context, face_index)

        if face_up is not None and face_right is not None:
            rotate_normal = face_right.cross(face_up)

        if face_up is not None:
            up_vector = face_up
        if face_right is not None:
            right_vector = face_right

        rotation = Quaternion(rotate_normal, data.mesh_rotate)
        up_vector = rotation * up_vector
        right_vector = rotation * right_vector

        up_vector.normalize()
        right_vector.normalize()

        tile_xy = (target_grid.tile_selection[0],
                   target_grid.tile_selection[1])
        preview_uvs = sprytile_uv.get_uv_positions(data, target_img.size,
                                                   target_grid, up_vector,
                                                   right_vector, tile_xy,
                                                   preview_verts, vtx_center)

        self.modal.set_preview_data(preview_verts, preview_uvs)
Exemple #11
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 #12
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)

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

        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(scene, False)

        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:
            return

        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 #13
0
    def cursor_snap(self, context, event):
        if self.tree is None or context.scene.sprytile_ui.use_mouse is True:
            return

        # get the context arguments
        scene = context.scene
        region = context.region
        rv3d = context.region_data
        coord = event.mouse_region_x, event.mouse_region_y

        # get the ray from the viewport and mouse
        ray_vector = view3d_utils.region_2d_to_vector_3d(region, rv3d, coord)
        ray_origin = view3d_utils.region_2d_to_origin_3d(region, rv3d, coord)

        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(
            scene)

        if event.type in self.is_keyboard_list and event.shift and event.value == 'PRESS':
            if scene.sprytile_data.cursor_snap == 'GRID':
                scene.sprytile_data.cursor_snap = 'VERTEX'
            else:
                scene.sprytile_data.cursor_snap = 'GRID'

        # Snap cursor, depending on setting
        if scene.sprytile_data.cursor_snap == 'GRID':
            location = intersect_line_plane(ray_origin,
                                            ray_origin + ray_vector,
                                            scene.cursor_location,
                                            plane_normal)
            if location is None:
                return
            world_pixels = scene.sprytile_data.world_pixels
            target_grid = sprytile_utils.get_grid(
                context, context.object.sprytile_gridid)
            grid_x = target_grid.grid[0]
            grid_y = target_grid.grid[1]

            grid_position, x_vector, y_vector = sprytile_utils.get_grid_pos(
                location, scene.cursor_location, right_vector.copy(),
                up_vector.copy(), world_pixels, grid_x, grid_y)
            scene.cursor_location = grid_position

        elif scene.sprytile_data.cursor_snap == 'VERTEX':
            location, normal, face_index, distance = self.raycast_object(
                context.object, ray_origin, ray_vector)
            if location is None:
                return
            # Location in world space, convert to object space
            matrix = context.object.matrix_world.copy()
            matrix_inv = matrix.inverted()
            location, normal, face_index, dist = self.tree.find_nearest(
                matrix_inv * location)
            if location is None:
                return

            # Found the nearest face, go to BMesh to find the nearest vertex
            if self.bmesh is None:
                self.refresh_mesh = True
                return
            if face_index >= len(self.bmesh.faces) or face_index < 0:
                return
            face = self.bmesh.faces[face_index]
            closest_vtx = -1
            closest_dist = float('inf')
            # positions are in object space
            for vtx_idx, vertex in enumerate(face.verts):
                test_dist = (location - vertex.co).magnitude
                if test_dist < closest_dist:
                    closest_vtx = vtx_idx
                    closest_dist = test_dist
            # convert back to world space
            if closest_vtx != -1:
                scene.cursor_location = matrix * face.verts[closest_vtx].co
Exemple #14
0
    def process_preview(context, scene, face_index):
        obj = context.object
        data = scene.sprytile_data

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

        if target_grid is None:
            return None, None, None, None, None, None, None

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

        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(scene, False)

        face_verts = []
        mesh = bmesh.from_edit_mesh(context.object.data)

        face = mesh.faces[face_index]
        for loop in face.loops:
            vert = loop.vert
            face_verts.append(context.object.matrix_world @ vert.co)

        # Get the center of the preview verts
        vtx_min = Vector((float('inf'), float('inf'), float('inf')))
        vtx_max = Vector((float('-inf'), float('-inf'), float('-inf')))
        for vtx in face_verts:
            vtx_min.x = min(vtx.x, vtx_min.x)
            vtx_min.y = min(vtx.y, vtx_min.y)
            vtx_min.z = min(vtx.z, vtx_min.z)
            vtx_max.x = max(vtx.x, vtx_max.x)
            vtx_max.y = max(vtx.y, vtx_max.y)
            vtx_max.z = max(vtx.z, vtx_max.z)
        vtx_center = (vtx_min + vtx_max) / 2

        rotate_normal = plane_normal

        # Recalculate the rotation normal
        face_up, face_right = sprytile_modal.VIEW3D_OP_SprytileModalTool.get_face_up_vector(context.object, context, face_index)

        if face_up is not None and face_right is not None:
            rotate_normal = face_right.cross(face_up)

        if face_up is not None:
            up_vector = face_up
        if face_right is not None:
            right_vector = face_right

        rotation = Quaternion(rotate_normal, data.mesh_rotate)
        up_vector = rotation @ up_vector
        right_vector = rotation @ right_vector

        up_vector.normalize()
        right_vector.normalize()

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

        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)

        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]

        uvs = sprytile_uv.get_uv_pos_size(data, target_img.size, target_grid,
                                          tile_xy, size_x, size_y,
                                          up_vector, right_vector,
                                          face_verts, vtx_center)
        return face, face_verts, uvs, target_grid, data, target_img, tile_xy
Exemple #15
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 #16
0
    def invoke(self, context, event):
        # Set active paint mode
        context.scene.sprytile_data.paint_mode = self.paint_mode

        if context.space_data.type != 'VIEW_3D':
            self.report({'WARNING'},
                        "Active space must be a View3d: {0}".format(
                            context.space_data.type))
            return {'CANCELLED'}

        obj = context.object
        if not obj.visible_get() or obj.type != 'MESH':
            self.report({'WARNING'}, "Active object must be a visible mesh")
            return {'CANCELLED'}
        if len(context.scene.sprytile_mats) < 1:
            bpy.ops.sprytile.validate_grids()
        if len(context.scene.sprytile_mats) < 1:
            self.report({'WARNING'}, "No valid materials")
            return {'CANCELLED'}

        use_default_grid_id = obj.sprytile_gridid == -1
        if sprytile_utils.get_grid(context, obj.sprytile_gridid) is None:
            use_default_grid_id = True

        if use_default_grid_id:
            obj.sprytile_gridid = context.scene.sprytile_mats[0].grids[0].id

        cur_space = context.area.spaces.active
        if cur_space.shading.type != 'MATERIAL':
            cur_space.shading.type = 'MATERIAL'

        self.virtual_cursor = deque([], 3)
        VIEW3D_OP_SprytileModalTool.no_undo = False
        self.update_bmesh_tree(context)
        self.refresh_mesh = False

        # Setup Rx Observer and Observables
        self.rx_observer = None
        observable_source = Observable.create(self.setup_rx_observer)
        # Setup multi casting Observable
        self.rx_source = observable_source.publish().auto_connect(1)

        # Tools receive events from the Observable
        self.tools = {
            "build": ToolBuild(self, self.rx_source),
            "paint": ToolPaint(self, self.rx_source),
            "fill": ToolFill(self, self.rx_source),
            "set_normal": ToolSetNormal(self, self.rx_source)
        }

        win_mgr = context.window_manager
        self.view_axis_timer = win_mgr.event_timer_add(0.1,
                                                       window=context.window)

        self.setup_user_keys(context)
        win_mgr.modal_handler_add(self)

        sprytile_data = context.scene.sprytile_data
        sprytile_data.is_snapping = False

        context.scene.sprytile_ui.is_dirty = True
        #bpy.ops.sprytile.gui_win('INVOKE_REGION_WIN') #TODO: Renable once ui works

        #Update view axis
        view_axis = self.find_view_axis(context)
        if view_axis is not None:
            if view_axis != sprytile_data.normal_mode:
                sprytile_data.normal_mode = view_axis
                sprytile_data.lock_normal = False

        self.modal(context, event)

        return {'RUNNING_MODAL'}
Exemple #17
0
def uv_map_face(context,
                up_vector,
                right_vector,
                tile_xy,
                origin_xy,
                face_index,
                mesh,
                tile_size=(1, 1)):
    """
    UV map the given face
    :param context:
    :param up_vector: World up vector
    :param right_vector: World right vector
    :param tile_xy: Tile placement XY coordinates
    :param origin_xy: Origin XY of tile placement
    :param face_index: Face index to UV map
    :param mesh:
    :param tile_size: Tile units being UV mapped
    :return:
    """
    if mesh is None:
        return None, None

    scene = context.scene
    obj = context.object
    data = scene.sprytile_data

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

    uv_layer = mesh.loops.layers.uv.verify()

    if face_index >= len(mesh.faces):
        return None, None

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

    face = mesh.faces[face_index]
    if face.hide_viewport:
        return None, None

    vert_origin = context.object.matrix_world @ face.calc_center_bounds()
    verts = []
    for loop in face.loops:
        vert = loop.vert
        verts.append(context.object.matrix_world @ vert.co)

    tile_start = [tile_xy[0], tile_xy[1]]
    if tile_size[0] > 1 or tile_size[1] > 1:
        tile_start[0] -= tile_size[0]
        tile_start[1] -= tile_size[1]

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

    uv_verts = get_uv_pos_size(data, target_img.size, target_grid, tile_start,
                               size_x, size_y, up_vector, right_vector, verts,
                               vert_origin)

    if uv_verts is None:
        return None, None

    apply_uvs(context,
              face,
              uv_verts,
              target_grid,
              mesh,
              data,
              target_img,
              tile_xy,
              origin_xy=origin_xy,
              uv_layer=uv_layer)

    return face.index, target_grid
Exemple #18
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 #19
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)
        # If building on decal layer, modify plane normal to the one 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)
            if hit_normal is not None:
                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

        # Rotate the vectors
        rotation = Quaternion(plane_normal, data.mesh_rotate)
        up_vector = rotation * up_vector
        right_vector = rotation * right_vector

        # 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 grid position of stroke
        if is_start:
            self.start_coord = grid_coord
        # Not starting stroke, filter out when can build
        elif self.start_coord is not None:
            start_offset = (grid_coord[0] - self.start_coord[0],
                            grid_coord[1] - self.start_coord[1])
            coord_mod = (start_offset[0] % grid.tile_selection[2],
                         start_offset[1] % grid.tile_selection[3])
            # Isn't at exact position for grid made by tile selection, with start_coord as origin
            if coord_mod[0] > 0 or coord_mod[1] > 0:
                # Try to snap grid_coord
                tolerance_min = (floor(grid.tile_selection[2] * 0.25),
                                 floor(grid.tile_selection[3] * 0.25))
                tolerance_max = (grid.tile_selection[2] - tolerance_min[0],
                                 grid.tile_selection[3] - tolerance_min[1])
                allow_snap_x = tolerance_min[0] <= coord_mod[
                    0] <= tolerance_max[0]
                allow_snap_y = tolerance_min[1] <= coord_mod[
                    1] <= tolerance_max[1]

                # If neither x or y can be snapped, return
                if not allow_snap_x and not allow_snap_y:
                    return

                coord_frac = [
                    start_offset[0] / grid.tile_selection[2],
                    start_offset[1] / grid.tile_selection[3]
                ]
                if coord_mod[0] > (grid.tile_selection[2] / 2.0):
                    coord_frac[0] = ceil(coord_frac[0])
                else:
                    coord_frac[0] = floor(coord_frac[0])

                if coord_mod[1] > (grid.tile_selection[3] / 2.0):
                    coord_frac[1] = ceil(coord_frac[1])
                else:
                    coord_frac[1] = floor(coord_frac[1])
                grid_coord = (self.start_coord[0] +
                              (coord_frac[0] * grid.tile_selection[2]),
                              self.start_coord[1] +
                              (coord_frac[1] * grid.tile_selection[3]))

        # 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

        # 1x1 tile selections cannot be auto joined
        tile_area = grid.tile_selection[2] * grid.tile_selection[3]
        if do_join and tile_area == 1:
            do_join = False

        # Store vertices of constructed faces for cursor flow
        faces_verts = []
        require_base_layer = data.work_layer != 'BASE'

        # Get the work layer filter, based on layer settings
        work_layer_mask = sprytile_utils.get_work_layer_data(data)

        # Build mode with join multi
        if do_join:
            origin_coord = ((grid_coord[0] + coord_min[0]),
                            (grid_coord[1] + coord_min[1]))

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

            tile_origin = (grid.tile_selection[0], grid.tile_selection[1])
            tile_coord = (tile_origin[0] + grid.tile_selection[2],
                          tile_origin[1] + grid.tile_selection[3])

            face_index = self.modal.construct_face(
                context,
                origin_coord, [size_x, size_y],
                tile_coord,
                tile_origin,
                grid_up,
                grid_right,
                up_vector,
                right_vector,
                plane_normal,
                require_base_layer=require_base_layer,
                work_layer_mask=work_layer_mask)
            if face_index is not None:
                face_verts = self.modal.face_to_world_verts(
                    context, face_index)
                faces_verts.extend(face_verts)
        # Build mode without auto join, try operation on each build coordinate
        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]
                ]

                face_index = self.modal.construct_face(
                    context,
                    grid_pos, [1, 1],
                    tile_pos,
                    tile_xy,
                    grid_up,
                    grid_right,
                    up_vector,
                    right_vector,
                    plane_normal,
                    require_base_layer=require_base_layer,
                    work_layer_mask=work_layer_mask)
                if face_index is not None:
                    face_verts = self.modal.face_to_world_verts(
                        context, face_index)
                    faces_verts.extend(face_verts)

        if plane_pos is not None:
            self.modal.add_virtual_cursor(plane_pos)

        if data.cursor_flow and data.work_layer == "BASE" and len(
                faces_verts) > 0:
            # Find which vertex the cursor should flow to
            new_cursor_pos = self.modal.flow_cursor_verts(
                context, faces_verts, plane_pos)
            if new_cursor_pos is not None:
                # Not base layer, move position back by offset
                if data.work_layer != 'BASE':
                    new_cursor_pos -= plane_normal * data.mesh_decal_offset
                # Calculate the world position of old start_coord
                old_start_pos = scene.cursor_location + (
                    self.start_coord[0] * grid_right) + (self.start_coord[1] *
                                                         grid_up)
                # find the offset of the old start position from the new cursor position
                new_start_offset = old_start_pos - new_cursor_pos
                # get how much the grid x/y vectors need to scale by to normalize
                scale_right = 1.0 / grid_right.magnitude
                scale_up = 1.0 / grid_up.magnitude
                # scale the offset by grid x/y, so can use normalized dot product to
                # find the grid coordinates the start position is from new cursor pos
                new_start_coord = Vector(
                    ((new_start_offset * scale_right).dot(
                        grid_right.normalized()),
                     (new_start_offset * scale_up).dot(grid_up.normalized())))
                # Record the new offset starting coord,
                # for the nice painting snap
                self.start_coord = new_start_coord

                scene.cursor_location = new_cursor_pos
Exemple #20
0
    def execute_fill(self, context, scene, ray_origin, ray_vector):
        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(
            scene, with_rotation=False)

        # Intersect on the virtual plane
        plane_hit = intersect_line_plane(ray_origin, ray_origin + ray_vector,
                                         scene.cursor_location, plane_normal)
        # Didn't hit the plane exit
        if plane_hit is None:
            return
        grid = sprytile_utils.get_grid(context, context.object.sprytile_gridid)
        sprytile_data = scene.sprytile_data

        world_pixels = sprytile_data.world_pixels
        grid_x = grid.grid[0]
        grid_y = grid.grid[1]

        # Find the position of the plane hit, in terms of grid coordinates
        hit_coord, grid_right, grid_up = sprytile_utils.get_grid_pos(
            plane_hit,
            scene.cursor_location,
            right_vector.copy(),
            up_vector.copy(),
            world_pixels,
            grid_x,
            grid_y,
            as_coord=True)

        # Check hit_coord is inside the work plane grid
        plane_size = sprytile_data.axis_plane_size
        if hit_coord.x < -plane_size[0] or hit_coord.x >= plane_size[0]:
            return
        if hit_coord.y < -plane_size[1] or hit_coord.y >= plane_size[1]:
            return

        # Build the fill map
        fill_map, face_idx_array = self.build_fill_map(context, grid_up,
                                                       grid_right,
                                                       plane_normal,
                                                       plane_size)

        # Convert from grid coordinate to map coordinate
        hit_array_coord = [
            int(hit_coord.x) + plane_size[0],
            int((plane_size[1] * 2) - 1 - (hit_coord.y + plane_size[1]))
        ]

        # Calculate the tile index of currently selected tile
        tile_xy = (grid.tile_selection[0], grid.tile_selection[1])
        # For getting paint settings later
        paint_setting_layer = self.modal.bmesh.faces.layers.int.get(
            'paint_settings')

        # Pre calculate for auto merge
        shift_vec = plane_normal.normalized() * 0.01
        threshold = (1 / context.scene.sprytile_data.world_pixels) * 2

        # Get vectors again, to apply tile rotations in UV stage
        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(
            scene)

        # Flood fill targets map cell coordinates
        hit_coord_content = int(
            fill_map[hit_array_coord[1]][hit_array_coord[0]])
        fill_coords = self.flood_fill(fill_map, hit_array_coord, -1,
                                      hit_coord_content)
        for cell_coord in fill_coords:
            self.construct_fill(context, scene, sprytile_data, cell_coord,
                                plane_size, face_idx_array,
                                paint_setting_layer, tile_xy, ray_vector,
                                shift_vec, threshold, up_vector, right_vector,
                                plane_normal, grid_up, grid_right)
Exemple #21
0
    def invoke(self, context, event):
        if context.scene.sprytile_data.is_running:
            return {'CANCELLED'}
        if context.space_data.type != 'VIEW_3D':
            self.report({'WARNING'},
                        "Active space must be a View3d: {0}".format(
                            context.space_data.type))
            return {'CANCELLED'}

        obj = context.object
        if obj.hide or obj.type != 'MESH':
            self.report({'WARNING'}, "Active object must be a visible mesh")
            return {'CANCELLED'}
        if len(context.scene.sprytile_mats) < 1:
            bpy.ops.sprytile.validate_grids()
        if len(context.scene.sprytile_mats) < 1:
            self.report({'WARNING'}, "No valid materials")
            return {'CANCELLED'}

        use_default_grid_id = obj.sprytile_gridid == -1
        if sprytile_utils.get_grid(context, obj.sprytile_gridid) is None:
            use_default_grid_id = True

        if use_default_grid_id:
            obj.sprytile_gridid = context.scene.sprytile_mats[0].grids[0].id

        if context.space_data.viewport_shade != 'MATERIAL':
            context.space_data.viewport_shade = 'MATERIAL'

        self.virtual_cursor = deque([], 3)
        self.no_undo = False
        self.left_down = False
        self.update_bmesh_tree(context)
        self.refresh_mesh = False

        # Setup Rx Observer and Observables
        self.rx_observer = None
        observable_source = Observable.create(self.setup_rx_observer)
        # Setup multi casting Observable
        self.rx_source = observable_source.publish().auto_connect(1)

        # Tools receive events from the Observable
        self.tools = {
            "build": ToolBuild(self, self.rx_source),
            "paint": ToolPaint(self, self.rx_source),
            "fill": ToolFill(self, self.rx_source),
            "set_normal": ToolSetNormal(self, self.rx_source)
        }

        # Set up timer callback
        win_mgr = context.window_manager
        self.view_axis_timer = win_mgr.event_timer_add(0.1, context.window)

        self.setup_user_keys(context)
        win_mgr.modal_handler_add(self)

        sprytile_data = context.scene.sprytile_data
        sprytile_data.is_running = True
        sprytile_data.is_snapping = False

        context.scene.sprytile_ui.is_dirty = True
        bpy.ops.sprytile.gui_win('INVOKE_REGION_WIN')
        return {'RUNNING_MODAL'}
Exemple #22
0
    def process_preview(modal, context, scene, face_index):
        obj = context.object
        data = scene.sprytile_data

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

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

        up_vector, right_vector, plane_normal = sprytile_utils.get_current_grid_vectors(scene, False)

        face_verts = []

        face = modal.bmesh.faces[face_index]
        for loop in face.loops:
            vert = loop.vert
            face_verts.append(context.object.matrix_world * vert.co)

        # Get the center of the preview verts
        vtx_center = Vector((0, 0, 0))
        for vtx in face_verts:
            vtx_center += vtx
        vtx_center /= len(face_verts)

        rotate_normal = plane_normal

        # Recalculate the rotation normal
        face_up, face_right = modal.get_face_up_vector(context, face_index)

        if face_up is not None and face_right is not None:
            rotate_normal = face_right.cross(face_up)

        if face_up is not None:
            up_vector = face_up
        if face_right is not None:
            right_vector = face_right

        rotation = Quaternion(rotate_normal, data.mesh_rotate)
        up_vector = rotation * up_vector
        right_vector = rotation * right_vector

        up_vector.normalize()
        right_vector.normalize()

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

        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)

        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]

        uvs = sprytile_uv.get_uv_pos_size(data, target_img.size, target_grid,
                                          tile_xy, size_x, size_y,
                                          up_vector, right_vector,
                                          face_verts, vtx_center)
        return face, face_verts, uvs, target_grid, data, target_img, tile_xy