def compute(self, plug, data): this_node = self.thisMObject() if plug == self.a_instance_data: # if the node has yet not been in initialized create the instance # data attribute and read all point if some exist if not self._state: self.initialize_state(plug, data) # cache geometry is_cached = data.inputValue(self.a_geo_cached).asBool() if not is_cached: # check if there is another spore node that already has a cache # object for the current inmesh # note: this does not ensure that the cache is up to date! found = False for key, node in sys._global_spore_tracking_dir.iteritems(): other_in_mesh = node_utils.get_connected_in_mesh( node.thisMObject()) in_mesh = node_utils.get_connected_in_mesh( self.thisMObject()) if in_mesh == other_in_mesh and node != self: self.geo_cache = node.geo_cache # check if the cache is still valid if self.geo_cache.validate_cache(): found = True else: in_mesh = node_utils.get_connected_in_mesh( self.thisMObject(), False) self.geo_cache.flush_cache() self.geo_cache.cache_geometry(in_mesh) # if the cache is invalid break, since we need to recache break # if no cache was found start creating a new one if not found: in_mesh = node_utils.get_connected_in_mesh( self.thisMObject(), False) self.geo_cache.cache_geometry(in_mesh) # set cached to true node_fn = om.MFnDependencyNode(self.thisMObject()) cached_plug = node_fn.findPlug('geoCached') cached_plug.setBool(True) is_delete = data.inputValue(self.a_clear).asBool() if is_delete: self._state.clear() node_fn = om.MFnDependencyNode(self.thisMObject()) clear_plug = node_fn.findPlug('clear') clear_plug.setBool(False) data.setClean(self.a_instance_data)
def emit(self, *args): """ run the actual sample command and check if transforms are frozen """ # exit spore context since it looses track of points after sampling if cmds.currentCtx().startswith('spore'): cmds.setToolTo('selectSuperContext') in_mesh = node_utils.get_connected_in_mesh(self._node) transform = cmds.listRelatives(in_mesh, p=True, f=True)[0] if cmds.getAttr(transform + '.translateX') != 0\ or cmds.getAttr(transform + '.translateY') != 0\ or cmds.getAttr(transform + '.translateZ') != 0\ or cmds.getAttr(transform + '.rotateX') != 0\ or cmds.getAttr(transform + '.rotateY') != 0\ or cmds.getAttr(transform + '.rotateZ') != 0\ or cmds.getAttr(transform + '.scaleX') != 1\ or cmds.getAttr(transform + '.scaleY') != 1\ or cmds.getAttr(transform + '.scaleZ') != 1: msg = 'Feeze transformations to sample the geomety!' result = message_utils.IOHandler().confirm_dialog(msg, 'Freeze Transformations') if result: cmds.makeIdentity(transform, a=True, s=True, r=True, t=True, n=0) else: return cmds.setAttr('{}.emit'.format(self._node), 1) cmds.sporeSampleCmd()
def altitude_filter(self, min_altitude, max_altitude, min_fuzziness, max_fuzziness): """ filter points based on y position relative to bounding box """ in_mesh = node_utils.get_connected_in_mesh(self.target, False) dag_fn = om.MFnDagNode(in_mesh) bb = dag_fn.boundingBox() bb_y_min = bb.min().y height = bb.height() invalid_ids = [] for i, (position, _, _, _, _) in enumerate(self.point_data): y_normalized = position[1] - bb_y_min pos_rel = y_normalized / height if pos_rel < min_altitude: if pos_rel < min_altitude - min_fuzziness: invalid_ids.append(i) elif min_altitude - pos_rel > random.uniform(0, min_fuzziness): invalid_ids.append(i) elif pos_rel > max_altitude: if pos_rel > max_altitude + max_fuzziness: invalid_ids.append(i) elif abs(max_altitude - pos_rel) > random.uniform( 0, max_fuzziness): invalid_ids.append(i) invalid_ids = sorted(invalid_ids, reverse=True) [self.point_data.remove(index) for index in invalid_ids]
def get_spore_setups(self): """ return a dictionary with an entry for each target mesh and for each entry a list with all connected spore nodes """ spore_nodes = cmds.ls(type='sporeNode', l=True) targets = collections.defaultdict(list) # TODO - once this can be debugged this would be the way to go # [targets[node_utils.get_connected_in_mesh(node)].append(node) for node in spore_nodes] for node in spore_nodes: target = node_utils.get_connected_in_mesh(node) if target: targets[target].append(node) else: # May help to debbug a situation wherfe target is sometime None target = cmds.getConnection('{}.inMesh'.format(node)) target_shape = cmds.listRelatives(cmds.ls(target), s=True, f=True) if len(target_shape) == 1: obj_type = cmds.objectType(target_shape[0]) self.logger.warning( 'Getting target mesh failed but spore has been able ' 'to use fallback. target: {}, shape: {}, type: ' '{}'.format(target, target_shape[0], obj_type)) targets[target_shape] = node else: obj_type = [cmds.objectType(s) for s in target_shape] raise RuntimeError( 'Could not get target mesh and spore failed to use ' 'fallback. target {}, shapes {}, types: {}'.format( target, target_shape, obj_type) ) return targets
def voxelize(self, cell_size): """ partition the spatial domain with the given cellsize. than assign each point to the cell is spatialy belongs to. :return dict: where: key == the cell index value == list of points indexes from the point_data obj """ partition = {} in_mesh = node_utils.get_connected_in_mesh(self.target, False) bb = om.MFnDagNode(in_mesh).boundingBox() self.w_count = int(math.ceil(bb.width() / cell_size)) self.h_count = int(math.ceil(bb.height() / cell_size)) self.d_count = int(math.ceil(bb.depth() / cell_size)) bb_min = bb.min() for i in xrange(self.point_data.position.length()): p_normalized = self.point_data.position[i] - bb_min p_x = int(p_normalized.x / cell_size) p_y = int(p_normalized.y / cell_size) p_z = int(p_normalized.z / cell_size) index = p_x + p_y * self.w_count + p_z * self.w_count * self.h_count partition.setdefault(index, []).append(i) return partition
def get_spore_setups(self): """ return a dictionary with an entry for each target mesh and for each entry a list with all connected spore nodes """ spore_nodes = cmds.ls(type='sporeNode', l=True) targets = collections.defaultdict(list) [targets[node_utils.get_connected_in_mesh(node)].append(node) for node in spore_nodes] return targets
def random_sampling(self, num_points): """ sample a given number of points on the previously cached triangle mesh. note: evaluating uvs on high poly meshes may take a long time """ if not self.geo_cache.validate_cache(): in_mesh = node_utils.get_connected_in_mesh(self.target, False) self.geo_cache.cache_geometry(in_mesh) self.point_data.set_length(num_points) [self.sample_triangle(random.choice(self.geo_cache.weighted_ids), i) for i in xrange(num_points)]
def evaluate_uvs(self): """ evaluate uv coords for all points in point data. note: this may take a long time for large meshes """ in_mesh = node_utils.get_connected_in_mesh(self.target, False) for i, (position, normal, poly_id, u_coord, v_coord) in enumerate(self.point_data): if not u_coord and not v_coord: pos = om.MPoint(position[0], position[1], position[2]) u_coord, v_coord = mesh_utils.get_uv_at_point(in_mesh, pos) self.point_data.u_coord[i] = u_coord self.point_data.v_coord[i] = v_coord
def estimate_num_samples(self, node): """ estimate how many random samples we need for grid or disk sampling """ self._node = node emit_type = cmds.getAttr('{}.emitType'.format(node)) if emit_type == 1: cell_size = cmds.getAttr(self._node + '.cellSize') elif emit_type == 2: cell_size = cmds.getAttr(self._node + '.minRadius') / math.sqrt(3) else: return in_mesh = node_utils.get_connected_in_mesh(self._node) area = cmds.polyEvaluate(in_mesh, worldArea=True) cmds.setAttr(self._node + '.numSamples', int(area/cell_size) * 5)
def toolOnSetup(self, event): """ tool setup: - get the node's inMesh and set it as target for the tool - update the context controller - install mouse & key events - build the canvas frot drawing """ self.logger.debug('Set up Spore context') # get spore_node's inMesh and set it as target # note: we expect the target node to be selected when we setup the tool # if no sporeNode is selected we try to use the last target as fallback # if there is no fallback, tool initialization will fail and display a # warning try: # try to get selection of type sporeNode node_name = cmds.ls(sl=True, l=True, type='sporeNode')[0] except IndexError: node_name = None # try to get inMesh of selected spore node if node_name: self.state.target = node_utils.get_connected_in_mesh(node_name) self.state.node = node_name if not self.state.target or not self.state.node: self.logger.error('Failed to initialize Spore Context') return # fallback to old target, just pass since target is already set elif self.state.target and self.state.node: pass # if we neither have a sporeNode selected nor have a fallback, tool init fails else: self.msg_io.set_message('No sporeNode selected: Can\'t operate on: {}'.format(cmds.ls(sl=1), 1)) self.logger.warn('Context could not find target spore node') return # get node state & cache points for editing # self.instance_data = instance_data.SporeState(self.state.node) spore_obj = node_utils.get_mobject_from_name(node_name) obj_handle = om.MObjectHandle(spore_obj) spore_locator = sys._global_spore_tracking_dir[obj_handle.hashCode()] self.instance_data = spore_locator._state self.state.get_brush_settings() if self.state.settings['mode'] == 'scale'\ or self.state.settings['mode'] == 'align'\ or self.state.settings['mode'] == 'smooth'\ or self.state.settings['mode'] == 'move' \ or self.state.settings['mode'] == 'id'\ or self.state.settings['mode'] == 'remove': try: self.instance_data.build_kd_tree() except ValueError: # the spore node is empty self.msg_io.set_message('SporeNode is empty. Nothing to edit') return # install event filter view = window_utils.active_view_wdg() view.installEventFilter(self.mouse_event_filter) window = window_utils.maya_main_window() window.installEventFilter(self.key_event_filter) # set up canvas for drawing if self.state.settings['mode'] == 'place': #'place': self._setCursor(omui.MCursor.crossHairCursor) else: self.canvas = canvas.CircularBrush(self.state) self.help_display = canvas.HelpDisplay(self.state.settings['mode']) self.help_display.set_visible(False)
def boundingBox(self): in_mesh = node_utils.get_connected_in_mesh(self.thisMObject(), False) mesh_fn = om.MFnDagNode(in_mesh) return mesh_fn.boundingBox()
def disk_sampling_2d(self, radius, u=1, v=1): """ sample poisson disk samples in uv space note: the given radius must be between 0 and 1 """ # def poisson(radius, k, w, h, n): # TODO - make uv sample space editable for user grid = [] active = [] ordered = [] cellsize = radius / math.sqrt(2) col = int(u / cellsize) row = int(v / cellsize) grid = [None] * col * row x = random.random() * u # random.uniform( 0, w ) y = random.random() * v # random.uniform( 0, h ) pos = (x, y) current_col = int(x / cellsize) current_row = int(y / cellsize) grid[current_col + current_row * col] = pos active.append(pos) ordered.append(pos) while len(active) > 0: rand_index = int(len(active) - 1) active_pos = active[rand_index] found = False k = 30 for each in xrange(k): # find a new point withhin a the range of radius - 2r rand_distance = random.uniform(radius, 2 * radius) theta = 360 * random.random() offset_x = math.cos(theta) * rand_distance offset_y = math.sin(theta) * rand_distance # get the absolut position of the new pos new_x = active_pos[0] + offset_x new_y = active_pos[1] + offset_y new_pos = (new_x, new_y) # get the new col & row position of the point active_col = int(new_x / cellsize) active_row = int(new_y / cellsize) if active_col > 0 \ and active_row > 0 \ and active_col < col \ and active_row < row \ and not grid[active_col + active_row * col]: valid = True for j in xrange(-1, 2): for f in xrange(-1, 2): index = (active_col + j) + (active_row + f) * col try: neighbor = grid[index] except IndexError: continue if neighbor: distance = math.sqrt( (neighbor[0] - new_pos[0])**2 + (neighbor[1] - new_pos[1])**2) if (distance < radius): valid = False if valid: found = True grid[active_col + active_row * col] = new_pos active.append(new_pos) ordered.append(new_pos) # TODO if not found: active.pop(rand_index) self.point_data.set_length(len(ordered)) util = om.MScriptUtil() in_mesh = node_utils.get_connected_in_mesh(self.target, False) mesh_fn = om.MFnMesh(in_mesh) for i, (u_coord, v_coord) in enumerate(ordered): face_ids = self.geo_cache.get_close_face_ids(u_coord, 1 - v_coord) position = om.MPoint() normal = om.MVector() for face_id in face_ids: util.createFromList([u_coord, 1 - v_coord], 2) ptr = util.asFloat2Ptr() try: mesh_fn.getPointAtUV(face_id, position, ptr, om.MSpace.kWorld) mesh_fn.getClosestNormal(position, normal) self.point_data.set(i, position, normal, 1, u_coord, 1 - v_coord) except: continue
def disk_sampling_3d(self, min_radius, grid_partition, cell_size): in_mesh = node_utils.get_connected_in_mesh(self.target, False) bb = om.MFnDagNode(in_mesh).boundingBox() # pick randomly an initial point from where we start sampling initial_key = random.choice(grid_partition.keys()) init_p_ref = random.choice(grid_partition[initial_key]) # create two list for active (not yet processed) and valid (sampled) points active = [] valid_points = [None] * self.w_count * self.h_count * self.d_count # append the first point to both lists active.append(init_p_ref) valid_points[initial_key] = init_p_ref while len(active) > 0: # pick a random point from the active points list p_active_index = int(len(active) * random.random()) p_active = active[p_active_index] # TODO - get each active point only once? # normalize the point and get it's x,y,z index in the grid p_normalized = self.point_data.position[p_active] - bb.min() p_grid_x = int(p_normalized.x / cell_size) p_grid_y = int(p_normalized.y / cell_size) p_grid_z = int(p_normalized.z / cell_size) # assume no point will be found found = False # try k times to find a new sample k = 30 for i in xrange(k): # get neighboring cell new_p_x, new_p_y, new_p_z = self.get_valid_neighbouring_cell( p_grid_x, p_grid_y, p_grid_z) # get liear index from x,y,z position. key = new_p_x + new_p_y * self.w_count + new_p_z * self.w_count * self.h_count # check if key is valid and if there isnt already a valid point... if grid_partition.has_key(key)\ and not valid_points[key]: # get a random point from the list associated with the key new_index = int(len(grid_partition[key]) * random.random()) point_index = grid_partition[key][new_index] point = self.point_data.position[point_index] # ...otherwise try again else: continue valid = True # check against all nearby cells if the sample is valid for x in xrange(new_p_x - 1, new_p_x + 2): for y in xrange(new_p_y - 1, new_p_y + 2): for z in xrange(new_p_z - 1, new_p_z + 2): # ignore invalid cells if x < 0 or y < 0 or z < 0: continue elif x > self.w_count - 1 or y > self.h_count - 1 or z > self.d_count - 1: continue # get the index for the current cell index = x + y * self.w_count + z * self.w_count * self.h_count # check if there is already a valid point in the cell if index >= 0 and index <= len(valid_points) - 1: if valid_points[index]: neighbor = self.point_data.position[ valid_points[index]] else: continue else: raise RuntimeError continue # check distance to the next neighbour # if it conflicts tag the point invalid and break # out of the loop distance = point.distanceTo( neighbor) # + min_radius / 10 if distance < min_radius: # - (min_radius / 10) : valid = False break if valid is False: break if valid is False: break if valid: found = True valid_points[key] = point_index active.append(point_index) if not found: active.remove(p_active) # active.pop(p_active_index) return [i for i in valid_points if i is not None]
def disk_sampling_2d(self, radius, u=1, v=1): """ sample poisson disk samples in uv space note: the given radius must be between 0 and 1 """ # TODO - make uv sample space editable for user grid = [] active = [] ordered = [] cellsize = radius / math.sqrt(2) col = int(u / cellsize) row = int(v / cellsize) grid = [None] * col * row # x = random.random() * u # y = random.random() * v # since x=1 + y=1 would raise an index err let's assume the first point # at 0.5 and 0.5 x = 0.5 y = 0.5 pos = (x, y) current_col = int(x / cellsize) current_row = int(y / cellsize) try: grid[current_col + current_row * col] = pos except IndexError as e: # this should not happen any more since the initial point is at # 0.5 / 0.5 but just in case let's log what causes the index error self.logger.error( 'Error intializing 2d poisson sampler. ' 'grid of len({}) build with {} columns, {} rows. ' 'Initial index is: {}, {}'.format(len(grid), col, row, current_col, current_row)) raise IndexError(e) active.append(pos) ordered.append(pos) while len(active) > 0: rand_index = int(len(active) - 1) active_pos = active[rand_index] found = False k = 30 for each in xrange(k): # find a new point withhin a the range of radius - 2r rand_distance = random.uniform(radius, 2 * radius) theta = 360 * random.random() offset_x = math.cos(theta) * rand_distance offset_y = math.sin(theta) * rand_distance # get the absolut position of the new pos new_x = active_pos[0] + offset_x new_y = active_pos[1] + offset_y new_pos = (new_x, new_y) # get the new col & row position of the point active_col = int(new_x / cellsize) active_row = int(new_y / cellsize) if active_col > 0 \ and active_row > 0 \ and active_col < col \ and active_row < row \ and not grid[active_col + active_row * col]: valid = True for j in xrange(-1, 2): for f in xrange(-1, 2): index = (active_col + j) + (active_row + f) * col try: neighbor = grid[index] except IndexError: continue if neighbor: distance = math.sqrt( (neighbor[0] - new_pos[0])**2 + (neighbor[1] - new_pos[1])**2) if (distance < radius): valid = False if valid: found = True grid[active_col + active_row * col] = new_pos active.append(new_pos) ordered.append(new_pos) # TODO if not found: active.pop(rand_index) self.point_data.set_length(len(ordered)) util = om.MScriptUtil() in_mesh = node_utils.get_connected_in_mesh(self.target, False) mesh_fn = om.MFnMesh(in_mesh) for i, (u_coord, v_coord) in enumerate(ordered): face_ids = self.geo_cache.get_close_face_ids(u_coord, 1 - v_coord) position = om.MPoint() normal = om.MVector() for face_id in face_ids: util.createFromList([u_coord, 1 - v_coord], 2) ptr = util.asFloat2Ptr() try: mesh_fn.getPointAtUV(face_id, position, ptr, om.MSpace.kWorld) mesh_fn.getClosestNormal(position, normal) self.point_data.set(i, position, normal, 1, u_coord, 1 - v_coord) except: continue