def __init__(self, *argv): if len(argv) == 1: if isinstance(argv[0], self.__class__): # Clone self.p0, self.p1, self.p2, self.p3 = argv[0].p0, argv[ 0].p1, argv[0].p2, argv[0].p3 if isMultiInstance(argv[0], (tuple, list)): self.p0, self.p1, self.p2, self.p3 = [ Point(item) for item in argv[0] ] if len(argv) == 4: if isMultiInstance(argv, (tuple, list)): self.p0, self.p1, self.p2, self.p3 = [ Point(item) for item in argv ] if isMultiInstance(argv, Point): self.p0, self.p1, self.p2, self.p3 = argv if len(argv) == 8: if isMultiInstance(argv, (float, int)): self.p0, self.p1, self.p2, self.p3 = [ Point(argv[i], argv[i + 1]) for i in range(len(argv) - 1) ] self.transform = Transform()
def filterClosePoly(point_list, in_reverse=False, grow_value=0): min_x, max_x, min_y, max_y = GlyphSampler.getBounds(point_list) x = min_x - grow_value if not in_reverse else max_x + grow_value point_list.insert(0, Point(x, min_y)) point_list.append(Point(x, max_y)) return point_list
def intersect_line(self, other_line): '''Find intersection point (X, Y) for two lines. Returns (None, None) if lines do not intersect.''' diff_x = Point(self.p0.x - self.p1.x, other_line.p0.x - other_line.p1.x) diff_y = Point(self.p0.y - self.p1.y, other_line.p0.y - other_line.p1.y) div = diff_x | diff_y if div == 0: return (None, None) d = Point(self.p0 | self.p1, other_line.p0 | other_line.p1) x = (d | diff_x) / div y = (d | diff_y) / div return (x, y)
def slant_shift(self, shift_x, shift_y, angle): '''Slanted move - move a node (in inclined space) according to Y coordinate slanted at given angle. Arguments: shift_x, shift_y (float) angle (float): Angle in degrees ''' # - Init new_point = Point((self.x + shift_x, self.y)) new_point.angle = angle # - Calculate & set new_x = new_point.solve_width(new_point.y + shift_y) self.smart_reloc(new_x, self.y + shift_y)
def __init__(self, *args, **kwargs): super(Node, self).__init__(*args, **kwargs) self.parent = kwargs.pop('parent', None) # - Basics if len(args) == 1: if isinstance(args[0], self.__class__): # Clone self.x, self.y = args[0].x, args[0].y if isinstance(args[0], (tuple, list)): self.x, self.y = args[0] elif len(args) == 2: if isMultiInstance(args, (float, int)): self.x, self.y = float(args[0]), float(args[1]) else: self.x, self.y = 0., 0. self.angle = kwargs.pop('angle', 0) self.transform = kwargs.pop('transform', Transform()) self.complex_math = kwargs.pop('complex', True) self.weight = Point(kwargs.pop('weight', (0.,0.))) # - Metadata if not kwargs.pop('proxy', False): # Initialize in proxy mode self.type = kwargs.pop('type', node_types['on']) self.name = kwargs.pop('name', '') self.identifier = kwargs.pop('identifier', False) self.smooth = kwargs.pop('smooth', False) self.selected = kwargs.pop('selected', False) self.g2 = kwargs.pop('g2', False)
def lerp_shift(self, delta_x, delta_y): '''Interpolated shift aka Interpolated Nudge. Arguments: shift_x, shift_y (float) ''' if self.is_on: # - Init shift = Point(delta_x, delta_y) curr_segmet_nodes, curr_segment = self.segment_nodes, self.segment prev_segment_nodes, prev_segment = self.prev_on.segment_nodes, self.prev_on.segment # - Process segments if isinstance(curr_segment, CubicBezier): new_curr = curr_segment.lerp_first(shift) for node, point in zip(curr_segmet_nodes, new_curr.tuple): node.smart_reloc(*point) # - Process segments if isinstance(prev_segment, CubicBezier): new_prev = prev_segment.lerp_last(shift) for node, point in zip(prev_segment_nodes, new_prev.tuple): node.smart_reloc(*point) if isinstance(curr_segment, Line) and isinstance(prev_segment, Line): self.smartShift(*shift.tuple)
def __init__(self, *argv): if len(argv) == 1: if isinstance(argv[0], self.__class__): # Clone self.p0, self.p1 = argv[0].p0, argv[0].p1 if isMultiInstance(argv[0], (tuple, list)): self.p0, self.p1 = [Point(item) for item in argv[0]] if len(argv) > 1: ''' if isMultiInstance(argv[0], (tuple, list)): self.p0, self.p1 = Point(argv[0]), Point(argv[1]) ''' if isMultiInstance(argv, Point): self.p0, self.p1 = argv if isMultiInstance(argv, (tuple, list)): self.p0, self.p1 = Point(argv[0]), Point(argv[1]) if isMultiInstance(argv, (float, int)): self.p0, self.p1 = Point(argv[0], argv[1]), Point(argv[2], argv[3]) self.transform = Transform()
def setProportion(pointA, pointB, prop): # - Set proportions according to Edaurdo Tunni sign = lambda x: (1, -1)[x < 0] # Helper function xDiff = max(pointA.x, pointB.x) - min(pointA.x, pointB.x) yDiff = max(pointA.y, pointB.y) - min(pointA.y, pointB.y) xEnd = pointA.x + xDiff * prop * sign(pointB.x - pointA.x) yEnd = pointA.y + yDiff * prop * sign(pointB.y - pointA.y) return Point(xEnd, yEnd)
def solve_extremes(self): '''Finds curve extremes and returns [(extreme_01_x, extreme_01_y, extreme_01_t)...(extreme_n_x, extreme_n_y, extreme_n_t)]''' tvalues, points = [], [] x0, y0 = self.p0.x, self.p0.y x1, y1 = self.p1.x, self.p1.y x2, y2 = self.p2.x, self.p2.y x3, y3 = self.p3.x, self.p3.y for i in range(0, 2): if i == 0: b = float(6 * x0 - 12 * x1 + 6 * x2) a = float(-3 * x0 + 9 * x1 - 9 * x2 + 3 * x3) c = float(3 * x1 - 3 * x0) else: b = float(6 * y0 - 12 * y1 + 6 * y2) a = float(-3 * y0 + 9 * y1 - 9 * y2 + 3 * y3) c = float(3 * y1 - 3 * y0) if abs(a) < 1e-12: # Numerical robustness if abs(b) < 1e-12: # Numerical robustness continue t = -c / b if 0 < t and t < 1: tvalues.append(t) continue b2ac = float(b * b - 4 * c * a) if b2ac < 0: continue else: sqrtb2ac = math.sqrt(b2ac) t1 = (-b + sqrtb2ac) / (2 * a) if 0 < t1 and t1 < 1: tvalues.append(t1) t2 = (-b - sqrtb2ac) / (2 * a) if 0 < t2 and t2 < 1: tvalues.append(t2) for j in range(0, len(tvalues)): t = tvalues[j] mt = 1 - t x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + ( 3 * mt * t * t * x2) + (t * t * t * x3) y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + ( 3 * mt * t * t * y2) + (t * t * t * y3) points.append((Point(x, y), t)) return points
def getCrossing(p0, p1, p2, p3): # - Init diffA = p1 - p0 # p1.x - p0.x, p1.y - p0.y prodA = p1 & Point(p0.y, -p0.x) # p1.x * p0.y - p0.x * p1.y diffB = p3 - p2 prodB = p3 & Point(p2.y, -p2.x) # - Get intersection det = diffA & Point( diffB.y, -diffB.x) # diffA.x * diffB.y - diffB.x * diffA.y x = diffB.x * prodA - diffA.x * prodB y = diffB.y * prodA - diffA.y * prodB try: return Point(x / det, y / det) except ZeroDivisionError: return practicalInfinity
def lerp_to(self, entity, time): '''Perform linear interpolation Args: entity -> Node or Point : Object to make delta to time(tx, ty) -> tuple((float, float) : Interpolation times (anisotropic X, Y) Returns: None ''' tx, ty = time self.point = Point(lerp(self.x, entity.x, tx), lerp(self.y, entity.y, ty))
def func(scale=(1., 1.), time=(0., 0.), transalte=(0., 0.), angle=0., compensate=(0., 0.)): for idx in range(len(self.nodes)): self.nodes[idx].point = Point( adaptive_scale( (node_array[idx][0], node_array[idx][1]), scale, transalte, time, compensate, angle, (node_array[idx][2][0], node_array[idx][3][0], node_array[idx][2][1], node_array[idx][3][1])))
def solve_handle_distance_from_base(self, ratio=(.5, .5)): '''Finds new handle positions for given ratio from base points.''' from typerig.core.func.geometry import line_intersect handle_intersection = Point(line_intersect(*self.tuple)) hl0i = Line(self.p0, handle_intersection) hl1i = Line(self.p3, handle_intersection) new_p1 = hl0i.solve_point(ratio[0]) new_p2 = hl1i.solve_point(ratio[1]) return self.__class__(self.p0.tuple, new_p1.tuple, new_p2.tuple, self.p3.tuple)
def delta_to(self, other, scale=(1.,1.), time=(0.,0.), transalte=(0.,0.), angle=0., compensate=(0.,0.)): '''Perform adaptive scaling by keeping the stem/stroke weights Args: other -> Node: Object to make delta to scale(sx, sy) -> tuple((float, float) : Scale factors (X, Y) time(tx, ty) -> tuple((float, float) : Interpolation times (anisotropic X, Y) translate(dx, dy) -> tuple((float, float) : Translate values (X, Y) angle -> (radians) : Angle of sharing (for italic designs) compensate(cx, cy) -> tuple((float, float) : Compensation factor 0.0 (no compensation) to 1.0 (full compensation) (X,Y) Returns: None ''' self.point = Point(adaptive_scale(((self.x, self.y), (other.x, other.y)), scale, transalte, time, compensate, angle, (self.weight.x, self.weight.y, other.weight.x, other.weight.y)))
def filterBandPass(point_list, cutout_depth=(10000, 10000), in_reverse=False): min_x, max_x, min_y, max_y = GlyphSampler.getBounds(point_list) cutout_depth_x, cutout_depth_y = cutout_depth return_list = [] cutout_value_x = [min_x, max_x ][in_reverse] + cutout_depth_x * [1, -1][in_reverse] cutout_value_y = [min_y, max_y ][in_reverse] + cutout_depth_y * [1, -1][in_reverse] for p in point_list: px, py = p.tuple if [px > cutout_value_x, px < cutout_value_x][in_reverse]: px = cutout_value_x if [py > cutout_value_y, py < cutout_value_y][in_reverse]: py = cutout_value_y return_list.append(Point(px, py)) return return_list
def get_handle_length(self): '''Returns handle length and radii from base points.''' from typerig.core.func.geometry import line_intersect hl0 = Line(self.p0, self.p1) hl1 = Line(self.p2, self.p3) handle_intersection = Point(line_intersect(*self.tuple)) hl0i = Line(self.p0, handle_intersection) hl1i = Line(self.p3, handle_intersection) radius_0 = hl0i.length if not math.isnan(hl0i.length) else 0. radius_1 = hl1i.length if not math.isnan(hl1i.length) else 0. handle_0 = hl0.length if not math.isnan(hl0.length) else 0. handle_1 = hl0.length if not math.isnan(hl1.length) else 0. #ratio_0 = ratfrac(hl0.length, radius_0, 1.) #ratio_1 = ratfrac(hl1.length, radius_1, 1.) return (radius_0, handle_0), (radius_1, handle_1)
def intersect_line(self, other_line, projection=False): '''Find intersection point (X, Y) for two lines. Returns Void() point if lines do not intersect.''' diff_x = Point(self.p0.x - self.p1.x, other_line.p0.x - other_line.p1.x) diff_y = Point(self.p0.y - self.p1.y, other_line.p0.y - other_line.p1.y) div = diff_x | diff_y if div == 0: return Void() # (None, None) d = Point(self.p0 | self.p1, other_line.p0 | other_line.p1) x = (d | diff_x) / div y = (d | diff_y) / div if projection: return Point(x, y) else: if self.hasPoint(Point(x, y)) and other_line.hasPoint(Point(x, y)): return Point(x, y) return Void() # (None, None)
def max_point(self): return Point(self.xmax, self.ymax)
def min_point(self): return Point(self.x, self.y)
def reloc(self, new_x, new_y): '''Relocate the node to new coordinates''' self.point = Point(new_x, new_y)
w0 = max(a0, key=lambda i: i[0])[0] - min(a0, key=lambda i: i[0])[0] w1 = max(a0, key=lambda i: i[1])[1] - min(a0, key=lambda i: i[1])[1] h0 = max(a1, key=lambda i: i[0])[0] - min(a0, key=lambda i: i[0])[0] h1 = max(a1, key=lambda i: i[1])[1] - min(a0, key=lambda i: i[1])[1] sx, sy = utils.adjuster(((w0, w1), (h0, h1)), scale_or_dimension, (ntx, nty), (dx, dy), (a0[0][2], a0[0][3], a1[0][2], a1[0][3])) result = map( lambda arr: self.__delta_scale(arr[0], arr[1], ntx, nty, sx, sy, cx, cy, dx, dy, i), process_array) return result if __name__ == '__main__': arr = PointArray([Point(10, 10), Point(740, 570), Point(70, 50)]) points = [[(10, 10), (20, 20), (60, 60)], [(30, 30), (40, 40), (70, 70)], [(50, 50), (60, 60), (80, 80)]] stems = [[(10, 20)], [(30, 50)], [(80, 90)]] arr_lerp = DeltaArray(points) a = DeltaScale(points, stems) b = DeltaScale(a) #print(a.scale_by_time((1,1), (1,3), (1.0, 1.0), (0,0), 0)) #print(a.scale_by_stem((40,25), (1,3), (1.0, 1.0), (0,0), 0)) print(a.x)
def lerp_xy(self, time_x, time_y) : return Point(self.p0.x * (1. - time_x) + self.p1.x * time_x, self.p0.y * (1 - time_y) + self.p1.y * time_y)
class Line(object): def __init__(self, *argv): if len(argv): if isinstance(argv[0], self.__class__): # Clone self.p0, self.p1 = argv[0].p0, argv[0].p1 if isMultiInstance(argv, Point): self.p0, self.p1 = argv if isMultiInstance(argv, (tuple, list)): self.p0, self.p1 = Point(argv[0]), Point(argv[1]) if isMultiInstance(argv, (float, int)): self.p0, self.p1 = Point(argv[0], argv[1]), Point(argv[2], argv[3]) self.transform = Transform() def __add__(self, other): return self.__class__(self.p0 + other, self.p1 + other) def __sub__(self, other): return self.__class__(self.p0 - other, self.p1 - other) def __mul__(self, other): return self.__class__(self.p0 * other, self.p1 * other) __rmul__ = __mul__ def __div__(self, other): return self.__class__(self.p0 / other, self.p1 / other) def __and__(self, other): return self.intersect_line(other) def __repr__(self): return '<Line: {}, {}>'.format(self.p0.tuple, self.p1.tuple) # -- Properties @property def tuple(self): return (self.p0.tuple, self.p1.tuple) @property def points(self): return [self.p0, self.p1] @property def diff_x(self): return self.p1.x - self.p0.x @property def diff_y(self): return self.p1.y - self.p0.y @property def x(self): return min(self.p0.x, self.p1.x) @property def y(self): return min(self.p0.y, self.p1.y) @property def x_max(self): return max(self.p0.x, self.p1.x) @property def y_max(self): return max(self.p0.y, self.p1.y) @property def width(self): return abs(self.x_max - self.x) @property def height(self): return abs(self.y_max - self.y) @property def length(self): return math.hypot(self.p0.x - self.p1.x, self.p0.y - self.p1.y) @property def slope(self): try: return self.diff_y / float(self.diff_x) except ZeroDivisionError: return float('nan') @property def angle(self): return math.degrees(math.atan2(self.diff_y, self.diff_x)) @property def y_intercept(self): '''Get the Y intercept of a line segment''' return self.p0.y - self.slope * self.p0.x if not math.isnan(self.slope) and self.slope != 0 else self.p0.y # -- Solvers def solve_y(self, x): '''Solve line equation for Y coordinate.''' return self.slope * x + self.y_intercept if not math.isnan(self.slope) and self.slope != 0 else self.p0.y def solve_x(self, y): '''Solve line equation for X coordinate.''' return (float(y) - self.y_intercept) / float(self.slope) if not math.isnan(self.slope) and self.slope != 0 else self.p0.x def solve_point(self, time): '''Find point on the line at given time''' return self.p0 * (1. - time) + self.p1 * time def solve_slice(self, time): '''Slice line at given time''' return self.__class__(self.p0.tuple, self.solve_point(time).tuple), self.__class__(self.solve_point(time).tuple, self.p1.tuple) def lerp(self, time): return self.solve_point(time) def lerp_xy(self, time_x, time_y) : return Point(self.p0.x * (1. - time_x) + self.p1.x * time_x, self.p0.y * (1 - time_y) + self.p1.y * time_y) def intersect_line(self, other_line): '''Find intersection point (X, Y) for two lines. Returns (None, None) if lines do not intersect.''' diff_x = Point(self.p0.x - self.p1.x, other_line.p0.x - other_line.p1.x) diff_y = Point(self.p0.y - self.p1.y, other_line.p0.y - other_line.p1.y) div = diff_x | diff_y if div == 0: return (None, None) d = Point(self.p0 | self.p1, other_line.p0 | other_line.p1) x = (d | diff_x) / div y = (d | diff_y) / div return (x, y) def shift(self, dx, dy): '''Shift coordinates by dx, dy''' self.p0.x += dx self.p1.x += dx self.p0.y += dy self.p1.y += dy # -- Modifiers def doSwap(self): return self.__class__(self.p1, self.p0) def doTransform(self, transform=None): if transform is None: transform = self.transform self.p0.doTransform(transform) self.p1.doTransform(transform)
def shift(self, delta_x, delta_y): '''Shift the node by given amout''' self.point += Point(delta_x, delta_y)
def solve_tunni(self): '''Make proportional handles keeping curvature and on-curve point positions Based on modified Andres Torresi implementation of Eduardo Tunni's method for control points ''' practicalInfinity = Point(100000, 100000) # - Helper functions def getCrossing(p0, p1, p2, p3): # - Init diffA = p1 - p0 # p1.x - p0.x, p1.y - p0.y prodA = p1 & Point(p0.y, -p0.x) # p1.x * p0.y - p0.x * p1.y diffB = p3 - p2 prodB = p3 & Point(p2.y, -p2.x) # - Get intersection det = diffA & Point( diffB.y, -diffB.x) # diffA.x * diffB.y - diffB.x * diffA.y x = diffB.x * prodA - diffA.x * prodB y = diffB.y * prodA - diffA.y * prodB try: return Point(x / det, y / det) except ZeroDivisionError: return practicalInfinity def setProportion(pointA, pointB, prop): # - Set proportions according to Edaurdo Tunni sign = lambda x: (1, -1)[x < 0] # Helper function xDiff = max(pointA.x, pointB.x) - min(pointA.x, pointB.x) yDiff = max(pointA.y, pointB.y) - min(pointA.y, pointB.y) xEnd = pointA.x + xDiff * prop * sign(pointB.x - pointA.x) yEnd = pointA.y + yDiff * prop * sign(pointB.y - pointA.y) return Point(xEnd, yEnd) # - Run ------------------ # -- Init crossing = getCrossing(self.p3, self.p2, self.p0, self.p1) if crossing != practicalInfinity: node2extrema = math.hypot(self.p3.x - crossing.x, self.p3.y - crossing.y) node2bcp = math.hypot(self.p3.x - self.p2.x, self.p3.y - self.p2.y) proportion = (node2bcp / node2extrema) # -- Calculate bcp2b = setProportion(self.p0, crossing, proportion) propA = math.hypot( self.p0.x - self.p1.x, self.p0.y - self.p1.y) / math.hypot( self.p0.x - crossing.x, self.p0.y - crossing.y) propB = math.hypot( self.p0.x - bcp2b.x, self.p0.y - bcp2b.y) / math.hypot( self.p0.x - crossing.x, self.p0.y - crossing.y) propMean = (propA + propB) / 2 bcp2c = setProportion(self.p0, crossing, propMean) bcp1b = setProportion(self.p3, crossing, propMean) return self.__class__(self.p0.tuple, (bcp2c.x, bcp2c.y), (bcp1b.x, bcp1b.y), self.p3.tuple) else: return None
def func(scale=(1.,1.), time=(0.,0.), transalte=(0.,0.), angle=0., compensate=(0.,0.)): self.point = Point(adaptive_scale(((x0, y0), (x1, y1)), scale, transalte, time, compensate, angle, weights))
def point(self): return Point(self.x, self.y, angle=self.angle, transform=self.transform, complex=self.complex_math)
@staticmethod def to_XML(self): raise NotImplementedError @staticmethod def from_XML(string): raise NotImplementedError if __name__ == '__main__': # - Test initialization, normal and from VFJ n0 = Node.from_VFJ('10 20 s g2') n1 = Node.from_VFJ('20 30 s') n2 = Node(35, 55.65) n3 = Node(44, 67, type='smooth') n4 = Node(n3) print(n3, n4) # - Test math and VFJ export n3.point = Point(34,88) n3.point += 30 print(n3.to_VFJ()) # - Test Containers and VFJ export c = Container([n0, n1, n2, n3, n4], default_factory=Node) c.append((99,99)) print(n0) n0.lerp_to(n1, (.5,.5)) print(n0)
def shift(self, delta_x, delta_y): '''Shift the layer by given amout''' for node in self.nodes: node.point += Point(delta_x, delta_y)
def func(tx, ty): self.point = Point(lerp(x0, x1, tx), lerp(y0, y1, ty))