def preferred_score(given_well, alignment): """combine distance from the given node to the gravity well, and the node's current alignment status for a preference score """ node_center = alignment.anode.center distance = pos._2d_distance(node_center, given_well) x, y = given_well num_aligned_nodes = len([ n for n in alignment.ancillaries if x == pos.center(n)[0] or y == pos.center(n)[1] ]) final_score = distance / num_aligned_nodes return final_score
def find_common_axis(nodes=None): """return the axis along which the given nodes are already aligned, if any. """ centers = [pos.center(n) for n in nodes] x_counter = Counter([c[0] for c in centers]) y_counter = Counter([c[1] for c in centers]) common_x = x_counter.most_common(2) common_y = y_counter.most_common(2) if common_x[0][1] > common_y[0][1]: if common_x[0][1] == len(nodes): return ('y', 'aligned') elif common_x[0][1] != common_x[1][1]: return ('x', common_x[0][0]) else: return None elif common_y[0][1] > common_x[0][1]: if common_y[0][1] == len(nodes): return ('x', 'aligned') elif common_y[0][1] != common_y[1][1]: return ('y', common_y[0][0]) else: return None else: return None
def align_selected(nodes=None): """align nodes based on current selection""" if len(nodes) == 1: node = nodes[0] cur_pos = (node.xpos(), node.ypos()) with utils.Undoable_Action(): preferred(node=node) if cur_pos == (node.xpos(), node.ypos()): cycle_wells(node=node) else: common_axis = find_common_axis(nodes) if common_axis: axis, position = common_axis if position == 'aligned': move.space_out_nodes(axis=axis, nodes=nodes) else: with utils.Undoable_Action(): for n in nodes: move.center_to(n, **{axis: position}) else: root_node = max(nodes, key=pre_aligned_score) root_pos = pos.center(root_node) bbox = pos.group_bounding_box(nodes) if bbox[2] - bbox[0] > bbox[3] - bbox[1]: mover = 'y' axis = 1 else: mover = 'x' axis = 0 with utils.Undoable_Action(): for n in nodes: move.center_to(n, **{mover: root_pos[axis]})
def __init__(self, node): super(AlignableNode, self).__init__() self.node = node self.name = node.name() self.corner = pos.xy(node) self.center = pos.center(node) self.node_class = node.Class() self.join = False
def gravity_wells(nodes): """return all possible alignments for a set of nodes""" node_centers = [pos.center(n) for n in nodes] wells = [(x[0], y[1]) for x in node_centers for y in node_centers if (x[0], y[1]) not in node_centers] # If there are no wells, then the ancillaries are all in a line. unique_wells = list(set(wells)) return sorted(unique_wells, key=lambda x: wells.index(x))
def space_out_nodes(nodes=None, axis='x'): """space out nodes along the given axis""" axes = ('x', 'y') axis_index = axes.index(axis) constrained_axis = 1 - axis_index min_node = pos.xmost_node(nodes=nodes, axis=axis, min_max=min) max_node = pos.xmost_node(nodes=nodes, axis=axis, min_max=max) min_pos = pos.center(min_node) max_pos = pos.center(max_node) min_on_axis = min_pos[axis_index] max_on_axis = max_pos[axis_index] constrained = min_pos[constrained_axis] interval = int(max_on_axis - min_on_axis) / (len(nodes) - 1) nodes.sort(key=lambda x: x['{}pos'.format(axis)].value()) # nodes.pop() for i, node in enumerate(nodes): npos = [0, 0] npos[constrained_axis] = constrained npos[axis_index] = min_on_axis + (i * interval) center_to(node=node, x=npos[0], y=npos[1])
def to_relative(node_one, node_two, x=0, y=0): """base function for moving nodes relative to each other. node_one is always relative to node_two """ cen_x, cen_y = pos.center(node_two) center_to(node_one, cen_x, cen_y) axis = 'x' if x else 'y' node_offset = _node_offsets(node_one, node_two, axis) if x < 0 or y < 0: node_offset = -node_offset if x: x += node_offset elif y: y += node_offset nudge(node_one, x, y)
def cycle_wells(node=None): """align the node to the next available gravity well""" alignment = Node_Alignment(node) node_center = alignment.anode.center wells = alignment.wells all_centers = [pos.center(n) for n in alignment.ancillaries] wells.sort(key=lambda x: wells_score(x, all_centers)) if wells: if node_center in wells: try: final_well = wells[wells.index(node_center) + 1] except IndexError: final_well = wells[0] else: final_well = wells[0] move.center_to(node, *final_well) else: pass
def _sort_wells(self): all_centers = [pos.center(a) for a in self.ancillaries] self.wells.sort(key=lambda x: (wells_score(x, all_centers)))
def expand_contract(direction, expand_or_contract, amt=.1, nodes=None): """expands a set of nodes in a given direction. direction can be left, right, up, down, topleft, topright, botleft, botright, xaxis, yaxis, or center.""" # TODO: needs a good pep-8-ing and probably a good refactoring if direction in ('left', 'right', 'up', 'down'): graphNodes = [n for n in nodes if n.Class() != 'BackdropNode'] backdropNodes = [n for n in nodes if n.Class() == 'BackdropNode'] # get the bounding box xmin, ymin, xmax, ymax = pos.group_bounding_box(nodes) # ------------------- Direction of Movement ----------------------- # if expand_or_contract == 'expand': amt = 1 + amt else: amt = 1 - amt if direction == 'down': total = ymax - ymin target = (total * amt) - total offsetter = 1 anchor = ymin if direction == 'up': total = ymax - ymin target = -1 * ((total * amt) - total) offsetter = 1 anchor = ymax if direction == 'left': total = xmax - xmin target = -1 * ((total * amt) - total) offsetter = 0 anchor = xmax if direction == 'right': total = xmax - xmin target = (total * amt) - total offsetter = 0 anchor = xmin # If the destination is the same as the origin, skip evaluation if total == 0: return # ------------------- Move Nodes ---------------------------------- # for n in graphNodes: offAxis = pos.center(n)[offsetter] offset = abs(offAxis - anchor) offsetRatio = float(offset) / float(total) offsetBy = target * offsetRatio n[('xpos', 'ypos')[offsetter]].setValue( int(n[('xpos', 'ypos')[offsetter]].getValue() + offsetBy)) # ------------------- Handle Backdrops ---------------------------- # for bn in backdropNodes: if direction == 'down' or direction == 'up': offsetTop = abs(bn['ypos'].getValue() - anchor) otRatio = offsetTop / total offsetBottom = abs((bn['ypos'].getValue() + bn['bdheight'].getValue()) - anchor) obRatio = offsetBottom / total offsetTopBy = target * otRatio offsetBottomBy = (target * obRatio) - offsetTopBy bn['ypos'].setValue(int(bn['ypos'].getValue() + offsetTopBy)) bn['bdheight'].setValue( int(bn['bdheight'].getValue() + offsetBottomBy)) if direction == 'left' or direction == 'right': offsetLeft = abs(bn['xpos'].getValue() - anchor) olRatio = offsetLeft / total offsetRight = abs((bn['xpos'].getValue() + bn['bdwidth'].getValue()) - anchor) orRatio = offsetRight / total offsetLeftBy = target * olRatio offsetRightBy = (target * orRatio) - offsetLeftBy bn['xpos'].setValue(int(bn['xpos'].getValue() + offsetLeftBy)) bn['bdwidth'].setValue( int(bn['bdwidth'].getValue() + offsetRightBy)) # ------------------- Macros ------------------------------------------ # elif direction == 'yaxis': expand_contract('up', expand_or_contract, (float(amt) / 2.0), nodes) expand_contract('down', expand_or_contract, (float(amt) / 2.0), nodes) elif direction == 'xaxis': expand_contract('left', expand_or_contract, (float(amt) / 2.0), nodes) expand_contract('right', expand_or_contract, (float(amt) / 2.0), nodes) elif direction == 'botright': expand_contract('left', expand_or_contract, amt, nodes) expand_contract('up', expand_or_contract, amt, nodes) elif direction == 'botleft': expand_contract('right', expand_or_contract, amt, nodes) expand_contract('up', expand_or_contract, amt, nodes) elif direction == 'topleft': expand_contract('right', expand_or_contract, amt, nodes) expand_contract('down', expand_or_contract, amt, nodes) elif direction == 'topleft': expand_contract('right', expand_or_contract, amt, nodes) expand_contract('down', expand_or_contract, amt, nodes) elif direction == 'topright': expand_contract('left', expand_or_contract, amt, nodes) expand_contract('down', expand_or_contract, amt, nodes) elif direction == 'center': expand_contract('left', expand_or_contract, (float(amt) / 2.0), nodes) expand_contract('right', expand_or_contract, (float(amt) / 2.0), nodes) expand_contract('up', expand_or_contract, (float(amt) / 2.0), nodes) expand_contract('down', expand_or_contract, (float(amt) / 2.0), nodes) else: pass
def center_to(node=None, x=None, y=None): """move the center of the node to the specified coordinates""" cen_x, cen_y = pos.center(node) move_by_x = x - cen_x if x else 0 move_by_y = y - cen_y if y else 0 nudge(node, move_by_x, move_by_y)