def binpack(nodes, max_bin_height, spacing=0): ''' Add nodes to the bins of given max bin height and spacing ''' if nodes: debug("There are %d nodes to bin pack" % (len(nodes))) for node in nodes: if node == None: debug("WARNING: a None node in the spawned nodes???") else: debug("WARNING: there are no nodes to bin pack!!!") return [] scale = 1.0 / get_dpi_factor() # dpi adjustment scale items = [NodeItem(node.dimensions.x * scale, node.dimensions.y * scale, node.bl_idname, node) for node in nodes] items = sorted(items, key=lambda item: item.height, reverse=True) bins = [] for item in items: # try to fit the next item into the first bin that is not yet full for n, bin in enumerate(bins): # check all the bins created so far if bin.height + len(bin.items) * spacing + item.height <= max_bin_height: # bin not full ? => add item debug("ADDING node <%s> to bin #%d" % (item.name, n)) bin.append(item) break # proceed to the next item else: # item didn't fit into any bin ? => add it to a new bin debug('ADDING node <%s> to new bin' % (item.name)) bin = Bin() bin.append(item) bins.append(bin) return bins
def get_text_drawing_location(node): ''' Get the text drawing location relative to the node location ''' location = Vector((node.absolute_location)) location = location + Vector( (node.width + 20, -20)) # shift right of the node location = location + Vector(( 10 * node.text_offset_x, # apply user offset 10 * node.text_offset_y)) location = location * get_dpi_factor() # dpi scale adjustment return list(location)
def get_preferences(): from sverchok.settings import get_dpi_factor return get_dpi_factor()
def draw_statistics(self, names, values): """ Draw the statistics in the node editor The statistics data can be either single or vectorized. * single: [ [sum], [avg], ... [[b1, b2, .. bN]] ] * vectorized: [ [sum1... sumM], [avg1... avgM], ... [[b11... b1N]... [bM1... bMN]] ] Q: how can we tell statistics are simple or vectorized? A: if the values[0] is a list with length > 1 then it's vectorized """ nvBGL.callback_disable(node_id(self)) # clear drawing if len(values) == 0: return if self.show_statistics: max_width = max(len(name) for name in names) # used for text alignment info = [] # for now let's treat single and vectorized separately (optimize later) is_vectorized = len(values[0]) > 1 if is_vectorized: for n, v in zip(names, values): if n in ["Histogram", "HIS"]: line_format = "{0:>{x}} : [{1}]" histogram_lines = [] for a in v: # for each histogram set if self.mode == "FLOAT": value_format = "{:.{p}f}" else: value_format = "{}" histogram_values = ", ".join([ value_format.format(aa, p=self.precision) for aa in a ]) histogram_lines.append( "[{}]".format(histogram_values)) line = line_format.format(n, ", ".join(histogram_lines), x=max_width) info.append(line) else: line_format = "{0:>{x}} : [{1}]" if n in ["Count", "CNT"]: value_format = "{}" else: if self.mode == "FLOAT": value_format = "{:.{p}f}" else: value_format = "{}" value_line = ", ".join([ value_format.format(vv, p=self.precision) for vv in v ]) line = line_format.format(n, value_line, x=max_width) info.append(line) else: # single values for n, v in zip(names, values): if n in ["Histogram", "HIS"]: # print("drawing histogram") line_format = "{0:>{x}} : [{1}]" if self.normalize: value_format = "{:.{p}f}" else: value_format = "{}" histogram_values = ", ".join([ value_format.format(a, p=self.precision) for a in v[0] ]) line = line_format.format(n, histogram_values, x=max_width) info.append(line) else: if n in ["Count", "CNT"]: line_format = "{0:>{x}} : {1}" else: if self.mode == "FLOAT": line_format = "{0:>{x}} : {1:.{p}f}" else: line_format = "{0:>{x}} : {1}" line = line_format.format(n, v[0], x=max_width, p=self.precision) info.append(line) draw_data = { 'tree_name': self.id_data.name[:], 'node_name': self.name[:], 'content': info, 'location': get_text_drawing_location, 'color': self.text_color[:], 'scale': self.text_scale * get_dpi_factor(), 'font_id': int(self.selected_font_id()) } nvBGL.callback_enable(node_id(self), draw_data)
def arrange_nodes(self, context): ''' Arrange the nodes in current category (using bin-packing) ''' try: nodes = get_spawned_nodes() debug("ARRANGING %d nodes constrained by %s" % (len(nodes), self.constrain_layout)) scale = 1.0 / get_dpi_factor() # dpi adjustment scale max_node_width = max([node.dimensions.x * scale for node in nodes]) if self.constrain_layout == "HEIGHT": # prioritize the layout height to fit all nodes => variable width bins = binpack(nodes, self.grid_height, self.grid_y_space) elif self.constrain_layout == "WIDTH": # find the height that gives the desired width max_tries = 11 num_steps = 0 min_h = 0 max_h = 2 * (sum([node.dimensions.y * scale for node in nodes]) + (len(nodes)-1)*self.grid_height) while num_steps < max_tries: num_steps = num_steps + 1 bin_height = 0.5 * (min_h + max_h) # middle of the height interval # get the packed bins for the next bin height bins = binpack(nodes, bin_height, self.grid_y_space) # find the total width for current bin layout totalWidth = sum([bin.width for bin in bins]) # add the spacing between bins totalWidth = totalWidth + self.grid_x_space * (len(bins)-1) debug("{0} : min_h = {1:.2f} : max_h = {2:.2f}".format(num_steps, min_h, max_h)) debug("For bin height = %d total width = %d (%d bins)" % (bin_height, totalWidth, len(bins))) delta = abs((self.grid_width - totalWidth)/self.grid_width) debug("{0} : target = {1:.2f} : current = {2:.2f} : delta % = {3:.2f}".format( num_steps, self.grid_width, totalWidth, delta)) if delta < 0.1: # converged ? break else: # not found ? => binary search if self.grid_width < totalWidth: # W < w (make h bigger) min_h = bin_height else: # W > w (make h smaller) max_h = bin_height debug("*** FOUND solution in %d steps" % num_steps) debug("* {} bins of height {} : width {} : space {} ".format(len(bins), int(bin_height), int(totalWidth), (len(bins)-1)*self.grid_x_space )) else: # self.constrain_layout == "ASPECT" # find the height and width closest to the grid aspect ratio target_aspect = self.grid_width / self.grid_height max_tries = 11 num_steps = 0 min_h = 0 max_h = 2 * sum([node.dimensions.y * scale for node in nodes]) while num_steps < max_tries: num_steps = num_steps + 1 bin_height = 0.5 * (min_h + max_h) # middle of the height interval # get the packed bins for the next bin height bins = binpack(nodes, bin_height, self.grid_y_space) # find the max width for current layout totalWidth = sum([bin.width for bin in bins]) # add the spacing between bins totalWidth = totalWidth + self.grid_x_space * (len(bins)-1) debug("{0} : min_h = {1:.2f} : max_h = {2:.2f}".format(num_steps, min_h, max_h)) debug("For bin height = %d total width = %d" % (bin_height, totalWidth)) current_aspect = totalWidth / bin_height delta_aspect = abs(current_aspect - target_aspect) debug("{0} : target = {1:.2f} : current = {2:.2f} : delta = {3:.2f}".format( num_steps, target_aspect, current_aspect, delta_aspect)) if delta_aspect < 0.1: # converged ? break else: # not found ? => binary search if target_aspect < current_aspect: # W/H < w/h (make h bigger) min_h = bin_height else: # W/H > w/h (make h smaller) max_h = bin_height debug("*** FOUND solution in %d steps" % num_steps) debug("* {} bins of height {} : width {} : space {} ".format(len(bins), int(bin_height), int(totalWidth), (len(bins)-1)*self.grid_x_space )) max_idname = max([len(node.bl_idname) for node in nodes]) # ARRANGE the nodes in the bins on the grid x = 0 for bin in bins: y = 0 for item in bin.items: node_width = item.width node_height = item.height node_name = item.name node = item.node if self.node_alignment == "LEFT": node.location[0] = x elif self.node_alignment == "RIGHT": node.location[0] = x + (bin.width - node_width) else: # CENTER node.location[0] = x + 0.5 * (bin.width - node_width) node.location[1] = y debug("node = {0:>{x}} : W, H ({1:.1f}, {2:.1f}) & X, Y ({3:.1f}, {4:.1f})".format( node_name, node.dimensions.x * scale, node.dimensions.y * scale, node.location.x, node.location.y, x=max_idname)) y = y - (item.height + self.grid_y_space) x = x + (bin.width + self.grid_x_space) except Exception as e: print('EXCEPTION: arranging nodes failed:', str(e))