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()
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
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
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)
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, ))
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)
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
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
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)
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)
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)
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
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
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)
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'}
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
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)
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.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)
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'}
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