def draw(self, root, surface=None): surface = surface if surface is not None else self.screen points = [] for func, args in flatten(root): #if func not in ["begin_region", "move_to", "line_to"]: continue if func == "stroke_and_fill": # Hack for arcs if len(points) <= 1: continue #flat_pts = [coord for point in points for coord in point] # Problem: with alpha, want a bbox and then translate... if default_get(args, "fill_color") and len(points) > 2: pygame.draw.polygon( surface, intcol(default_get(args, "fill_color")), map(tuple, points), 0) if default_get(args, "stroke_color"): if default_get(args, "dash"): #[start*t + end*(1-t) for t in midpoints] pass pygame.draw.lines( surface, intcol(default_get(args, "stroke_color")), False, points, int(default_get(args, "line_width"))) elif func == "text": font_size = args['font_size'] if numpy.array_equal(args["transform"], identity) or args['transform'][0][1] or args['transform'][1][0]\ else args['font_size'] * args["transform"][0][0] #font_size = args['font_size'] font_param = (args.get("font_face"), int(round(1.5 * font_size))) color = intcol(default_get(args, "stroke_color")) text = get_font(font_param).render(str(args["text"]), True, color) surface.blit(text, args["botleft"] - P(0, text.get_height())) elif func == "group": pass elif func == "begin_region": points = [] elif func == "end_region": pass elif func in ["move_to", "line_to"]: points.append(args) elif func == "arc": # Doesn't work inside polygons yet. topleft = args["center"] - P(args["radius"], args["radius"]) wh = P(2 * args["radius"], 2 * args["radius"]) angles = args["angle"] if angles == (0, 2 * math.pi): pygame.draw.circle( surface, intcol(default_get(args["style"], "stroke_color")), map(int, args["center"]), args["radius"], 0) else: pygame.draw.arc( surface, intcol(default_get(args["style"], "stroke_color")), (topleft, wh), angles[0], angles[1], default_get(args, "line_width")) else: raise Exception('Unknown function %s, %s' % (func, args))
def run_button(mod=None): root = doc[doc["selection.root"]] txy = doc["editor.mouse_txy"] cui = doc['custom_ui'] if default_get(doc['custom_ui'], 'visible') else [] more = [] for child in chain(cui, reversed(root)): if collide(child, txy): print "clicked on", child["id"], "on_click" in child, mod if "on_click" in child and not mod: run_text(child["id"], "on_click") elif "on_shift_click" in child and mod == "shift": run_text(child["id"], "on_shift_click") elif child.get("button_group"): # Wrong. Should test children right away in reverse order. more.extend([(gc, child.transform) for gc in child]) print "Button group", len(more) #[n['id'] for n in more] continue return True # TODO: refactor for child, transform in more: if collide(child, txy, transform=transform): print "clicked on", child["id"], "on_click" in child, mod if "on_click" in child and not mod: run_text(child["id"], "on_click") elif "on_shift_click" in child and mod == "shift": run_text(child["id"], "on_shift_click") elif child.get("button_group"): more.extend([(gc, child.transform) for gc in child]) print "Button group", [n['id'] for n in more] continue return True return False
def draw(self, root): points = [] for func, args in flatten(root): #if func not in ["begin_region", "move_to", "line_to"]: continue if func == "stroke_and_fill": # Hack for arcs if len(points) <= 1: continue flat_pts = [coord for point in points for coord in point] self.canvas.create_polygon( *flat_pts, width=default_get(args, "line_width"), dash=default_get(args, "dash")[0], outline=hexcol(default_get(args, "stroke_color")), fill=hexcol(default_get(args, "fill_color"))) elif func == "text": font = (args["font_face"] if args.get("font_face") else "TkFixedFont", int(round(args["font_size"]))) self.canvas.create_text(*args["botleft"], font=font, fill=hexcol( default_get(args, "stroke_color")), text=args["text"], anchor="sw") elif func == "group": pass elif func == "begin_region": points = [] elif func == "end_region": pass elif func in ["move_to", "line_to"]: points.append(args) elif func == "arc": # Doesn't work inside polygons yet. x, y = args["center"] r = args["radius"] start = args["angle"][0] * 180 / math.pi, angle_diff = (args["angle"][0] - args["angle"][1]) * 180 / math.pi slice_type = Tkinter.PIESLICE if default_get( args["style"], "fill_color") else Tkinter.ARC if angle_diff % 360 == 0: #self.canvas.create_arc(x-r, y-r, x+r, y+r, start=0, extent=360, fill="red") start = 0.0 # Must be a tkinter bug angle_diff = 359 self.canvas.create_arc( x - r, y - r, x + r, y + r, start=start, extent=angle_diff, style=slice_type, outline=hexcol(default_get(args["style"], "stroke_color")), fill=hexcol(default_get(args["style"], "fill_color"))) else: raise Exception('Unknown function %s, %s' % (func, args))
def bbox(self, transform=identity, skip=False): """ Bound box. Includes self's transform.""" # Want some way to cache the answer for children? # Would need transforms to be applied after instead of before. # Could almost make this an expression # transformed() already applies the last transform to points if not skip and self.name != "point": transform = transform.dot(self.transform) if self.name in ["group", "path"]: boxes = [child.bbox(transform) for child in self] boxes = zip(*[ box for box in boxes if not numpy.array_equal(box, (None, None)) ]) if not boxes: return (None, None) return (numpy.min(numpy.vstack(boxes[0]), 0), numpy.max(numpy.vstack(boxes[1]), 0)) elif self.name == "ref": return self.reference().bbox(transform, skip=True) elif self.name == "line": m = numpy.vstack([ transformed(self["start"], transform), transformed(self["end"], transform) ]) line_width = default_get(self, "line_width") return numpy.min(m, 0) - line_width, numpy.max(m, 0) + line_width elif self.name == "arc": end0, end1 = arc_endpoints(transformed(self["center"], transform), self["radius"], default_get(self, "angle")) m = numpy.vstack([end0, end1]) line_width = default_get(self, "line_width") return numpy.min(m, 0) - line_width, numpy.max(m, 0) + line_width elif self.name == "point": point = transformed(self, transform) return point, point elif self.name == "text": botleft = transformed(self["botleft"], transform) xy, wh, dxy = extents(unicode(self["value"]), default_get(self, "font_size"), default_get(self, "font_face")) botright = botleft + dxy topleft = botleft + xy return topleft, botright
def collide(root, xy, style = {}, transform=identity, tolerance=3, skip=False): style = style.copy() for key in ["line_width", "stroke_color", "fill_color", "dash"]: if key in root: style[key] = root[key] if root.name in ["path", "group"]: if not skip: transform = transform.dot(root.transform) if root.name == "path" and style.get("fill_color"): # print "Testing fill" path = [(transformed(seg["start"], transform), transformed(seg["end"], transform)) for seg in root if seg.name == 'line'] # print "Input", xy, path if point_in_closed_path(xy, path): return True return any(collide(child, xy, style, transform, tolerance) for child in root) elif root.name == "line": line = (transformed(root["start"], transform), transformed(root["end"], transform)) xy = numpy.array(xy) dist2 = default_get(style, "line_width") + tolerance**2 #print "dist", distance2(transform[:2,:2] * xy, line), dist2 return distance2(xy, line) < dist2 elif root.name == "arc": center=transformed(root["center"], transform) return distance_to_arc(xy, center, root["radius"], default_get(root, "angle")) < tolerance elif root.name == "point": point = transformed(root, transform) xy = numpy.array(xy) return norm2(xy - point) < tolerance**2 elif root.name == "text": top_left, bottom_right = root.bbox(transform) def contains(top_left, bottom_right, xy): if numpy.array_equal((top_left, bottom_right), (None, None)): return False return all(top_left <= xy) and all(xy <= bottom_right) #return numpy.all(top_left <= xy <= bottom_right) tol = (tolerance, tolerance) return contains(top_left - tol, bottom_right + tol, xy) else: # Not yet implemented return False
def run_button(): root = doc[doc["selection.root"]] txy = doc["editor.mouse_txy"] cui = doc['custom_ui'] if default_get(doc['custom_ui'], 'visible') else [] for child in chain(cui, reversed(root)): if collide(child, txy): print "clicked on", child["id"], "on_click" in child if "on_click" in child: run_text(child["id"], "on_click") return True return False
def grab_point(): root = doc[doc["selection.root"]] cui = doc['custom_ui'].dfs() if default_get(doc['custom_ui'], 'visible') else [] for child, transform in chain(root.dfs(), cui): if child.name == "point" and\ collide(child, doc["editor.mouse_txy"], transform=transform, tolerance=8): doc["editor.drag_start"] = doc["editor.mouse_txy"] doc["editor.grabbed"] = child["id"] child.transforms["editor"] = Ex( "('translate', `editor.mouse_txy - `editor.drag_start)", calc='on first read') return True return False
def flatten_seg(root, style = {}, transform = identity): if root.name in ["line", "curve", "arc"]: if root.name in "line": yield ("line_to", transformed(root["end"], transform)) elif root.name == "curve": yield ("curve_to", transformed(root["start_control"], transform) +\ transformed(root["end_control"], transform) +\ transformed(root["end"], transform)) elif root.name == "arc": center = transformed(root["center"], transform) yield ("arc", {"center": center, "radius": root["radius"], "angle": default_get(root, "angle"), "style": style})
def draw(self, root): points = [] for func, args in flatten(root): #if func not in ["begin_region", "move_to", "line_to"]: continue if func == "stroke_and_fill": # Hack for arcs if len(points) <= 1: continue if default_get(args, "fill_color"): glColor3f(*default_get(args, "fill_color")) glBegin(GL_POLYGON) for point in points: glVertex2d(*(point)) glEnd() if default_get(args, "stroke_color"): glColor3f(*default_get(args, "stroke_color")) glBegin(GL_LINE_LOOP) for point in points: glVertex2d(*(point)) glEnd() elif func == "text": glWindowPos2f(args["botleft"][0], self.height - args["botleft"][1]) for ch in args["text"]: glutBitmapCharacter(GLUT_BITMAP_TIMES_ROMAN_24, ctypes.c_int(ord(ch))) elif func == "group": pass elif func == "begin_region": points = [] elif func == "end_region": pass elif func in ["move_to", "line_to"]: points.append(args) elif func == "arc": # Doesn't work inside polygons yet. x, y = args["center"] r = args["radius"] start = args["angle"][0] * 180 / math.pi, angle_diff = (args["angle"][0] - args["angle"][1]) * 180 / math.pi if "fill_color" in args["style"]: glColor3f(*default_get(args["style"], "fill_color")) glBegin(GL_POLYGON) else: glColor3f(*default_get(args["style"], "stroke_color")) glBegin(GL_LINES) N = 20 for i in xrange(N): theta = i * 2 * math.pi / N glVertex2d(x + r * math.sin(theta), y + r * math.cos(theta)) glEnd() else: raise Exception('Unknown function %s, %s' % (func, args))
def rectangle_lasso_points(): root = doc[doc["selection.root"]] cui = doc['custom_ui'].dfs() if default_get(doc['custom_ui'], 'visible') else [] root = doc[doc["selection"]["root"]] txy = doc["editor.mouse_txy"] for rect in reversed(root): if collide(rect, txy): topleft, botright = rect.bbox() print("Found rect", topleft, botright) for child, transform in chain(root.dfs(), cui): if child.name == "point" and\ (topleft <= child['value']).all() and\ (child['value'] <= botright).all(): if child["id"] not in doc["editor.selected"]: selection_add(doc, child) break
def draw(self, layer = "drawing", root = None): if root is None: doc, root_id = self.layers[layer] root = doc[root_id] context = self.contexts[layer] for func, args in flatten(root): if func == "stroke_and_fill": if default_get(args, "fill_color"): context.set_source_rgb(*default_get(args, "fill_color")) if args.get("stroke_color") is not None: context.fill_preserve() else: context.fill() if default_get(args, "stroke_color"): context.set_line_width(default_get(args, "line_width")) context.set_source_rgb(*default_get(args, "stroke_color")) context.set_dash(*default_get(args, "dash")) context.stroke() elif func == "text": if args["text"] is None: continue context.set_source_rgb(*default_get(args, "stroke_color")) context.move_to(*args["botleft"]) context.set_font_size(1.5 * args["font_size"]) if numpy.array_equal(args["transform"], identity): context.show_text(unicode(args["text"])) else: context.save() matrix = cairo.Matrix(*args["transform"].T[:,:2].flatten()) context.transform(matrix) context.show_text(unicode(args["text"])) context.restore() elif func == "group": pass elif func in ["begin_region", "end_region"]: pass elif func == "arc": flat = list(args["center"]) + [args["radius"]] + list(args["angle"]) context.arc(*flat) else: getattr(context, func)(*args)
def font_change(factor=1.2): factor = float(factor) for ref in doc['selection']: for node, transform in ref['ref'].dfs(): if node.name == "text": node['font_size'] = default_get(node, "font_size") * factor
def toggle_ui_visible(): doc['custom_ui.visible'] = not default_get(doc['custom_ui'], 'visible')
def draw(self, layer="drawing", root=None): if root is None: doc, root_id = self.layers[layer] root = doc[root_id] context = self.contexts[layer] for func, args in flatten(root): if func == "stroke_and_fill": opacity = default_get(args, "opacity") if default_get(args, "fill_color"): context.set_source_rgba(*default_get(args, "fill_color"), alpha=opacity) if args.get("stroke_color") is not None: context.fill_preserve() else: context.fill() if default_get(args, "stroke_color"): context.set_line_width(default_get(args, "line_width")) context.set_source_rgba(*default_get(args, "stroke_color"), alpha=opacity) context.set_dash(*default_get(args, "dash")) context.stroke() elif func == "text": if args["text"] is None: continue opacity = default_get(args, "opacity") context.set_source_rgba(*default_get(args, "stroke_color"), alpha=opacity) context.move_to(*args["botleft"]) context.set_font_size(1.5 * args["font_size"]) if args.get("font_face"): context.select_font_face(args["font_face"], cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) else: context.set_font_face(None) if numpy.array_equal(args["transform"], identity): context.show_text(unicode(args["text"])) else: context.save() matrix = cairo.Matrix( *args["transform"].T[:, :2].flatten()) context.transform(matrix) context.show_text(unicode(args["text"])) context.restore() elif func == "image": # PNG only for the moment img = cairo.ImageSurface.create_from_png(args["filename"]) if numpy.array_equal(args["transform"], identity): context.set_source_surface(img, *args["topleft"]) context.paint() else: context.save() matrix = cairo.Matrix( *args["transform"].T[:, :2].flatten()) context.transform(matrix) context.set_source_surface(img, *args["topleft"]) context.paint() context.restore() elif func == "group": pass elif func in ["begin_region", "end_region"]: pass elif func == "arc": flat = list(args["center"]) + [args["radius"]] + list( args["angle"]) context.arc(*flat) else: getattr(context, func)(*args)
def _flatten(root, style={}, transform=identity, skip_transform=False): """ Flatten tree into drawing commands.""" assert(root.doc is not None) if not default_get(root, "visible"): return style = style.copy() for key in ["line_width", "stroke_color", "fill_color", "dash", "skip_points"]: if key in root: style[key] = root[key] if "opacity" in root: style["opacity"] = default_get(style, "opacity") * root["opacity"] if not skip_transform: transform = transform.dot(root.transform) if root.name in ["line", "curve", "arc", "path"]: if not default_get(root, "visible"): return children = root if root.name == "path" else [root] border = [] if children: yield ("begin_region", ()) if children[0].name == "arc": yield ("move_to", transformed(children[0]["center"], transform)) else: yield ("move_to", transformed(children[0]["start"], transform)) for child in children: for segment in flatten_seg(child, style = style, transform = transform): yield segment yield ("end_region", ()) yield ("stroke_and_fill", style) if not default_get(style, "skip_points"): for child in children: for grandchild in child: for elem in flatten(grandchild, style = style, transform = transform): yield elem elif root.name in ["clip"]: # Clipping needs to specify both the clipped region and # the clipped elements! # Maybe only allow clipped rectangles at first? yield ("begin_clipped", ()) for child in root["clip_path"]: for segment in flatten_seg(child, style = style, transform = transform): yield segment yield ("end_clip", ()) yield ("end_clipped", ()) elif root.name == "point": if default_get(style, "skip_points"): return matrix = numpy.array([[1, 0, root["value"][0]], [0, 1, root["value"][1]], [0, 0, 1]]) transform = transform.dot(matrix) for elem in flatten(root.doc[default_get(root, "icon")], transform=transform, style={"skip_points":True}): yield elem elif root.name == "text": if "value" in root: value = root["value"] elif "ref_id" in root: value = str(root.doc[root["ref_id"]][root["ref_param"]]) else: raise Exception('Text node with no value or ref_id: %s' % root) yield ("text", {"text": value, "transform": transform, "font_size": default_get(root, "font_size"), "font_face": default_get(root, "font_face"), "stroke_color": default_get(style, "stroke_color"), "botleft": transformed(root["botleft"], transform), "opacity": default_get(style, "opacity")}) elif root.name == "image": yield ("image", {"filename": root['filename'], "topleft": default_get(root, "topleft")['value'], "transform": transform}) elif root.name == "group": yield ("group", ()) if root.name in ["group", "text", "image"]: #if default_get(root, "visible"): for child in root: for elem in flatten(child, style = style, transform = transform): yield elem if default_get(root, "render"): for child in root["render"]: for elem in flatten(child, style = style, transform = transform): yield elem