def draw(self, scale=0.01, cm=False): self.dat.scale(scale, center=False) self.page = self.page.scale(scale) b = self.dat.bounds() limits = Rect(0, 0, 11, 8.5) if cm: limits = limits.scale(2.54) if b.x >= 0 and b.y >= 0 and b.w <= limits.w and b.h <= limits.h: print("Drawing!") else: print("Too big!", b) return False ad = axidraw.AxiDraw() ad.interactive() ad.options.units = 1 if cm else 0 ad.options.speed_pendown = 50 ad.options.speed_penup = 50 ad.connect() ad.penup() self.ad = ad tp = TransformPen(self, (1, 0, 0, -1, 0, self.page.h)) self.dat.replay(tp) time.sleep(MOVE_DELAY) ad.penup() ad.moveto(0, 0) ad.disconnect()
def __init__(self, glyphName, width=500, **kwargs): r = Rect(kwargs.get("rect", Rect(1000, 1000))) kwargs.pop("rect", None) self.width = width self.body = r.take(750, "mdy").take(self.width, "mdx") self.glyphName = glyphName super().__init__(rect=r, **kwargs)
def clip_timeline(self, fi, far, sw=30, sh=50, font_name=None): seq = DATPens() seq += (DATPen() .rect(Rect(0, 0, (self.duration * sw), sh)).f(bw(0.5, 0.35))) for tidx, t in enumerate(self.tracks): for cgidx, cg in enumerate(t.clip_groups): for cidx, c in enumerate(cg.clips): r = Rect(c.start*sw, sh*tidx, (c.duration*sw)-2, sh) current = c.start-25 <= fi <= c.end+25 if current: seq.append(DATPens([ (DATPen() .rect(r) .f(hsl(cgidx*0.4+tidx*0.2, 0.75).lighter(0.2))), (DATText(c.input_text, Style(font_name, 24, load_font=0, fill=bw(0, 1 if tidx == self.workarea_track else 0.5)), r.inset(3, 8)))])) on_cut = fi in self.jumps() seq.translate(far.w/2 - fi*sw, 10) seq.append(DATPen() .rect(far.take(sh*len(self.tracks)+20, "mny").take(6 if on_cut else 2, "mdx")) .f(hsl(0.9, 1) if on_cut else hsl(0.8, 0.5))) return seq
def draw(self, scale=0.01, dry=True, cm=False): if dry: with viewer() as v: dp = DATPen().record(self.dat).attr(fill=None, stroke=0) v.send(SVGPen.Composite([dp], self.page), self.page) else: self.dat.scale(scale, center=False) self.page = self.page.scale(scale) b = self.dat.bounds() limits = Rect(0, 0, 11, 8.5) if cm: limits = limits.scale(2.54) if b.x >= 0 and b.y >= 0 and b.w <= limits.w and b.h <= limits.h: print("Drawing!") else: print("Too big!", b) return False ad = axidraw.AxiDraw() ad.interactive() ad.options.units = 1 if cm else 0 ad.options.speed_pendown = 50 ad.options.speed_penup = 50 ad.connect() ad.penup() self.ad = ad tp = TransformPen(self, (1, 0, 0, -1, 0, self.page.h)) self.dat.replay(tp) time.sleep(MOVE_DELAY) ad.penup() ad.moveto(0, 0) ad.disconnect()
def __init__(self, rect=(1080, 1080), duration=10, storyboard=[0], timeline: Timeline = None, audio=None, **kwargs): super().__init__(**kwargs) self.rect = Rect(rect) self.r = self.rect self.audio = audio if self.audio and sf: self.wavfile = Wavfile(audio) else: self.wavfile = None self.start = 0 self.end = duration self.storyboard = storyboard if timeline: self.timeline = timeline self.t = timeline self.start = timeline.start self.end = timeline.end if self.storyboard != [0] and timeline.storyboard == [0]: pass else: self.storyboard = timeline.storyboard.copy() else: self.timeline = Timeline(duration)
def bounds(self): """Calculate the bounds of this shape; mostly for internal use.""" try: cbp = BoundsPen(None) self.replay(cbp) mnx, mny, mxx, mxy = cbp.bounds return Rect((mnx, mny, mxx - mnx, mxy - mny)) except: return Rect(0, 0, 0, 0)
def __init__(self, rect=(1080, 1080), duration=10, storyboard=[0], timeline: Timeline = None, **kwargs): super().__init__(**kwargs) self.rect = Rect(rect) self.r = self.rect self.start = 0 self.end = duration self.duration = duration self.storyboard = storyboard if timeline: self.timeline = timeline self.t = timeline self.start = timeline.start self.end = timeline.end self.duration = timeline.duration if self.storyboard != [0] and timeline.storyboard == [0]: pass else: self.storyboard = timeline.storyboard else: self.timeline = None
def __init__(self, rect=(1080, 1080), bg="whitesmoke", hide=False, fmt="png", rasterizer=None, prefix=None, dst=None, custom_folder=None, postfn=None, watch=[], layers=[], ui_callback=None): self.hide = hide self.rect = Rect(rect) self.bg = normalize_color(bg) self.fmt = fmt self.prefix = prefix self.dst = Path(dst).expanduser().resolve() if dst else None self.custom_folder = custom_folder self.postfn = postfn self.ui_callback = ui_callback self.watch = [Path(w).expanduser().resolve() for w in watch] self.rasterizer = rasterizer self.layers = layers if not rasterizer: if self.fmt == "svg": self.rasterizer = "svg" else: system = platform.system() if system == "Darwin": self.rasterizer = "drawbot" else: self.rasterizer = "cairo"
def __init__(self, rect=(1080, 1080), scale=1, **kwargs): if not db: raise Exception("DrawBot not installed!") super().__init__(rect=Rect(rect).scale(scale), rasterizer="drawbot", **kwargs) self.self_rasterizing = True
def getFrame(self, th=False, tv=False): """For internal use; creates a frame based on calculated bounds.""" if self.frame: if (th or tv) and len(self.value) > 0: f = self.frame b = self.bounds() if th and tv: return b elif th: return Rect(b.x, f.y, b.w, f.h) else: return Rect(f.x, b.y, f.w, b.h) else: return self.frame else: return self.bounds()
def roundedRect(self, rect, hr, vr): """Rounded rectangle primitive""" l, b, w, h = Rect(rect) r, t = l + w, b + h K = 4 * (math.sqrt(2) - 1) / 3 circle = hr == 0.5 and vr == 0.5 if hr <= 0.5: hr = w * hr if vr <= 0.5: vr = h * vr self.moveTo((l + hr, b)) if not circle: self.lineTo((r - hr, b)) self.curveTo((r + hr * (K - 1), b), (r, b + vr * (1 - K)), (r, b + vr)) if not circle: self.lineTo((r, t - vr)) self.curveTo((r, t - vr * (1 - K)), (r - hr * (1 - K), t), (r - hr, t)) if not circle: self.lineTo((l + hr, t)) self.curveTo((l + hr * (1 - K), t), (l, t - vr * (1 - K)), (l, t - vr)) if not circle: self.lineTo((l, b + vr)) self.curveTo((l, b + vr * (1 - K)), (l + hr * (1 - K), b), (l + hr, b)) self.closePath() return self
def svg(self, file, gid, rect=Rect(0, 0, 0, 100)): """WIP; attempt to read an svg file into the pen""" from bs4 import BeautifulSoup with open(file, "r") as f: soup = BeautifulSoup(f.read(), features="lxml") tp = TransformPen(self, (1, 0, 0, -1, 0, rect.h)) for path in soup.find(id=gid).find_all("path"): parse_path(path.get("d"), tp) return self
def dots(self, radius=4): """(Necessary?) Create circles at moveTo commands""" dp = DATPen() for t, pts in self.value: if t == "moveTo": x, y = pts[0] dp.oval(Rect((x - radius, y - radius, radius, radius))) self.value = dp.value return self
def rect(self, rect, *args): """Rectangle primitive — `moveTo/lineTo/lineTo/lineTo/closePath`""" if isinstance(rect, Rect): self.moveTo(rect.point("SW").xy()) self.lineTo(rect.point("SE").xy()) self.lineTo(rect.point("NE").xy()) self.lineTo(rect.point("NW").xy()) self.closePath() elif isinstance(rect, Number): if len(args) == 1: return self.rect(Rect(rect, args[0])) else: return self.rect(Rect(rect, args[0], args[1], args[2])) elif isinstance(rect[0], Rect): for r in rect: self.rect(r) else: self.rect(Rect(rect)) return self
def getFrame(self, th=False, tv=False): if self.frame and (th == False and tv == False): return self.frame else: try: union = self.pens[0].getFrame(th=th, tv=tv) for p in self.pens[1:]: union = union.union(p.getFrame(th=th, tv=tv)) return union except Exception as e: return Rect(0, 0, 0, 0)
def img(self, src=None, rect=Rect(0, 0, 500, 500), pattern=True, opacity=1.0): """Get/set an image fill""" if src: return self.attr(image=dict( src=src, rect=rect, pattern=pattern, opacity=opacity)) else: return self.attr(field="image")
def pens(self, align=False): rects = self.lineRects() pens = DATPens() for idx, l in enumerate(self.lines): r = rects[idx] dps = l.pens().translate(r.x, r.y) # r.x dps.addFrame(Rect(r.x, r.y, r.w, r.h)) #if align: # dps.container = Rect(0, r.y, r.w, r.h) # dps.align(dps.container, x=self.style.x, y=None) pens.append(dps) return pens
def image(self, src=None, opacity=1, rect=None, rotate=0, repeating=False, scale=True): bounds = self.dat.bounds() src = str(src) if not rect: rect = bounds try: img_w, img_h = db.imageSize(src) except ValueError: print("DrawBotPen: No image") return x = bounds.x y = bounds.y if repeating: x_count = bounds.w / rect.w y_count = bounds.h / rect.h else: x_count = 1 y_count = 1 _x = 0 _y = 0 while x <= (bounds.w+bounds.x) and _x < x_count: _x += 1 while y <= (bounds.h+bounds.y) and _y < y_count: _y += 1 with db.savedState(): r = Rect(x, y, rect.w, rect.h) #db.fill(1, 0, 0.5, 0.05) #db.oval(*r) if scale == True: db.scale(rect.w/img_w, center=r.point("SW")) elif scale: try: db.scale(scale[0], scale[1], center=r.point("SW")) except TypeError: db.scale(scale, center=r.point("SW")) db.rotate(rotate) db.image(src, (r.x, r.y), alpha=opacity) y += rect.h y = 0 x += rect.w
def skeleton(self, scale=1, returnSet=False): """Vector-editing visualization""" dp = DATPen() moveTo = DATPen(fill=("random", 0.5)) lineTo = DATPen(fill=("random", 0.5)) curveTo_on = DATPen(fill=("random", 0.5)) curveTo_off = DATPen(fill=("random", 0.25)) curveTo_bars = DATPen(fill=None, stroke=dict(color=("random", 0.5), weight=1 * scale)) for idx, (t, pts) in enumerate(self.value): if t == "moveTo": r = 12 * scale x, y = pts[0] moveTo.rect(Rect((x - r / 2, y - r / 2, r, r))) elif t == "curveTo": r = 6 * scale x, y = pts[-1] curveTo_on.oval(Rect((x - r / 2, y - r / 2, r, r))) r = 4 * scale x, y = pts[1] curveTo_off.oval(Rect((x - r / 2, y - r / 2, r, r))) x, y = pts[0] curveTo_off.oval(Rect((x - r / 2, y - r / 2, r, r))) p0 = self.value[idx - 1][-1][-1] curveTo_bars.line((p0, pts[0])) curveTo_bars.line((pts[1], pts[2])) elif t == "lineTo": r = 6 * scale x, y = pts[0] lineTo.rect(Rect((x - r / 2, y - r / 2, r, r))) all_pens = [moveTo, lineTo, curveTo_on, curveTo_off, curveTo_bars] if returnSet: return all_pens else: for _dp in all_pens: dp.record(_dp) self.value = dp.value return self
def resetGlyphRun(self): self.glyphs = self.style.font.font.getGlyphRunFromTextInfo( self.text_info, addDrawings=False, features=self.features, varLocation=self.variations) #self.glyphs = self.style.font.font.getGlyphRun(self.text, features=self.features, varLocation=self.variations) x = 0 for glyph in self.glyphs: glyph.frame = Rect(x + glyph.dx, glyph.dy, glyph.ax, self.style.capHeight) x += glyph.ax self.getGlyphFrames()
def contactsheet(r: Rect): _pngs = list(sorted(self.output_folder.glob("*.png"))) if sliced: pngs = _pngs[sl] else: pngs = [p for i, p in enumerate(_pngs) if i in sl] dps = DATPens() dps += DATPen().rect(r).f(self.bg) for idx, g in enumerate(r.grid(columns=gx, rows=gy)): if idx < len(pngs): dps += DATPen().rect(g).f(None).img(pngs[idx], g, pattern=False) return dps
def pixellate(self, rect, increment=50, inset=0): """WIP""" x = -200 y = -200 dp = DATPen() while x < 1000: while y < 1000: #print(x, y) pen = PointInsidePen(None, (x, y)) self.replay(pen) isInside = pen.getResult() if isInside: dp.rect(Rect(x, y, increment, increment).inset(inset)) y += increment x += increment y = -200 self.value = dp.value return self
def get_image_rect(src): w, h = db.imageSize(str(src)) return Rect(0, 0, w, h)
def __init__(self, rect=(1080, 1080), bg="whitesmoke", fmt="png", name=None, rasterizer=None, prefix=None, dst=None, custom_folder=None, postfn=None, watch=[], watch_restarts=[], watch_soft=[], solo=False, rstate=False, preview_only=False, direct_draw=False, clip=False, composites=False, bg_render=False, style="default", viewBox=True, layer=False): """Base configuration for a renderable function""" self.rect = Rect(rect) self.bg = normalize_color(bg) self.fmt = fmt self.prefix = prefix self.dst = Path(dst).expanduser().resolve() if dst else None self.custom_folder = custom_folder self.postfn = postfn self.last_passes = [] self.last_result = None self.style = style self.composites = composites self.watch = [] for w in watch: self.add_watchee(w) self.watch_restarts = [] for w in watch_restarts: self.watch_restarts.append(self.add_watchee(w, "restart")) self.watch_soft = [] for w in watch_soft: self.watch_soft.append(self.add_watchee(w, "soft")) self.name = name self.codepath = None self.rasterizer = rasterizer self.self_rasterizing = False self.hidden = solo == -1 self.solo = solo self.preview_only = preview_only self.rstate = rstate self.clip = clip self.viewBox = viewBox self.direct_draw = direct_draw self.bg_render = bg_render self.layer = layer if self.layer: self.bg = normalize_color(None) if not rasterizer: if self.fmt == "svg": self.rasterizer = "svg" elif self.fmt == "pickle": self.rasterizer = "pickle" else: self.rasterizer = "skia"
class animation(renderable, Timeable): """ Base class for any frame-wise animation animatable by Coldtype """ def __init__(self, rect=(1080, 1080), duration=10, storyboard=[0], timeline: Timeline = None, audio=None, **kwargs): super().__init__(**kwargs) self.rect = Rect(rect) self.r = self.rect self.audio = audio if self.audio and sf: self.wavfile = Wavfile(audio) else: self.wavfile = None self.start = 0 self.end = duration self.storyboard = storyboard if timeline: self.timeline = timeline self.t = timeline self.start = timeline.start self.end = timeline.end if self.storyboard != [0] and timeline.storyboard == [0]: pass else: self.storyboard = timeline.storyboard.copy() else: self.timeline = Timeline(duration) def __call__(self, func): res = super().__call__(func) self.prefix = self.name + "_" return res def folder(self, filepath): return filepath.stem + "/" + self.name # TODO necessary? def all_frames(self): return list(range(0, self.duration)) def _active_frames(self, renderer_state): frames = [] for f in renderer_state.get_frame_offsets(self.name): frames.append(f % self.duration) return frames def active_frames(self, action, renderer_state, indices): frames = self._active_frames(renderer_state) if action == Action.RenderAll: frames = self.all_frames() elif action in [Action.PreviewIndices, Action.RenderIndices]: frames = indices elif action in [Action.RenderWorkarea]: if self.timeline: try: frames = self.workarea() except: frames = self.all_frames() #if hasattr(self.timeline, "find_workarea"): # frames = self.timeline.find_workarea() return frames def workarea(self): return list(self.timeline.workareas[0]) def jump(self, current, direction): c = current % self.duration js = self.timeline.jumps() if direction < 0: for j in reversed(js): if c > j: return j else: for j in js: if c < j: return j return current def pass_suffix(self, index): return "{:04d}".format(index) def passes(self, action, renderer_state, indices=[]): frames = self.active_frames(action, renderer_state, indices) return [ RenderPass(self, self.pass_suffix(i), [Frame(i, self)]) for i in frames ] def runpost(self, result, render_pass, renderer_state): res = super().runpost(result, render_pass, renderer_state) if Overlay.Info in renderer_state.overlays: t = self.rect.take(50, "mxy") frame: Frame = render_pass.args[0] wave = DATPen() if self.audio and sf: wave = (self.wavfile.frame_waveform( frame.i, self.rect.inset(0, 300), 20).translate(0, self.rect.h / 2).s(1).sw(5)) return DATPens([ wave, res, DATPen().rect(t).f(bw(0, 0.75)), DATText(f"{frame.i} / {self.duration}", Style("Times", 42, load_font=0, fill=bw(1)), t.inset(10)) ]) return res def package(self, filepath, output_folder): pass def contactsheet(self, gx, sl=slice(0, None, None)): try: sliced = True start, stop, step = sl.indices(self.duration) duration = (stop - start) // step except AttributeError: # indices storyboard duration = len(sl) sliced = False ar = self.rect gy = math.ceil(duration / gx) @renderable(rect=(ar.w * gx, ar.h * gy), bg=self.bg, name=self.name + "_contactsheet") def contactsheet(r: Rect): _pngs = list(sorted(self.output_folder.glob("*.png"))) if sliced: pngs = _pngs[sl] else: pngs = [p for i, p in enumerate(_pngs) if i in sl] dps = DATPens() dps += DATPen().rect(r).f(self.bg) for idx, g in enumerate(r.grid(columns=gx, rows=gy)): if idx < len(pngs): dps += DATPen().rect(g).f(None).img(pngs[idx], g, pattern=False) return dps return contactsheet
class renderable(): """ Base class for any content renderable by Coldtype """ def __init__(self, rect=(1080, 1080), bg="whitesmoke", fmt="png", name=None, rasterizer=None, prefix=None, dst=None, custom_folder=None, postfn=None, watch=[], watch_restarts=[], watch_soft=[], solo=False, rstate=False, preview_only=False, direct_draw=False, clip=False, composites=False, bg_render=False, style="default", viewBox=True, layer=False): """Base configuration for a renderable function""" self.rect = Rect(rect) self.bg = normalize_color(bg) self.fmt = fmt self.prefix = prefix self.dst = Path(dst).expanduser().resolve() if dst else None self.custom_folder = custom_folder self.postfn = postfn self.last_passes = [] self.last_result = None self.style = style self.composites = composites self.watch = [] for w in watch: self.add_watchee(w) self.watch_restarts = [] for w in watch_restarts: self.watch_restarts.append(self.add_watchee(w, "restart")) self.watch_soft = [] for w in watch_soft: self.watch_soft.append(self.add_watchee(w, "soft")) self.name = name self.codepath = None self.rasterizer = rasterizer self.self_rasterizing = False self.hidden = solo == -1 self.solo = solo self.preview_only = preview_only self.rstate = rstate self.clip = clip self.viewBox = viewBox self.direct_draw = direct_draw self.bg_render = bg_render self.layer = layer if self.layer: self.bg = normalize_color(None) if not rasterizer: if self.fmt == "svg": self.rasterizer = "svg" elif self.fmt == "pickle": self.rasterizer = "pickle" else: self.rasterizer = "skia" def add_watchee(self, w, flag=None): try: pw = Path(w).expanduser().resolve() if not pw.exists(): print(w, "<<< does not exist (cannot be watched)") else: self.watch.append([pw, flag]) return pw except TypeError: if isinstance(w, Font): self.watch.append([w, flag]) else: raise Exception( "Can only watch path strings, Paths, and Fonts") def __call__(self, func): self.func = func if not self.name: self.name = self.func.__name__ return self def folder(self, filepath): return "" def pass_suffix(self): return self.name def passes(self, action, renderer_state, indices=[]): return [RenderPass(self, self.pass_suffix(), [self.rect])] def package(self, filepath, output_folder): pass def run(self, render_pass, renderer_state): if self.rstate: return render_pass.fn(*render_pass.args, renderer_state) else: return render_pass.fn(*render_pass.args) def runpost(self, result, render_pass, renderer_state): if self.postfn: return self.postfn(self, result) else: return result def draw_preview(self, scale, canvas: skia.Canvas, rect, result, render_pass): sr = self.rect.scale(scale, "mnx", "mxx") SkiaPen.CompositeToCanvas(result, sr, canvas, scale, style=self.style) def hide(self): self.hidden = True return self def show(self): self.hidden = False return self def normalize_result(self, pens): if not pens: return DATPens() elif hasattr(pens, "pens"): return pens elif isinstance(pens, DATPen): return DATPens([pens]) elif isinstance(pens, DATText): return DATPens([pens]) elif not isinstance(pens, DATPens): return DATPens(pens) else: return pens