class Graf(): def __init__(self, lines, container, style=None, **kwargs): if isinstance(container, Rect): self.container = DATPen().rect(container) else: self.container = container if style and isinstance(style, GrafStyle): self.style = style else: self.style = GrafStyle(**kwargs) self.lines = lines def lineRects(self): # which came first, the height or the width??? rects = [] leadings = [] box = self.container.getFrame() leftover = box for l in self.lines: box, leftover = leftover.divide(l.height(), "maxy", forcePixel=True) if self.style.leading < 0: # need to add pixels back to leftover leftover.h += abs(self.style.leading) else: leading, leftover = leftover.divide(self.style.leading, "maxy", forcePixel=True) leadings.append(leading) rects.append(box) return rects def width(self): if self.style.width > 0: return self.style.width else: return max([l.width() for l in self.lines]) def fit(self, width=None): rects = self.lineRects() for idx, l in enumerate(self.lines): if width: fw = width else: fw = rects[idx].w - self.style.xp l.fit(fw) return self def pens(self, align=False): rects = self.lineRects() pens = DATPenSet() 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 fpa(self, rect=None): return self.fit().pens().align(rect or self.container.getFrame())
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 __init__(self, lines, container, style=None, **kwargs): if isinstance(container, Rect): self.container = DATPen().rect(container) else: self.container = container if style and isinstance(style, GrafStyle): self.style = style else: self.style = GrafStyle(**kwargs) self.lines = lines
def shadow(self, clip=None, radius=10, alpha=0.3, color=Color.from_rgb(0,0,0,1)): if clip: cp = DATPen(fill=None).rect(clip) bp = db.BezierPath() cp.replay(bp) db.clipPath(bp) #elif self.rect: # cp = DATPen(fill=None).rect(self.rect).xor(self.dat) # bp = db.BezierPath() # cp.replay(bp) # db.clipPath(bp) db.shadow((0, 0), radius*3, list(color.with_alpha(alpha)))
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 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 run(self, render_pass, renderer_state): use_pool = True if use_pool: pool = AppKit.NSAutoreleasePool.alloc().init() try: db.newDrawing() if renderer_state.previewing: ps = renderer_state.preview_scale db.size(self.rect.w * ps, self.rect.h * ps) db.scale(ps, ps) DATPen().rect(self.rect).f(self.bg).db_drawPath() else: db.size(self.rect.w, self.rect.h) render_pass.fn(*render_pass.args) result = None if renderer_state.previewing: previews = (render_pass.output_path.parent / "_previews") previews.mkdir(exist_ok=True, parents=True) preview_frame = previews / render_pass.output_path.name db.saveImage(str(preview_frame)) result = preview_frame else: render_pass.output_path.parent.mkdir(exist_ok=True, parents=True) db.saveImage(str(render_pass.output_path)) result = render_pass.output_path db.endDrawing() finally: if use_pool: del pool return result
def _emptyPenWithAttrs(self): attrs = dict(fill=self.style.fill) if self.style.stroke: attrs["stroke"] = dict(color=self.style.stroke, weight=self.style.strokeWidth) dp = DATPen(**attrs) return dp
def stretcher_slnt(w, xy, angle, i, p): if abs(angle) in [90, 270]: return p x0, y0 = xy ra = math.radians(90 + angle) xdsc = x0 + (-250 - y0) / math.tan(ra) xasc = x0 + (1000 - y0) / math.tan(ra) np = (p.flatten(flatten) if flatten else p).nonlinear_transform(lambda x, y: (x if is_left( (xdsc, -250), (xasc, 1000), (x, y)) else x + w, y)) if debug: (np.record(DATPen().line([ (xdsc, -250), (xasc, 1000) ]).outline()).record(DATPen().moveTo( (x0 + 50 / 2, y0 + 50 / 2)).dots(radius=50))) return np
def stretcher_slnt(h, xy, angle, i, p): if abs(angle) in [90, 270]: return p x0, y0 = xy ra = math.radians(90 + angle) ydsc = y0 + (0 - x0) / math.tan(ra) yasc = y0 + (p.getFrame().point("E").x - x0) / math.tan(ra) p0 = (0, ydsc) p1 = (p.getFrame().point("E").x, yasc) np = (p.flatten(flatten) if flatten else p).nonlinear_transform( lambda x, y: (x, y if not is_left(p0, p1, (x, y)) else y + h)) if debug: (np.record(DATPen().line([p0, p1 ]).outline()).record(DATPen().moveTo( (x0 + 50 / 2, y0 + 50 / 2)).dots(radius=50))) return np
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 remove_blanklines(self, blank=None): if not blank and self.blankfill: blank = self.blankfill for line in self.pens: txt = reduce(lambda acc, p: p.data.get("txt", "") + acc, line, "") if txt == blank: line.pens = [DATPen()] return self
def stretcher(h, yp, i, p): np = (p.flatten(flatten) if flatten else p).nonlinear_transform( lambda x, y: (x, y if y < yp else y + h)) if align == "mdy": np.translate(0, -h / 2) elif align == "mxy": np.translate(0, -h) if debug: (np.record(DATPen().line([(0, yp), (p.getFrame().point("E").x, yp) ]).outline())) return np
def frame_waveform(self, fi, r, inc=1, pen=None): wave = pen or DATPen() samples = self.samples_for_frame(fi)[::inc] ww = r.w / len(samples) wh = r.h for idx, w in enumerate(samples): if idx == 0: wave.moveTo((r.x, w[0] * wh)) else: wave.lineTo((idx * ww, w[0] * wh)) wave.endPath() wave.f(None).s(1).sw(2) return wave
def ease(style, x): if style == "linear": return x, 0.5 e = eases.get(style) if e: return e().ease(x), 0.5 elif False: if style in easer_ufo: return curve_pos_and_speed(DATPen().glyph(easer_ufo[style]), x) else: raise Exception("No easing function with that mnemonic") else: raise Exception("No easing function with that mnemonic")
def scalePenToStyle(self, glyph, in_pen): s = self.scale() t = Transform() try: bs = self.style.baselineShift[idx] except: bs = self.style.baselineShift if callable(bs): t = t.translate(0, bs(idx)) else: try: t = t.translate(0, bs[idx]) except: try: t = t.translate(0, bs) except: pass t = t.scale(s) t = t.translate(glyph.frame.x / self.scale(), glyph.frame.y / self.scale()) #t = t.translate(0, bs) out_pen = DATPen() tp = TransformPen(out_pen, (t[0], t[1], t[2], t[3], t[4], t[5])) in_pen.replay(tp) if self.style.rotate: out_pen.rotate(self.style.rotate) # TODO this shouldn't be necessary if True: valid_values = [] for (move, pts) in out_pen.value: if move != "addComponent": valid_values.append((move, pts)) out_pen.value = valid_values return out_pen
def ease(style, x) -> [float, float]: """ Though available as a general-purpose function, this logic is usually accessed through something like the `.progress` function on an animation or timeable. Return two values — the first is the easing result at a given time x; the second is the tangent to that, if calculable (is not, atm, calculable for the mnemonics given) for reference, easing mnemonics: * cei = CubicEaseIn * ceo = CubicEaseOut * ceio = CubicEaseInOut * qei = QuadEaseIn * qeo = QuadEaseOut * qeio = QuadEaseInOut * eei = ExponentialEaseIn * eeo = ExponentialEaseOut * eeio = ExponentialEaseInOut * sei = SineEaseIn * seo = SineEaseOut * seio = SineEaseInOut * bei = BounceEaseIn * beo = BounceEaseOut * beio = BounceEaseInOut * eleo = ElasticEaseOut * elei = ElasticEaseIn, * elieo = ElasticEaseInOut """ if style == "linear": return x, 0.5 e = eases.get(style) if e: return e().ease(x), 0.5 elif False: if style in easer_ufo: return curve_pos_and_speed(DATPen().glyph(easer_ufo[style]), x) else: raise Exception("No easing function with that mnemonic") else: raise Exception("No easing function with that mnemonic")
def stretcher(w, xp, i, p): np = (p.flatten(flatten) if flatten else p).nonlinear_transform( lambda x, y: (x if x < xp else x + w, y)) if debug: (np.record(DATPen().line([(xp, -250), (xp, 1000)]).outline())) return np