def execute(self, context, scene, ray_origin, ray_vector): # Raycast the object obj = context.object # Get the work layer filter, based on layer settings work_layer_mask = sprytile_utils.get_work_layer_data( scene.sprytile_data) hit_loc, hit_normal, face_index, hit_dist = self.modal.raycast_object( obj, ray_origin, ray_vector, work_layer_mask=work_layer_mask) if hit_loc is None: return face, verts, uvs, target_grid, data, target_img, tile_xy = ToolPaint.process_preview( self.modal, context, scene, face_index) if face is None: return self.modal.add_virtual_cursor(hit_loc) sprytile_uv.apply_uvs(context, face, uvs, target_grid, self.modal.bmesh, data, target_img, tile_xy, origin_xy=tile_xy)
def apply_uvs(context, face, uv_verts, target_grid, mesh, data, target_img, tile_xy, uv_layer=None, origin_xy=None): if uv_layer is None: uv_layer = mesh.loops.layers.uv.verify() # 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 # If adding more layers, make sure setup in sprytile_modal.update_bmesh_tree grid_layer_id = mesh.faces.layers.int.get(UvDataLayers.GRID_INDEX) grid_layer_tileid = mesh.faces.layers.int.get(UvDataLayers.GRID_TILE_ID) grid_sel_width = mesh.faces.layers.int.get(UvDataLayers.GRID_SEL_WIDTH) grid_sel_height = mesh.faces.layers.int.get(UvDataLayers.GRID_SEL_HEIGHT) grid_sel_origin = mesh.faces.layers.int.get(UvDataLayers.GRID_SEL_ORIGIN) paint_settings_id = mesh.faces.layers.int.get(UvDataLayers.PAINT_SETTINGS) work_layer_id = mesh.faces.layers.int.get(UvDataLayers.WORK_LAYER) 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] origin_id = tile_id if origin_xy is not None: origin_id = (origin_xy[1] * row_size) + origin_xy[0] paint_settings = sprytile_utils.get_paint_settings(data) work_layer_data = sprytile_utils.get_work_layer_data(data) sel_width = target_grid.tile_selection[2] sel_height = target_grid.tile_selection[3] face[grid_layer_id] = context.object.sprytile_gridid face[grid_layer_tileid] = tile_id face[grid_sel_width] = sel_width face[grid_sel_height] = sel_height face[grid_sel_origin] = origin_id face[paint_settings_id] = paint_settings face[work_layer_id] = work_layer_data bmesh.update_edit_mesh(context.object.data) mesh.faces.index_update() return face.index, target_grid
def build_preview(self, context, scene, ray_origin, ray_vector): # Raycast the object obj = context.object # Get the work layer filter, based on layer settings work_layer_mask = sprytile_utils.get_work_layer_data( scene.sprytile_data) hit_loc, hit_normal, face_index, hit_dist = self.modal.raycast_object( obj, ray_origin, ray_vector, work_layer_mask=work_layer_mask) if hit_loc is None: self.modal.clear_preview_data() return face, verts, uvs, target_grid, data, target_img, tile_xy = ToolPaint.process_preview( self.modal, context, scene, face_index) if face is None: self.modal.clear_preview_data() return self.modal.set_preview_data(verts, uvs, is_quads=False)
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
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 find_face_tile(self, context, event): if self.tree is None or context.scene.sprytile_ui.use_mouse is True: return None # 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) work_layer_mask = sprytile_utils.get_work_layer_data( context.scene.sprytile_data) location, normal, face_index, distance = self.raycast_object( context.object, ray_origin, ray_vector, work_layer_mask=work_layer_mask) if location is None: return None 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 None tilegrid = sprytile_utils.get_grid(context, grid_id) if tilegrid is None: return None texture = sprytile_utils.get_grid_texture(context.object, tilegrid) if texture is None: return None 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) # Extract the tile orientation/selection data packed in paint settings 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() return face_index