Module sverchok.utils.modules.profile_mk3.interpreter

Expand source code
# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

import ast
from math import *
import numpy as np

from mathutils.geometry import interpolate_bezier
from mathutils import Vector, Matrix

from sverchok.utils.sv_logging import sv_logger
from sverchok.utils.geom import interpolate_quadratic_bezier
from sverchok.utils.sv_curve_utils import Arc
from sverchok.utils.nurbs_common import SvNurbsMaths
from sverchok.utils.curve import SvCircle, SvLine, SvBezierCurve, SvCubicBezierCurve
import sverchok.utils.curve.knotvector as sv_knotvector
from sverchok.utils.curve.nurbs_solver import SvNurbsCurveControlPoints
from sverchok.utils.curve.nurbs_solver_applications import prepare_solver_for_interpolation

def make_functions_dict(*functions):
    return dict([(function.__name__, function) for function in functions])

# Functions
safe_names = make_functions_dict(
        # From math module
        acos, acosh, asin, asinh, atan, atan2,
        atanh, ceil, copysign, cos, cosh, degrees,
        erf, erfc, exp, expm1, fabs, factorial, floor,
        fmod, frexp, fsum, gamma, hypot, isfinite, isinf,
        isnan, ldexp, lgamma, log, log10, log1p, log2, modf,
        pow, radians, sin, sinh, sqrt, tan, tanh, trunc,
        # Additional functions
        abs,
        # From mathutlis module
        Vector, Matrix,
        # Python type conversions
        tuple, list, str
    )
# Constants
safe_names['e'] = e
safe_names['pi'] = pi

##########################################
# Expression classes
##########################################

class Expression(object):
    def __init__(self, expr, string):
        self.expr = expr
        self.string = string

    def __repr__(self):
        return "Expr({})".format(self.string)

    def __eq__(self, other):
        # Proper comparison of ast.Expression would be too complex to implement
        # (it is not implemented in the ast module).
        return isinstance(other, Expression) and self.string == other.string

    @classmethod
    def from_string(cls, string):
        try:
            string = string[1:][:-1]
            expr = ast.parse(string, mode='eval')
            return Expression(expr, string)
        except Exception as e:
            print(e)
            print(string)
            return None

    def eval_(self, variables):
        env = dict()
        env.update(safe_names)
        env.update(variables)
        env["__builtins__"] = {}
        return eval(compile(self.expr, "<expression>", 'eval'), env)

    def get_variables(self):
        result = {node.id for node in ast.walk(self.expr) if isinstance(node, ast.Name)}
        return result.difference(safe_names.keys())

class Const(Expression):
    def __init__(self, value):
        self.value = value

    @classmethod
    def from_string(cls, string):
        try:
            return Const( float(string) )
        except ValueError:
            return None

    def __repr__(self):
        return "Const({})".format(self.value)

    def __eq__(self, other):
        return isinstance(other,Const) and self.value == other.value

    def eval_(self, variables):
        return self.value

    def get_variables(self):
        return set()

class Variable(Expression):
    def __init__(self, name):
        self.name = name

    @classmethod
    def from_string(cls, string):
        return Variable(string)

    def __repr__(self):
        return "Variable({})".format(self.name)

    def __eq__(self, other):
        return isinstance(other, Variable) and self.name == other.name

    def eval_(self, variables):
        value = variables.get(self.name, None)
        if value is not None:
            return value
        else:
            raise SyntaxError("Unknown variable: " + self.name)

    def get_variables(self):
        return set([self.name])

# In general, this does not have very much sense:
# instead of -a one can write {-a}, then it will
# be parsed as Expression and will work fine.
# This is mostly implemented for compatibility
# with older Profile node syntax.
class NegatedVariable(Variable):
    @classmethod
    def from_string(cls, string):
        return NegatedVariable(string)

    def eval_(self, variables):
        value = variables.get(self.name, None)
        if value is not None:
            return -value
        else:
            raise SyntaxError("Unknown variable: " + self.name)

    def __repr__(self):
        return "NegatedVariable({})".format(self.name)

    def __eq__(self, other):
        return isinstance(other, NegatedVariable) and self.name == other.name

############################################
# Statement classes
# Classes for AST of our DSL
############################################

# These classes are responsible for interpretation of specific DSL statements.
# Each of these classes does the following:
# 
#  * Stores statement parameters (for example, MoveTo stores x and y).
#  * defines get_variables() method, which should return a set of all
#    variables used by all expressions in the statement.
#  * defines interpret() method, which should calculate all vertices and
#    edges according to statement parameters, and pass them to the interpreter.

class Statement(object):
    
    def get_variables(self):
        return set()

    def get_hidden_inputs(self):
        return set()

    def get_optional_inputs(self):
        return set()

    def _interpolate(self, v0, v1, num_segments):
        if num_segments is None or num_segments <= 1:
            return [v0, v1]
        dx_total, dy_total = v1[0] - v0[0], v1[1] - v0[1]
        dx, dy = dx_total / float(num_segments), dy_total / float(num_segments)
        x, y = v0
        dt = 1.0 / float(num_segments)
        result = []
        t = 0
        for i in range(round(num_segments)):
            result.append((x,y))
            x = x + dx
            y = y + dy
        result.append(v1)
        return result

class MoveTo(Statement):
    def __init__(self, is_abs, x, y):
        self.is_abs = is_abs
        self.x = x
        self.y = y

    def __repr__(self):
        letter = "M" if self.is_abs else "m"
        return "{} {} {}".format(letter, self.x, self.y)

    def __eq__(self, other):
        return isinstance(other, MoveTo) and \
                self.is_abs == other.is_abs and \
                self.x == other.x and \
                self.y == other.y

    def get_variables(self):
        variables = set()
        variables.update(self.x.get_variables())
        variables.update(self.y.get_variables())
        return variables

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()
        pos = interpreter.calc_vertex(self.is_abs, self.x, self.y, variables)
        interpreter.position = pos
        interpreter.new_knot("M.#", *pos)
        interpreter.has_last_vertex = False

class LineTo(Statement):
    def __init__(self, is_abs, pairs, num_segments, close):
        self.is_abs = is_abs
        self.pairs = pairs
        self.num_segments = num_segments
        self.close = close

    def get_variables(self):
        variables = set()
        for x, y in self.pairs:
            variables.update(x.get_variables())
            variables.update(y.get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "L" if self.is_abs else "l"
        return "{} {} n={} {}".format(letter, self.pairs, self.num_segments, self.close)
    
    def __eq__(self, other):
        return isinstance(other, LineTo) and \
                self.is_abs == other.is_abs and \
                self.pairs == other.pairs and \
                self.num_segments == other.num_segments and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()
        v0 = interpreter.position
        if interpreter.has_last_vertex:
            prev_index = interpreter.get_last_vertex()
        else:
            prev_index = interpreter.new_vertex(*v0)

        if self.num_segments is not None:
            num_segments = interpreter.eval_(self.num_segments, variables)
        else:
            num_segments = None

        for i, (x_expr, y_expr) in enumerate(self.pairs):
            v1 = interpreter.calc_vertex(self.is_abs, x_expr, y_expr, variables)
            interpreter.position = v1
            for vertex in self._interpolate(v0, v1, num_segments)[1:]:
                v_index = interpreter.new_vertex(*vertex)
                interpreter.new_edge(prev_index, v_index)
                prev_index = v_index
            interpreter.new_line_segment(v0, v1)
            v0 = v1
            interpreter.new_knot("L#.{}".format(i), *v1)

        if self.close:
            interpreter.close_segment(v_index)

        interpreter.has_last_vertex = True

class HorizontalLineTo(Statement):
    def __init__(self, is_abs, xs, num_segments):
        self.is_abs = is_abs
        self.xs = xs
        self.num_segments = num_segments

    def get_variables(self):
        variables = set()
        for x in self.xs:
            variables.update(x.get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "H" if self.is_abs else "h"
        return "{} {} n={};".format(letter, self.xs, self.num_segments)

    def __eq__(self, other):
        return isinstance(other, HorizontalLineTo) and \
                self.is_abs == other.is_abs and \
                self.num_segments == other.num_segments and \
                self.xs == other.xs

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()
        v0 = interpreter.position
        if interpreter.has_last_vertex:
            prev_index = interpreter.get_last_vertex()
        else:
            prev_index = interpreter.new_vertex(*v0)

        if self.num_segments is not None:
            num_segments = interpreter.eval_(self.num_segments, variables)
        else:
            num_segments = None

        for i, x_expr in enumerate(self.xs):
            x0,y0 = interpreter.position
            x = interpreter.eval_(x_expr, variables)
            if not self.is_abs:
                x = x0 + x
            v1 = (x, y0)
            interpreter.position = v1
            verts = self._interpolate(v0, v1, num_segments)
            # sv_logger.debug("V0 %s, v1 %s, N %s => %s", v0, v1, num_segments, verts)
            for vertex in verts[1:]:
                v_index = interpreter.new_vertex(*vertex)
                interpreter.new_edge(prev_index, v_index)
                prev_index = v_index
            interpreter.new_line_segment(v0, v1)
            v0 = v1
            interpreter.new_knot("H#.{}".format(i), *v1)

        interpreter.has_last_vertex = True

class VerticalLineTo(Statement):
    def __init__(self, is_abs, ys, num_segments):
        self.is_abs = is_abs
        self.ys = ys
        self.num_segments = num_segments

    def get_variables(self):
        variables = set()
        for y in self.ys:
            variables.update(y.get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "V" if self.is_abs else "v"
        return "{} {} n={};".format(letter, self.ys, self.num_segments)

    def __eq__(self, other):
        return isinstance(other, VerticalLineTo) and \
                self.is_abs == other.is_abs and \
                self.num_segments == other.num_segments and \
                self.ys == other.ys

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()
        v0 = interpreter.position
        if interpreter.has_last_vertex:
            prev_index = interpreter.get_last_vertex()
        else:
            prev_index = interpreter.new_vertex(*v0)

        if self.num_segments is not None:
            num_segments = interpreter.eval_(self.num_segments, variables)
        else:
            num_segments = None

        for i, y_expr in enumerate(self.ys):
            x0,y0 = interpreter.position
            y = interpreter.eval_(y_expr, variables)
            if not self.is_abs:
                y = y0 + y
            v1 = (x0, y)
            interpreter.position = v1
            for vertex in self._interpolate(v0, v1, num_segments)[1:]:
                v_index = interpreter.new_vertex(*vertex)
                interpreter.new_edge(prev_index, v_index)
                prev_index = v_index
            interpreter.new_line_segment(v0, v1)
            v0 = v1
            interpreter.new_knot("V#.{}".format(i), *v1)

        interpreter.has_last_vertex = True

class CurveTo(Statement):
    class Segment(object):
        def __init__(self, control1, control2, knot2):
            self.control1 = control1
            self.control2 = control2
            self.knot2 = knot2

        def __repr__(self):
            return "{} {} {}".format(self.control1, self.control2, self.knot2)

        def __eq__(self, other):
            return self.control1 == other.control1 and \
                    self.control2 == other.control2 and \
                    self.knot2 == other.knot2

    def __init__(self, is_abs, segments, num_segments, close):
        self.is_abs = is_abs
        self.segments = segments
        self.num_segments = num_segments
        self.close = close

    def get_variables(self):
        variables = set()
        for segment in self.segments:
            variables.update(segment.control1[0].get_variables())
            variables.update(segment.control1[1].get_variables())
            variables.update(segment.control2[0].get_variables())
            variables.update(segment.control2[1].get_variables())
            variables.update(segment.knot2[0].get_variables())
            variables.update(segment.knot2[1].get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "C" if self.is_abs else "c"
        segments = " ".join(str(segment) for segment in self.segments)
        return "{} {} n={} {}".format(letter, segments, self.num_segments, self.close)

    def __eq__(self, other):
        return isinstance(other, CurveTo) and \
                self.is_abs == other.is_abs and \
                self.segments == other.segments and \
                self.num_segments == other.num_segments and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        vec = lambda v: Vector((v[0], v[1], 0))

        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        knot1 = None
        for i, segment in enumerate(self.segments):
            # For first segment, knot1 is initial pen position;
            # for the following, knot1 is knot2 of previous segment.
            if knot1 is None:
                knot1 = interpreter.position
            else:
                knot1 = knot2

            handle1 = interpreter.calc_vertex(self.is_abs, segment.control1[0], segment.control1[1], variables)

            # In Profile mk2, for "c" handle2 was calculated relative to handle1,
            # and knot2 was calculated relative to handle2.
            # But in SVG specification, 
            # >> ...  *At the end of the command*, the new current point becomes
            # >> the final (x,y) coordinate pair used in the polybézier.
            # This is also behaviour of browsers.

            #interpreter.position = handle1
            handle2 = interpreter.calc_vertex(self.is_abs, segment.control2[0], segment.control2[1], variables)
            #interpreter.position = handle2
            knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
            # Judging by the behaviour of Inkscape and Firefox, by "end of command"
            # SVG spec means "end of segment".
            interpreter.position = knot2

            if self.num_segments is not None:
                r = interpreter.eval_(self.num_segments, variables)
            else:
                r = interpreter.dflt_num_verts

            curve = SvCubicBezierCurve(vec(knot1), vec(handle1), vec(handle2), vec(knot2))
            interpreter.new_curve(curve, self)

            points = interpolate_bezier(vec(knot1), vec(handle1), vec(handle2), vec(knot2), r)

            interpreter.new_knot("C#.{}.h1".format(i), *handle1)
            interpreter.new_knot("C#.{}.h2".format(i), *handle2)
            interpreter.new_knot("C#.{}.k".format(i), *knot2)

            interpreter.prev_bezier_knot = handle2

            for point in points[1:]:
                v1_index = interpreter.new_vertex(point.x, point.y)
                interpreter.new_edge(v0_index, v1_index)
                v0_index = v1_index

        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

class SmoothCurveTo(Statement):
    class Segment(object):
        def __init__(self, control2, knot2):
            self.control2 = control2
            self.knot2 = knot2

        def __repr__(self):
            return "{} {}".format(self.control2, self.knot2)

        def __eq__(self, other):
            return self.control2 == other.control2 and \
                    self.knot2 == other.knot2

    def __init__(self, is_abs, segments, num_segments, close):
        self.is_abs = is_abs
        self.segments = segments
        self.num_segments = num_segments
        self.close = close

    def get_variables(self):
        variables = set()
        for segment in self.segments:
            variables.update(segment.control2[0].get_variables())
            variables.update(segment.control2[1].get_variables())
            variables.update(segment.knot2[0].get_variables())
            variables.update(segment.knot2[1].get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "S" if self.is_abs else "s"
        segments = " ".join(str(segment) for segment in self.segments)
        return "{} {} n={} {}".format(letter, segments, self.num_segments, self.close)

    def __eq__(self, other):
        return isinstance(other, SmoothCurveTo) and \
                self.is_abs == other.is_abs and \
                self.segments == other.segments and \
                self.num_segments == other.num_segments and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        vec = lambda v: Vector((v[0], v[1], 0))

        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        knot1 = None
        for i, segment in enumerate(self.segments):
            # For first segment, knot1 is initial pen position;
            # for the following, knot1 is knot2 of previous segment.
            if knot1 is None:
                knot1 = interpreter.position
            else:
                knot1 = knot2

            if interpreter.prev_bezier_knot is None:
                # If there is no previous command or if the previous command was
                # not an C, c, S or s, assume the first control point is coincident
                # with the current point.
                handle1 = knot1
            else:
                # The first control point is assumed to be the reflection of the
                # second control point on the previous command relative to the
                # current point. 
                prev_knot_x, prev_knot_y = interpreter.prev_bezier_knot
                x0, y0 = knot1
                dx, dy = x0 - prev_knot_x, y0 - prev_knot_y
                handle1 = x0 + dx, y0 + dy

            # I assume that handle2 should be relative to knot1, not to handle1.
            # interpreter.position = handle1
            handle2 = interpreter.calc_vertex(self.is_abs, segment.control2[0], segment.control2[1], variables)
            # interpreter.position = handle2
            knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
            interpreter.position = knot2

            if self.num_segments is not None:
                r = interpreter.eval_(self.num_segments, variables)
            else:
                r = interpreter.dflt_num_verts

            curve = SvCubicBezierCurve(vec(knot1), vec(handle1), vec(handle2), vec(knot2))
            interpreter.new_curve(curve, self)

            points = interpolate_bezier(vec(knot1), vec(handle1), vec(handle2), vec(knot2), r)

            interpreter.new_knot("S#.{}.h1".format(i), *handle1)
            interpreter.new_knot("S#.{}.h2".format(i), *handle2)
            interpreter.new_knot("S#.{}.k".format(i), *knot2)

            interpreter.prev_bezier_knot = handle2

            for point in points[1:]:
                v1_index = interpreter.new_vertex(point.x, point.y)
                interpreter.new_edge(v0_index, v1_index)
                v0_index = v1_index

        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

class QuadraticCurveTo(Statement):
    class Segment(object):
        def __init__(self, control, knot2):
            self.control = control
            self.knot2 = knot2

        def __repr__(self):
            return "{} {}".format(self.control, self.knot2)

        def __eq__(self, other):
            return self.control == other.control and \
                    self.knot2 == other.knot2

    def __init__(self, is_abs, segments, num_segments, close):
        self.is_abs = is_abs
        self.segments = segments
        self.num_segments = num_segments
        self.close = close

    def get_variables(self):
        variables = set()
        for segment in self.segments:
            variables.update(segment.control[0].get_variables())
            variables.update(segment.control[1].get_variables())
            variables.update(segment.knot2[0].get_variables())
            variables.update(segment.knot2[1].get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "Q" if self.is_abs else "q"
        segments = " ".join(str(segment) for segment in self.segments)
        return "{} {} n={} {}".format(letter, segments, self.num_segments, self.close)

    def __eq__(self, other):
        return isinstance(other, QuadraticCurveTo) and \
                self.is_abs == other.is_abs and \
                self.segments == other.segments and \
                self.num_segments == other.num_segments and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        vec = lambda v: Vector((v[0], v[1], 0))

        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        knot1 = None
        for i, segment in enumerate(self.segments):
            # For first segment, knot1 is initial pen position;
            # for the following, knot1 is knot2 of previous segment.
            if knot1 is None:
                knot1 = interpreter.position
            else:
                knot1 = knot2

            handle = interpreter.calc_vertex(self.is_abs, segment.control[0], segment.control[1], variables)
            knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
            interpreter.position = knot2

            if self.num_segments is not None:
                r = interpreter.eval_(self.num_segments, variables)
            else:
                r = interpreter.dflt_num_verts

            curve = SvBezierCurve([vec(knot1), vec(handle), vec(knot2)])
            interpreter.new_curve(curve, self)

            points = interpolate_quadratic_bezier(vec(knot1), vec(handle), vec(knot2), r)

            interpreter.new_knot("Q#.{}.h".format(i), *handle)
            interpreter.new_knot("Q#.{}.k".format(i), *knot2)

            interpreter.prev_quad_bezier_knot = handle

            for point in points[1:]:
                v1_index = interpreter.new_vertex(point.x, point.y)
                interpreter.new_edge(v0_index, v1_index)
                v0_index = v1_index

        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

class SmoothQuadraticCurveTo(Statement):
    class Segment(object):
        def __init__(self, knot2):
            self.knot2 = knot2

        def __repr__(self):
            return str(self.knot2)

        def __eq__(self, other):
            return self.knot2 == other.knot2

    def __init__(self, is_abs, segments, num_segments, close):
        self.is_abs = is_abs
        self.segments = segments
        self.num_segments = num_segments
        self.close = close

    def get_variables(self):
        variables = set()
        for segment in self.segments:
            variables.update(segment.knot2[0].get_variables())
            variables.update(segment.knot2[1].get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "T" if self.is_abs else "t"
        segments = " ".join(str(segment) for segment in self.segments)
        return "{} {} n={} {}".format(letter, segments, self.num_segments, self.close)

    def __eq__(self, other):
        return isinstance(other, SmoothQuadraticCurveTo) and \
                self.is_abs == other.is_abs and \
                self.segments == other.segments and \
                self.num_segments == other.num_segments and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        vec = lambda v: Vector((v[0], v[1], 0))

        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        knot1 = None
        for i, segment in enumerate(self.segments):
            # For first segment, knot1 is initial pen position;
            # for the following, knot1 is knot2 of previous segment.
            if knot1 is None:
                knot1 = interpreter.position
            else:
                knot1 = knot2

            if interpreter.prev_quad_bezier_knot is None:
                # If there is no previous command or if the previous command was
                # not a Q, q, T or t, assume the control point is coincident with
                # the current point.
                handle = knot1
            else:
                # The first control point is assumed to be the reflection of the
                # second control point on the previous command relative to the
                # current point. 
                prev_knot_x, prev_knot_y = interpreter.prev_quad_bezier_knot
                x0, y0 = knot1
                dx, dy = x0 - prev_knot_x, y0 - prev_knot_y
                handle = x0 + dx, y0 + dy

            knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
            interpreter.position = knot2

            if self.num_segments is not None:
                r = interpreter.eval_(self.num_segments, variables)
            else:
                r = interpreter.dflt_num_verts

            curve = SvBezierCurve([vec(knot1), vec(handle), vec(knot2)])
            interpreter.new_curve(curve, self)

            points = interpolate_quadratic_bezier(vec(knot1), vec(handle), vec(knot2), r)

            interpreter.new_knot("T#.{}.h".format(i), *handle)
            interpreter.new_knot("T#.{}.k".format(i), *knot2)

            interpreter.prev_quad_bezier_knot = handle

            for point in points[1:]:
                v1_index = interpreter.new_vertex(point.x, point.y)
                interpreter.new_edge(v0_index, v1_index)
                v0_index = v1_index

        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

class ArcTo(Statement):
    def __init__(self, is_abs, radii, rot, flag1, flag2, end, num_verts, close):
        self.is_abs = is_abs
        self.radii = radii
        self.rot = rot
        self.flag1 = flag1
        self.flag2 = flag2
        self.end = end
        self.num_verts = num_verts
        self.close = close

    def get_variables(self):
        variables = set()
        variables.update(self.radii[0].get_variables())
        variables.update(self.radii[1].get_variables())
        variables.update(self.rot.get_variables())
        variables.update(self.flag1.get_variables())
        variables.update(self.flag2.get_variables())
        variables.update(self.end[0].get_variables())
        variables.update(self.end[1].get_variables())
        if self.num_verts:
            variables.update(self.num_verts.get_variables())
        return variables

    def __repr__(self):
        letter = "A" if self.is_abs else "a"
        return "{} {} {} {} {} {} n={} {}".format(letter, self.radii, self.rot, self.flag1, self.flag2, self.end, self.num_verts, self.close)

    def __eq__(self, other):
        return isinstance(other, ArcTo) and \
                self.is_abs == other.is_abs and \
                self.radii == other.radii and \
                self.rot == other.rot and \
                self.flag1 == other.flag1 and \
                self.flag2 == other.flag2 and \
                self.end == other.end and \
                self.num_verts == other.num_verts and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        start = complex(*v0)
        rad_x_expr, rad_y_expr = self.radii
        rad_x = interpreter.eval_(rad_x_expr, variables)
        rad_y = interpreter.eval_(rad_y_expr, variables)
        radius = complex(rad_x, rad_y)
        xaxis_rot = interpreter.eval_(self.rot, variables)
        flag1 = interpreter.eval_(self.flag1, variables)
        flag2 = interpreter.eval_(self.flag2, variables)

        # numverts, requires -1 else it means segments (21 verts is 20 segments).
        if self.num_verts is not None:
            num_verts = interpreter.eval_(self.num_verts, variables)
        else:
            num_verts = interpreter.dflt_num_verts
        num_verts -= 1

        end = interpreter.calc_vertex(self.is_abs, self.end[0], self.end[1], variables)
        end = complex(*end)

        arc = Arc(start, radius, xaxis_rot, flag1, flag2, end)

        theta = 1/num_verts
        for i in range(1, num_verts+1):
            v1 = x, y = arc.point(theta * i)
            v1_index = interpreter.new_vertex(x, y)
            interpreter.new_edge(v0_index, v1_index)
            v0_index = v1_index

        curve = SvCircle.from_arc(arc, z_axis=interpreter.z_axis)
        interpreter.new_curve(curve, self)

        interpreter.position = v1
        interpreter.new_knot("A.#", *v1)
        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

class InterpolatedCurveTo(Statement):
    def __init__(self, is_abs, degree, points, num_segments, metric, is_smooth, close):
        self.is_abs = is_abs
        self.degree = degree
        self.points = points
        self.metric = metric
        self.num_segments = num_segments
        self.is_smooth = is_smooth
        self.close = close

    def get_variables(self):
        variables = set()
        variables.update(self.degree.get_variables())
        for point in self.points:
            variables.update(point[0].get_variables())
            variables.update(point[1].get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "I" if self.is_abs else "i"
        points = " ".join(str(point) for point in self.points)
        return f"{letter} {self.degree} {points} n={self.num_segments} {self.close}"

    def __eq__(self, other):
        return isinstance(other, InterpolatedCurveTo) and \
                self.is_abs == other.is_abs and \
                self.points == other.points and \
                self.degree == other.degree and \
                self.metric == other.metric and \
                self.num_segments == other.num_segments and \
                self.is_smooth == other.is_smooth and \
                self.close == other.close

    def _make_curve(self, interpreter, degree, points):
        points = np.array(points)
        prev_control_point = None
        if degree == 2 and interpreter.prev_quad_bezier_knot is not None:
            prev_control_point = interpreter.to3d_np(interpreter.prev_quad_bezier_knot)
        elif degree == 3 and interpreter.prev_bezier_knot is not None:
            prev_control_point = interpreter.to3d_np(interpreter.prev_bezier_knot)
        if self.is_smooth and prev_control_point is not None:
            solver = prepare_solver_for_interpolation(degree,
                                points,
                                metric = self.metric,
                                cyclic = self.close)
            pos = interpreter.to3d_np(interpreter.position)
            new_control_point = pos + (pos - prev_control_point)
            print(f"Prev CP: {prev_control_point}, pos: {pos}, new CP: {new_control_point}")
            solver.add_goal(SvNurbsCurveControlPoints.single(1, new_control_point, relative=False))
            knotvector = solver.get_knotvector()
            n_cpts = solver.guess_n_control_points()
            knotvector = sv_knotvector.add_one_by_resampling(knotvector, index=1, degree=degree)
            solver.set_curve_params(n_cpts, knotvector)
            curve = solver.solve_welldetermined()
        else:
            curve = SvNurbsMaths.interpolate_curve(SvNurbsMaths.NATIVE, degree,
                                points,
                                metric = self.metric,
                                cyclic = self.close)
        return curve

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        interpolated_points = [[v0[0], v0[1], 0.0]]
        for i, pt in enumerate(self.points):
            knot = interpreter.calc_vertex(self.is_abs, pt[0], pt[1], variables)
            interpreter.new_knot(f"I#.{i}", knot[0], knot[1])
            interpolated_points.append([knot[0], knot[1], 0.0])

        degree = interpreter.eval_(self.degree, variables)
        curve = self._make_curve(interpreter, degree, interpolated_points)
        interpreter.new_curve(curve, self)
        interpreter.position = knot

        cpts = curve.get_control_points()
        if degree == 2:
            interpreter.prev_quad_bezier_knot = (cpts[-2][0], cpts[-2][1])
        elif degree == 3:
            interpreter.prev_bezier_knot = (cpts[-2][0], cpts[-2][1])

        if self.num_segments is not None:
            r = interpreter.eval_(self.num_segments, variables)
        else:
            r = interpreter.dflt_num_verts

        t_min, t_max = curve.get_u_bounds()
        ts = np.linspace(t_min, t_max, num = r)
        points = curve.evaluate_array(ts)

        for point in points[1:]:
            v1_index = interpreter.new_vertex(point[0], point[1])
            interpreter.new_edge(v0_index, v1_index)
            v0_index = v1_index

        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

class CloseAll(Statement):
    def __init__(self):
        pass

    def __repr__(self):
        return "X"

    def __eq__(self, other):
        return isinstance(other, CloseAll)

    def get_variables(self):
        return set()

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        if not interpreter.has_last_vertex:
            sv_logger.info("X statement: no current point, do nothing")
            return

        v0 = interpreter.vertices[0]
        v1 = interpreter.vertices[-1]

        distance = (Vector(v0) - Vector(v1)).length

        if distance < interpreter.close_threshold:
            interpreter.pop_last_vertex()

        v1_index = interpreter.get_last_vertex()
        interpreter.new_edge(v1_index, 0)

        interpreter.new_line_segment(v1, v0)

        interpreter.closed = True

class ClosePath(Statement):
    def __init__(self):
        pass

    def __repr__(self):
        return "x"

    def __eq__(self, other):
        return isinstance(other, ClosePath)

    def get_variables(self):
        return set()

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        if not interpreter.has_last_vertex:
            sv_logger.info("X statement: no current point, do nothing")
            return

        v0 = interpreter.vertices[interpreter.close_first_index]
        v1 = interpreter.vertices[-1]

        distance = (Vector(v0) - Vector(v1)).length

        if distance < interpreter.close_threshold:
            interpreter.pop_last_vertex()

        v1_index = interpreter.get_last_vertex()
        interpreter.new_edge(v1_index, interpreter.close_first_index)
        interpreter.new_line_segment(v1_index, interpreter.close_first_index)
        interpreter.close_first_index = interpreter.next_vertex_index

class Default(Statement):
    def __init__(self, name, value):
        self.name = name
        self.value = value

    def __repr__(self):
        return "default {} = {}".format(self.name, self.value)

    def __eq__(self, other):
        return isinstance(other, Default) and \
                self.name == other.name and \
                self.value == other.value

    def get_variables(self):
        return self.value.get_variables()

    def get_optional_inputs(self):
        return set([self.name])

    def interpret(self, interpreter, variables):
        if self.name in interpreter.defaults:
            raise Exception("Value for the `{}' variable has been already assigned!".format(self.name))
        if self.name not in interpreter.input_names:
            value = interpreter.eval_(self.value, variables)
            interpreter.defaults[self.name] = value

class Assign(Default):
    def __repr__(self):
        return "let {} = {}".format(self.name, self.value)

    def __eq__(self, other):
        return isinstance(other, Assign) and \
                self.name == other.name and \
                self.value == other.value

    def get_hidden_inputs(self):
        return set([self.name])

#################################
# DSL Interpreter
#################################

# This class does the following:
#
# * Stores the "drawing" state, such as "current pen position"
# * Provides API for Statement classes to add vertices, edges to the current
#   drawing
# * Contains the interpret() method, which runs the whole interpretation process.

class Interpreter(object):

    NURBS = 'NURBS'
    BEZIER = 'BEZIER'

    def __init__(self, node, input_names, curves_form = None, force_curves_form = False, z_axis='Z'):
        self.position = (0, 0)
        self.next_vertex_index = 0
        self.segment_start_index = 0
        self.segment_continues_line = False
        self.segment_number = 0
        self.has_last_vertex = False
        self.closed = False
        self.close_first_index = 0
        self.prev_bezier_knot = None
        self.prev_quad_bezier_knot = None
        self.curves = []
        self.vertices = []
        self.edges = []
        self.knots = []
        self.knotnames = []
        self.dflt_num_verts = node.curve_points_count
        self.close_threshold = node.close_threshold
        self.defaults = dict()
        self.input_names = input_names
        self.curves_form = curves_form
        self.force_curves_form = force_curves_form
        self.z_axis = z_axis

    def to3d(self, vertex):
        if self.z_axis == 'X':
            return Vector((0, vertex[0], vertex[1]))
        elif self.z_axis == 'Y':
            return Vector((vertex[0], 0, vertex[1]))
        else: # self.z_axis == 'Z':
            return Vector((vertex[0], vertex[1], 0))

    def to3d_np(self, vertex):
        if self.z_axis == 'X':
            return np.array((0, vertex[0], vertex[1]))
        elif self.z_axis == 'Y':
            return np.array((vertex[0], 0, vertex[1]))
        else: # self.z_axis == 'Z':
            return np.array((vertex[0], vertex[1], 0))

    def assert_not_closed(self):
        if self.closed:
            raise Exception("Path was already closed, will not process any further directives!")

    def relative(self, x, y):
        x0, y0 = self.position
        return x0+x, y0+y

    def calc_vertex(self, is_abs, x_expr, y_expr, variables):
        x = self.eval_(x_expr, variables)
        y = self.eval_(y_expr, variables)
        if is_abs:
            return x,y
        else:
            return self.relative(x,y)

    def new_vertex(self, x, y):
        index = self.next_vertex_index
        vertex = (x, y)
        self.vertices.append(vertex)
        self.next_vertex_index += 1
        return index

    def new_edge(self, v1, v2):
        self.edges.append((v1, v2))

    def new_knot(self, name, x, y):
        self.knots.append((x, y))
        name = name.replace("#", str(self.segment_number))
        self.knotnames.append(name)

    def new_curve(self, curve, statement):
        if self.curves_form == Interpreter.NURBS:
            if hasattr(curve, 'to_nurbs'):
                curve = curve.to_nurbs()
            else:
                if self.force_curves_form:
                    raise Exception(f"Cannot convert curve to NURBS: {statement}")
        elif self.curves_form == Interpreter.BEZIER:
            if not isinstance(curve, (SvBezierCurve, SvCubicBezierCurve)):
                if hasattr(curve, 'to_bezier'):
                    curve = curve.to_bezier()
                else:
                    if self.force_curves_form:
                        raise Exception("Cannot convert curve to Bezier: {statement}")
        self.curves.append(curve)

    def new_line_segment(self, v1, v2):
        if isinstance(v1, int):
            v1, v2 = self.vertices[v1], self.vertices[v2]
        v1, v2 = self.to3d(v1), self.to3d(v2)
        if (v1 - v2).length < self.close_threshold:
            return
        curve = SvLine.from_two_points(v1, v2)
        self.new_curve(curve, None)

    def start_new_segment(self):
        self.segment_start_index = self.next_vertex_index
        self.segment_continues_line = self.has_last_vertex
        self.segment_number += 1

    def close_segment(self, v_index):
        if self.segment_continues_line:
            start_index = self.segment_start_index-1
        else:
            start_index = self.segment_start_index
        self.new_edge(v_index, start_index)

    def get_last_vertex(self):
        return self.next_vertex_index - 1

    def pop_last_vertex(self):
        self.vertices.pop()
        self.next_vertex_index -= 1
        is_not_last = lambda e: e[0] != self.next_vertex_index and e[1] != self.next_vertex_index
        self.edges = list(filter(is_not_last, self.edges))

    def eval_(self, expr, variables):
        variables_ = self.defaults.copy()
        for name in variables:
            value = variables[name]
            if value is not None:
                variables_[name] = value
        return expr.eval_(variables_)

    def interpret(self, profile, variables):
        if not profile:
            return
        for statement in profile:
            sv_logger.debug("Interpret: %s", statement)
            statement.interpret(self, variables)

Functions

def make_functions_dict(*functions)
Expand source code
def make_functions_dict(*functions):
    return dict([(function.__name__, function) for function in functions])

Classes

class ArcTo (is_abs, radii, rot, flag1, flag2, end, num_verts, close)
Expand source code
class ArcTo(Statement):
    def __init__(self, is_abs, radii, rot, flag1, flag2, end, num_verts, close):
        self.is_abs = is_abs
        self.radii = radii
        self.rot = rot
        self.flag1 = flag1
        self.flag2 = flag2
        self.end = end
        self.num_verts = num_verts
        self.close = close

    def get_variables(self):
        variables = set()
        variables.update(self.radii[0].get_variables())
        variables.update(self.radii[1].get_variables())
        variables.update(self.rot.get_variables())
        variables.update(self.flag1.get_variables())
        variables.update(self.flag2.get_variables())
        variables.update(self.end[0].get_variables())
        variables.update(self.end[1].get_variables())
        if self.num_verts:
            variables.update(self.num_verts.get_variables())
        return variables

    def __repr__(self):
        letter = "A" if self.is_abs else "a"
        return "{} {} {} {} {} {} n={} {}".format(letter, self.radii, self.rot, self.flag1, self.flag2, self.end, self.num_verts, self.close)

    def __eq__(self, other):
        return isinstance(other, ArcTo) and \
                self.is_abs == other.is_abs and \
                self.radii == other.radii and \
                self.rot == other.rot and \
                self.flag1 == other.flag1 and \
                self.flag2 == other.flag2 and \
                self.end == other.end and \
                self.num_verts == other.num_verts and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        start = complex(*v0)
        rad_x_expr, rad_y_expr = self.radii
        rad_x = interpreter.eval_(rad_x_expr, variables)
        rad_y = interpreter.eval_(rad_y_expr, variables)
        radius = complex(rad_x, rad_y)
        xaxis_rot = interpreter.eval_(self.rot, variables)
        flag1 = interpreter.eval_(self.flag1, variables)
        flag2 = interpreter.eval_(self.flag2, variables)

        # numverts, requires -1 else it means segments (21 verts is 20 segments).
        if self.num_verts is not None:
            num_verts = interpreter.eval_(self.num_verts, variables)
        else:
            num_verts = interpreter.dflt_num_verts
        num_verts -= 1

        end = interpreter.calc_vertex(self.is_abs, self.end[0], self.end[1], variables)
        end = complex(*end)

        arc = Arc(start, radius, xaxis_rot, flag1, flag2, end)

        theta = 1/num_verts
        for i in range(1, num_verts+1):
            v1 = x, y = arc.point(theta * i)
            v1_index = interpreter.new_vertex(x, y)
            interpreter.new_edge(v0_index, v1_index)
            v0_index = v1_index

        curve = SvCircle.from_arc(arc, z_axis=interpreter.z_axis)
        interpreter.new_curve(curve, self)

        interpreter.position = v1
        interpreter.new_knot("A.#", *v1)
        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

Ancestors

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    variables = set()
    variables.update(self.radii[0].get_variables())
    variables.update(self.radii[1].get_variables())
    variables.update(self.rot.get_variables())
    variables.update(self.flag1.get_variables())
    variables.update(self.flag2.get_variables())
    variables.update(self.end[0].get_variables())
    variables.update(self.end[1].get_variables())
    if self.num_verts:
        variables.update(self.num_verts.get_variables())
    return variables
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    interpreter.assert_not_closed()
    interpreter.start_new_segment()

    v0 = interpreter.position
    if interpreter.has_last_vertex:
        v0_index = interpreter.get_last_vertex()
    else:
        v0_index = interpreter.new_vertex(*v0)

    start = complex(*v0)
    rad_x_expr, rad_y_expr = self.radii
    rad_x = interpreter.eval_(rad_x_expr, variables)
    rad_y = interpreter.eval_(rad_y_expr, variables)
    radius = complex(rad_x, rad_y)
    xaxis_rot = interpreter.eval_(self.rot, variables)
    flag1 = interpreter.eval_(self.flag1, variables)
    flag2 = interpreter.eval_(self.flag2, variables)

    # numverts, requires -1 else it means segments (21 verts is 20 segments).
    if self.num_verts is not None:
        num_verts = interpreter.eval_(self.num_verts, variables)
    else:
        num_verts = interpreter.dflt_num_verts
    num_verts -= 1

    end = interpreter.calc_vertex(self.is_abs, self.end[0], self.end[1], variables)
    end = complex(*end)

    arc = Arc(start, radius, xaxis_rot, flag1, flag2, end)

    theta = 1/num_verts
    for i in range(1, num_verts+1):
        v1 = x, y = arc.point(theta * i)
        v1_index = interpreter.new_vertex(x, y)
        interpreter.new_edge(v0_index, v1_index)
        v0_index = v1_index

    curve = SvCircle.from_arc(arc, z_axis=interpreter.z_axis)
    interpreter.new_curve(curve, self)

    interpreter.position = v1
    interpreter.new_knot("A.#", *v1)
    if self.close:
        interpreter.close_segment(v1_index)

    interpreter.has_last_vertex = True
class Assign (name, value)
Expand source code
class Assign(Default):
    def __repr__(self):
        return "let {} = {}".format(self.name, self.value)

    def __eq__(self, other):
        return isinstance(other, Assign) and \
                self.name == other.name and \
                self.value == other.value

    def get_hidden_inputs(self):
        return set([self.name])

Ancestors

Methods

def get_hidden_inputs(self)
Expand source code
def get_hidden_inputs(self):
    return set([self.name])
class CloseAll
Expand source code
class CloseAll(Statement):
    def __init__(self):
        pass

    def __repr__(self):
        return "X"

    def __eq__(self, other):
        return isinstance(other, CloseAll)

    def get_variables(self):
        return set()

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        if not interpreter.has_last_vertex:
            sv_logger.info("X statement: no current point, do nothing")
            return

        v0 = interpreter.vertices[0]
        v1 = interpreter.vertices[-1]

        distance = (Vector(v0) - Vector(v1)).length

        if distance < interpreter.close_threshold:
            interpreter.pop_last_vertex()

        v1_index = interpreter.get_last_vertex()
        interpreter.new_edge(v1_index, 0)

        interpreter.new_line_segment(v1, v0)

        interpreter.closed = True

Ancestors

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    return set()
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    interpreter.assert_not_closed()
    if not interpreter.has_last_vertex:
        sv_logger.info("X statement: no current point, do nothing")
        return

    v0 = interpreter.vertices[0]
    v1 = interpreter.vertices[-1]

    distance = (Vector(v0) - Vector(v1)).length

    if distance < interpreter.close_threshold:
        interpreter.pop_last_vertex()

    v1_index = interpreter.get_last_vertex()
    interpreter.new_edge(v1_index, 0)

    interpreter.new_line_segment(v1, v0)

    interpreter.closed = True
class ClosePath
Expand source code
class ClosePath(Statement):
    def __init__(self):
        pass

    def __repr__(self):
        return "x"

    def __eq__(self, other):
        return isinstance(other, ClosePath)

    def get_variables(self):
        return set()

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        if not interpreter.has_last_vertex:
            sv_logger.info("X statement: no current point, do nothing")
            return

        v0 = interpreter.vertices[interpreter.close_first_index]
        v1 = interpreter.vertices[-1]

        distance = (Vector(v0) - Vector(v1)).length

        if distance < interpreter.close_threshold:
            interpreter.pop_last_vertex()

        v1_index = interpreter.get_last_vertex()
        interpreter.new_edge(v1_index, interpreter.close_first_index)
        interpreter.new_line_segment(v1_index, interpreter.close_first_index)
        interpreter.close_first_index = interpreter.next_vertex_index

Ancestors

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    return set()
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    interpreter.assert_not_closed()
    if not interpreter.has_last_vertex:
        sv_logger.info("X statement: no current point, do nothing")
        return

    v0 = interpreter.vertices[interpreter.close_first_index]
    v1 = interpreter.vertices[-1]

    distance = (Vector(v0) - Vector(v1)).length

    if distance < interpreter.close_threshold:
        interpreter.pop_last_vertex()

    v1_index = interpreter.get_last_vertex()
    interpreter.new_edge(v1_index, interpreter.close_first_index)
    interpreter.new_line_segment(v1_index, interpreter.close_first_index)
    interpreter.close_first_index = interpreter.next_vertex_index
class Const (value)
Expand source code
class Const(Expression):
    def __init__(self, value):
        self.value = value

    @classmethod
    def from_string(cls, string):
        try:
            return Const( float(string) )
        except ValueError:
            return None

    def __repr__(self):
        return "Const({})".format(self.value)

    def __eq__(self, other):
        return isinstance(other,Const) and self.value == other.value

    def eval_(self, variables):
        return self.value

    def get_variables(self):
        return set()

Ancestors

Static methods

def from_string(string)
Expand source code
@classmethod
def from_string(cls, string):
    try:
        return Const( float(string) )
    except ValueError:
        return None

Methods

def eval_(self, variables)
Expand source code
def eval_(self, variables):
    return self.value
def get_variables(self)
Expand source code
def get_variables(self):
    return set()
class CurveTo (is_abs, segments, num_segments, close)
Expand source code
class CurveTo(Statement):
    class Segment(object):
        def __init__(self, control1, control2, knot2):
            self.control1 = control1
            self.control2 = control2
            self.knot2 = knot2

        def __repr__(self):
            return "{} {} {}".format(self.control1, self.control2, self.knot2)

        def __eq__(self, other):
            return self.control1 == other.control1 and \
                    self.control2 == other.control2 and \
                    self.knot2 == other.knot2

    def __init__(self, is_abs, segments, num_segments, close):
        self.is_abs = is_abs
        self.segments = segments
        self.num_segments = num_segments
        self.close = close

    def get_variables(self):
        variables = set()
        for segment in self.segments:
            variables.update(segment.control1[0].get_variables())
            variables.update(segment.control1[1].get_variables())
            variables.update(segment.control2[0].get_variables())
            variables.update(segment.control2[1].get_variables())
            variables.update(segment.knot2[0].get_variables())
            variables.update(segment.knot2[1].get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "C" if self.is_abs else "c"
        segments = " ".join(str(segment) for segment in self.segments)
        return "{} {} n={} {}".format(letter, segments, self.num_segments, self.close)

    def __eq__(self, other):
        return isinstance(other, CurveTo) and \
                self.is_abs == other.is_abs and \
                self.segments == other.segments and \
                self.num_segments == other.num_segments and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        vec = lambda v: Vector((v[0], v[1], 0))

        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        knot1 = None
        for i, segment in enumerate(self.segments):
            # For first segment, knot1 is initial pen position;
            # for the following, knot1 is knot2 of previous segment.
            if knot1 is None:
                knot1 = interpreter.position
            else:
                knot1 = knot2

            handle1 = interpreter.calc_vertex(self.is_abs, segment.control1[0], segment.control1[1], variables)

            # In Profile mk2, for "c" handle2 was calculated relative to handle1,
            # and knot2 was calculated relative to handle2.
            # But in SVG specification, 
            # >> ...  *At the end of the command*, the new current point becomes
            # >> the final (x,y) coordinate pair used in the polybézier.
            # This is also behaviour of browsers.

            #interpreter.position = handle1
            handle2 = interpreter.calc_vertex(self.is_abs, segment.control2[0], segment.control2[1], variables)
            #interpreter.position = handle2
            knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
            # Judging by the behaviour of Inkscape and Firefox, by "end of command"
            # SVG spec means "end of segment".
            interpreter.position = knot2

            if self.num_segments is not None:
                r = interpreter.eval_(self.num_segments, variables)
            else:
                r = interpreter.dflt_num_verts

            curve = SvCubicBezierCurve(vec(knot1), vec(handle1), vec(handle2), vec(knot2))
            interpreter.new_curve(curve, self)

            points = interpolate_bezier(vec(knot1), vec(handle1), vec(handle2), vec(knot2), r)

            interpreter.new_knot("C#.{}.h1".format(i), *handle1)
            interpreter.new_knot("C#.{}.h2".format(i), *handle2)
            interpreter.new_knot("C#.{}.k".format(i), *knot2)

            interpreter.prev_bezier_knot = handle2

            for point in points[1:]:
                v1_index = interpreter.new_vertex(point.x, point.y)
                interpreter.new_edge(v0_index, v1_index)
                v0_index = v1_index

        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

Ancestors

Class variables

var Segment

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    variables = set()
    for segment in self.segments:
        variables.update(segment.control1[0].get_variables())
        variables.update(segment.control1[1].get_variables())
        variables.update(segment.control2[0].get_variables())
        variables.update(segment.control2[1].get_variables())
        variables.update(segment.knot2[0].get_variables())
        variables.update(segment.knot2[1].get_variables())
    if self.num_segments:
        variables.update(self.num_segments.get_variables())
    return variables
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    vec = lambda v: Vector((v[0], v[1], 0))

    interpreter.assert_not_closed()
    interpreter.start_new_segment()

    v0 = interpreter.position
    if interpreter.has_last_vertex:
        v0_index = interpreter.get_last_vertex()
    else:
        v0_index = interpreter.new_vertex(*v0)

    knot1 = None
    for i, segment in enumerate(self.segments):
        # For first segment, knot1 is initial pen position;
        # for the following, knot1 is knot2 of previous segment.
        if knot1 is None:
            knot1 = interpreter.position
        else:
            knot1 = knot2

        handle1 = interpreter.calc_vertex(self.is_abs, segment.control1[0], segment.control1[1], variables)

        # In Profile mk2, for "c" handle2 was calculated relative to handle1,
        # and knot2 was calculated relative to handle2.
        # But in SVG specification, 
        # >> ...  *At the end of the command*, the new current point becomes
        # >> the final (x,y) coordinate pair used in the polybézier.
        # This is also behaviour of browsers.

        #interpreter.position = handle1
        handle2 = interpreter.calc_vertex(self.is_abs, segment.control2[0], segment.control2[1], variables)
        #interpreter.position = handle2
        knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
        # Judging by the behaviour of Inkscape and Firefox, by "end of command"
        # SVG spec means "end of segment".
        interpreter.position = knot2

        if self.num_segments is not None:
            r = interpreter.eval_(self.num_segments, variables)
        else:
            r = interpreter.dflt_num_verts

        curve = SvCubicBezierCurve(vec(knot1), vec(handle1), vec(handle2), vec(knot2))
        interpreter.new_curve(curve, self)

        points = interpolate_bezier(vec(knot1), vec(handle1), vec(handle2), vec(knot2), r)

        interpreter.new_knot("C#.{}.h1".format(i), *handle1)
        interpreter.new_knot("C#.{}.h2".format(i), *handle2)
        interpreter.new_knot("C#.{}.k".format(i), *knot2)

        interpreter.prev_bezier_knot = handle2

        for point in points[1:]:
            v1_index = interpreter.new_vertex(point.x, point.y)
            interpreter.new_edge(v0_index, v1_index)
            v0_index = v1_index

    if self.close:
        interpreter.close_segment(v1_index)

    interpreter.has_last_vertex = True
class Default (name, value)
Expand source code
class Default(Statement):
    def __init__(self, name, value):
        self.name = name
        self.value = value

    def __repr__(self):
        return "default {} = {}".format(self.name, self.value)

    def __eq__(self, other):
        return isinstance(other, Default) and \
                self.name == other.name and \
                self.value == other.value

    def get_variables(self):
        return self.value.get_variables()

    def get_optional_inputs(self):
        return set([self.name])

    def interpret(self, interpreter, variables):
        if self.name in interpreter.defaults:
            raise Exception("Value for the `{}' variable has been already assigned!".format(self.name))
        if self.name not in interpreter.input_names:
            value = interpreter.eval_(self.value, variables)
            interpreter.defaults[self.name] = value

Ancestors

Subclasses

Methods

def get_optional_inputs(self)
Expand source code
def get_optional_inputs(self):
    return set([self.name])
def get_variables(self)
Expand source code
def get_variables(self):
    return self.value.get_variables()
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    if self.name in interpreter.defaults:
        raise Exception("Value for the `{}' variable has been already assigned!".format(self.name))
    if self.name not in interpreter.input_names:
        value = interpreter.eval_(self.value, variables)
        interpreter.defaults[self.name] = value
class Expression (expr, string)
Expand source code
class Expression(object):
    def __init__(self, expr, string):
        self.expr = expr
        self.string = string

    def __repr__(self):
        return "Expr({})".format(self.string)

    def __eq__(self, other):
        # Proper comparison of ast.Expression would be too complex to implement
        # (it is not implemented in the ast module).
        return isinstance(other, Expression) and self.string == other.string

    @classmethod
    def from_string(cls, string):
        try:
            string = string[1:][:-1]
            expr = ast.parse(string, mode='eval')
            return Expression(expr, string)
        except Exception as e:
            print(e)
            print(string)
            return None

    def eval_(self, variables):
        env = dict()
        env.update(safe_names)
        env.update(variables)
        env["__builtins__"] = {}
        return eval(compile(self.expr, "<expression>", 'eval'), env)

    def get_variables(self):
        result = {node.id for node in ast.walk(self.expr) if isinstance(node, ast.Name)}
        return result.difference(safe_names.keys())

Subclasses

Static methods

def from_string(string)
Expand source code
@classmethod
def from_string(cls, string):
    try:
        string = string[1:][:-1]
        expr = ast.parse(string, mode='eval')
        return Expression(expr, string)
    except Exception as e:
        print(e)
        print(string)
        return None

Methods

def eval_(self, variables)
Expand source code
def eval_(self, variables):
    env = dict()
    env.update(safe_names)
    env.update(variables)
    env["__builtins__"] = {}
    return eval(compile(self.expr, "<expression>", 'eval'), env)
def get_variables(self)
Expand source code
def get_variables(self):
    result = {node.id for node in ast.walk(self.expr) if isinstance(node, ast.Name)}
    return result.difference(safe_names.keys())
class HorizontalLineTo (is_abs, xs, num_segments)
Expand source code
class HorizontalLineTo(Statement):
    def __init__(self, is_abs, xs, num_segments):
        self.is_abs = is_abs
        self.xs = xs
        self.num_segments = num_segments

    def get_variables(self):
        variables = set()
        for x in self.xs:
            variables.update(x.get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "H" if self.is_abs else "h"
        return "{} {} n={};".format(letter, self.xs, self.num_segments)

    def __eq__(self, other):
        return isinstance(other, HorizontalLineTo) and \
                self.is_abs == other.is_abs and \
                self.num_segments == other.num_segments and \
                self.xs == other.xs

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()
        v0 = interpreter.position
        if interpreter.has_last_vertex:
            prev_index = interpreter.get_last_vertex()
        else:
            prev_index = interpreter.new_vertex(*v0)

        if self.num_segments is not None:
            num_segments = interpreter.eval_(self.num_segments, variables)
        else:
            num_segments = None

        for i, x_expr in enumerate(self.xs):
            x0,y0 = interpreter.position
            x = interpreter.eval_(x_expr, variables)
            if not self.is_abs:
                x = x0 + x
            v1 = (x, y0)
            interpreter.position = v1
            verts = self._interpolate(v0, v1, num_segments)
            # sv_logger.debug("V0 %s, v1 %s, N %s => %s", v0, v1, num_segments, verts)
            for vertex in verts[1:]:
                v_index = interpreter.new_vertex(*vertex)
                interpreter.new_edge(prev_index, v_index)
                prev_index = v_index
            interpreter.new_line_segment(v0, v1)
            v0 = v1
            interpreter.new_knot("H#.{}".format(i), *v1)

        interpreter.has_last_vertex = True

Ancestors

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    variables = set()
    for x in self.xs:
        variables.update(x.get_variables())
    if self.num_segments:
        variables.update(self.num_segments.get_variables())
    return variables
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    interpreter.assert_not_closed()
    interpreter.start_new_segment()
    v0 = interpreter.position
    if interpreter.has_last_vertex:
        prev_index = interpreter.get_last_vertex()
    else:
        prev_index = interpreter.new_vertex(*v0)

    if self.num_segments is not None:
        num_segments = interpreter.eval_(self.num_segments, variables)
    else:
        num_segments = None

    for i, x_expr in enumerate(self.xs):
        x0,y0 = interpreter.position
        x = interpreter.eval_(x_expr, variables)
        if not self.is_abs:
            x = x0 + x
        v1 = (x, y0)
        interpreter.position = v1
        verts = self._interpolate(v0, v1, num_segments)
        # sv_logger.debug("V0 %s, v1 %s, N %s => %s", v0, v1, num_segments, verts)
        for vertex in verts[1:]:
            v_index = interpreter.new_vertex(*vertex)
            interpreter.new_edge(prev_index, v_index)
            prev_index = v_index
        interpreter.new_line_segment(v0, v1)
        v0 = v1
        interpreter.new_knot("H#.{}".format(i), *v1)

    interpreter.has_last_vertex = True
class InterpolatedCurveTo (is_abs, degree, points, num_segments, metric, is_smooth, close)
Expand source code
class InterpolatedCurveTo(Statement):
    def __init__(self, is_abs, degree, points, num_segments, metric, is_smooth, close):
        self.is_abs = is_abs
        self.degree = degree
        self.points = points
        self.metric = metric
        self.num_segments = num_segments
        self.is_smooth = is_smooth
        self.close = close

    def get_variables(self):
        variables = set()
        variables.update(self.degree.get_variables())
        for point in self.points:
            variables.update(point[0].get_variables())
            variables.update(point[1].get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "I" if self.is_abs else "i"
        points = " ".join(str(point) for point in self.points)
        return f"{letter} {self.degree} {points} n={self.num_segments} {self.close}"

    def __eq__(self, other):
        return isinstance(other, InterpolatedCurveTo) and \
                self.is_abs == other.is_abs and \
                self.points == other.points and \
                self.degree == other.degree and \
                self.metric == other.metric and \
                self.num_segments == other.num_segments and \
                self.is_smooth == other.is_smooth and \
                self.close == other.close

    def _make_curve(self, interpreter, degree, points):
        points = np.array(points)
        prev_control_point = None
        if degree == 2 and interpreter.prev_quad_bezier_knot is not None:
            prev_control_point = interpreter.to3d_np(interpreter.prev_quad_bezier_knot)
        elif degree == 3 and interpreter.prev_bezier_knot is not None:
            prev_control_point = interpreter.to3d_np(interpreter.prev_bezier_knot)
        if self.is_smooth and prev_control_point is not None:
            solver = prepare_solver_for_interpolation(degree,
                                points,
                                metric = self.metric,
                                cyclic = self.close)
            pos = interpreter.to3d_np(interpreter.position)
            new_control_point = pos + (pos - prev_control_point)
            print(f"Prev CP: {prev_control_point}, pos: {pos}, new CP: {new_control_point}")
            solver.add_goal(SvNurbsCurveControlPoints.single(1, new_control_point, relative=False))
            knotvector = solver.get_knotvector()
            n_cpts = solver.guess_n_control_points()
            knotvector = sv_knotvector.add_one_by_resampling(knotvector, index=1, degree=degree)
            solver.set_curve_params(n_cpts, knotvector)
            curve = solver.solve_welldetermined()
        else:
            curve = SvNurbsMaths.interpolate_curve(SvNurbsMaths.NATIVE, degree,
                                points,
                                metric = self.metric,
                                cyclic = self.close)
        return curve

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        interpolated_points = [[v0[0], v0[1], 0.0]]
        for i, pt in enumerate(self.points):
            knot = interpreter.calc_vertex(self.is_abs, pt[0], pt[1], variables)
            interpreter.new_knot(f"I#.{i}", knot[0], knot[1])
            interpolated_points.append([knot[0], knot[1], 0.0])

        degree = interpreter.eval_(self.degree, variables)
        curve = self._make_curve(interpreter, degree, interpolated_points)
        interpreter.new_curve(curve, self)
        interpreter.position = knot

        cpts = curve.get_control_points()
        if degree == 2:
            interpreter.prev_quad_bezier_knot = (cpts[-2][0], cpts[-2][1])
        elif degree == 3:
            interpreter.prev_bezier_knot = (cpts[-2][0], cpts[-2][1])

        if self.num_segments is not None:
            r = interpreter.eval_(self.num_segments, variables)
        else:
            r = interpreter.dflt_num_verts

        t_min, t_max = curve.get_u_bounds()
        ts = np.linspace(t_min, t_max, num = r)
        points = curve.evaluate_array(ts)

        for point in points[1:]:
            v1_index = interpreter.new_vertex(point[0], point[1])
            interpreter.new_edge(v0_index, v1_index)
            v0_index = v1_index

        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

Ancestors

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    variables = set()
    variables.update(self.degree.get_variables())
    for point in self.points:
        variables.update(point[0].get_variables())
        variables.update(point[1].get_variables())
    if self.num_segments:
        variables.update(self.num_segments.get_variables())
    return variables
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    interpreter.assert_not_closed()
    interpreter.start_new_segment()

    v0 = interpreter.position
    if interpreter.has_last_vertex:
        v0_index = interpreter.get_last_vertex()
    else:
        v0_index = interpreter.new_vertex(*v0)

    interpolated_points = [[v0[0], v0[1], 0.0]]
    for i, pt in enumerate(self.points):
        knot = interpreter.calc_vertex(self.is_abs, pt[0], pt[1], variables)
        interpreter.new_knot(f"I#.{i}", knot[0], knot[1])
        interpolated_points.append([knot[0], knot[1], 0.0])

    degree = interpreter.eval_(self.degree, variables)
    curve = self._make_curve(interpreter, degree, interpolated_points)
    interpreter.new_curve(curve, self)
    interpreter.position = knot

    cpts = curve.get_control_points()
    if degree == 2:
        interpreter.prev_quad_bezier_knot = (cpts[-2][0], cpts[-2][1])
    elif degree == 3:
        interpreter.prev_bezier_knot = (cpts[-2][0], cpts[-2][1])

    if self.num_segments is not None:
        r = interpreter.eval_(self.num_segments, variables)
    else:
        r = interpreter.dflt_num_verts

    t_min, t_max = curve.get_u_bounds()
    ts = np.linspace(t_min, t_max, num = r)
    points = curve.evaluate_array(ts)

    for point in points[1:]:
        v1_index = interpreter.new_vertex(point[0], point[1])
        interpreter.new_edge(v0_index, v1_index)
        v0_index = v1_index

    if self.close:
        interpreter.close_segment(v1_index)

    interpreter.has_last_vertex = True
class Interpreter (node, input_names, curves_form=None, force_curves_form=False, z_axis='Z')
Expand source code
class Interpreter(object):

    NURBS = 'NURBS'
    BEZIER = 'BEZIER'

    def __init__(self, node, input_names, curves_form = None, force_curves_form = False, z_axis='Z'):
        self.position = (0, 0)
        self.next_vertex_index = 0
        self.segment_start_index = 0
        self.segment_continues_line = False
        self.segment_number = 0
        self.has_last_vertex = False
        self.closed = False
        self.close_first_index = 0
        self.prev_bezier_knot = None
        self.prev_quad_bezier_knot = None
        self.curves = []
        self.vertices = []
        self.edges = []
        self.knots = []
        self.knotnames = []
        self.dflt_num_verts = node.curve_points_count
        self.close_threshold = node.close_threshold
        self.defaults = dict()
        self.input_names = input_names
        self.curves_form = curves_form
        self.force_curves_form = force_curves_form
        self.z_axis = z_axis

    def to3d(self, vertex):
        if self.z_axis == 'X':
            return Vector((0, vertex[0], vertex[1]))
        elif self.z_axis == 'Y':
            return Vector((vertex[0], 0, vertex[1]))
        else: # self.z_axis == 'Z':
            return Vector((vertex[0], vertex[1], 0))

    def to3d_np(self, vertex):
        if self.z_axis == 'X':
            return np.array((0, vertex[0], vertex[1]))
        elif self.z_axis == 'Y':
            return np.array((vertex[0], 0, vertex[1]))
        else: # self.z_axis == 'Z':
            return np.array((vertex[0], vertex[1], 0))

    def assert_not_closed(self):
        if self.closed:
            raise Exception("Path was already closed, will not process any further directives!")

    def relative(self, x, y):
        x0, y0 = self.position
        return x0+x, y0+y

    def calc_vertex(self, is_abs, x_expr, y_expr, variables):
        x = self.eval_(x_expr, variables)
        y = self.eval_(y_expr, variables)
        if is_abs:
            return x,y
        else:
            return self.relative(x,y)

    def new_vertex(self, x, y):
        index = self.next_vertex_index
        vertex = (x, y)
        self.vertices.append(vertex)
        self.next_vertex_index += 1
        return index

    def new_edge(self, v1, v2):
        self.edges.append((v1, v2))

    def new_knot(self, name, x, y):
        self.knots.append((x, y))
        name = name.replace("#", str(self.segment_number))
        self.knotnames.append(name)

    def new_curve(self, curve, statement):
        if self.curves_form == Interpreter.NURBS:
            if hasattr(curve, 'to_nurbs'):
                curve = curve.to_nurbs()
            else:
                if self.force_curves_form:
                    raise Exception(f"Cannot convert curve to NURBS: {statement}")
        elif self.curves_form == Interpreter.BEZIER:
            if not isinstance(curve, (SvBezierCurve, SvCubicBezierCurve)):
                if hasattr(curve, 'to_bezier'):
                    curve = curve.to_bezier()
                else:
                    if self.force_curves_form:
                        raise Exception("Cannot convert curve to Bezier: {statement}")
        self.curves.append(curve)

    def new_line_segment(self, v1, v2):
        if isinstance(v1, int):
            v1, v2 = self.vertices[v1], self.vertices[v2]
        v1, v2 = self.to3d(v1), self.to3d(v2)
        if (v1 - v2).length < self.close_threshold:
            return
        curve = SvLine.from_two_points(v1, v2)
        self.new_curve(curve, None)

    def start_new_segment(self):
        self.segment_start_index = self.next_vertex_index
        self.segment_continues_line = self.has_last_vertex
        self.segment_number += 1

    def close_segment(self, v_index):
        if self.segment_continues_line:
            start_index = self.segment_start_index-1
        else:
            start_index = self.segment_start_index
        self.new_edge(v_index, start_index)

    def get_last_vertex(self):
        return self.next_vertex_index - 1

    def pop_last_vertex(self):
        self.vertices.pop()
        self.next_vertex_index -= 1
        is_not_last = lambda e: e[0] != self.next_vertex_index and e[1] != self.next_vertex_index
        self.edges = list(filter(is_not_last, self.edges))

    def eval_(self, expr, variables):
        variables_ = self.defaults.copy()
        for name in variables:
            value = variables[name]
            if value is not None:
                variables_[name] = value
        return expr.eval_(variables_)

    def interpret(self, profile, variables):
        if not profile:
            return
        for statement in profile:
            sv_logger.debug("Interpret: %s", statement)
            statement.interpret(self, variables)

Class variables

var BEZIER
var NURBS

Methods

def assert_not_closed(self)
Expand source code
def assert_not_closed(self):
    if self.closed:
        raise Exception("Path was already closed, will not process any further directives!")
def calc_vertex(self, is_abs, x_expr, y_expr, variables)
Expand source code
def calc_vertex(self, is_abs, x_expr, y_expr, variables):
    x = self.eval_(x_expr, variables)
    y = self.eval_(y_expr, variables)
    if is_abs:
        return x,y
    else:
        return self.relative(x,y)
def close_segment(self, v_index)
Expand source code
def close_segment(self, v_index):
    if self.segment_continues_line:
        start_index = self.segment_start_index-1
    else:
        start_index = self.segment_start_index
    self.new_edge(v_index, start_index)
def eval_(self, expr, variables)
Expand source code
def eval_(self, expr, variables):
    variables_ = self.defaults.copy()
    for name in variables:
        value = variables[name]
        if value is not None:
            variables_[name] = value
    return expr.eval_(variables_)
def get_last_vertex(self)
Expand source code
def get_last_vertex(self):
    return self.next_vertex_index - 1
def interpret(self, profile, variables)
Expand source code
def interpret(self, profile, variables):
    if not profile:
        return
    for statement in profile:
        sv_logger.debug("Interpret: %s", statement)
        statement.interpret(self, variables)
def new_curve(self, curve, statement)
Expand source code
def new_curve(self, curve, statement):
    if self.curves_form == Interpreter.NURBS:
        if hasattr(curve, 'to_nurbs'):
            curve = curve.to_nurbs()
        else:
            if self.force_curves_form:
                raise Exception(f"Cannot convert curve to NURBS: {statement}")
    elif self.curves_form == Interpreter.BEZIER:
        if not isinstance(curve, (SvBezierCurve, SvCubicBezierCurve)):
            if hasattr(curve, 'to_bezier'):
                curve = curve.to_bezier()
            else:
                if self.force_curves_form:
                    raise Exception("Cannot convert curve to Bezier: {statement}")
    self.curves.append(curve)
def new_edge(self, v1, v2)
Expand source code
def new_edge(self, v1, v2):
    self.edges.append((v1, v2))
def new_knot(self, name, x, y)
Expand source code
def new_knot(self, name, x, y):
    self.knots.append((x, y))
    name = name.replace("#", str(self.segment_number))
    self.knotnames.append(name)
def new_line_segment(self, v1, v2)
Expand source code
def new_line_segment(self, v1, v2):
    if isinstance(v1, int):
        v1, v2 = self.vertices[v1], self.vertices[v2]
    v1, v2 = self.to3d(v1), self.to3d(v2)
    if (v1 - v2).length < self.close_threshold:
        return
    curve = SvLine.from_two_points(v1, v2)
    self.new_curve(curve, None)
def new_vertex(self, x, y)
Expand source code
def new_vertex(self, x, y):
    index = self.next_vertex_index
    vertex = (x, y)
    self.vertices.append(vertex)
    self.next_vertex_index += 1
    return index
def pop_last_vertex(self)
Expand source code
def pop_last_vertex(self):
    self.vertices.pop()
    self.next_vertex_index -= 1
    is_not_last = lambda e: e[0] != self.next_vertex_index and e[1] != self.next_vertex_index
    self.edges = list(filter(is_not_last, self.edges))
def relative(self, x, y)
Expand source code
def relative(self, x, y):
    x0, y0 = self.position
    return x0+x, y0+y
def start_new_segment(self)
Expand source code
def start_new_segment(self):
    self.segment_start_index = self.next_vertex_index
    self.segment_continues_line = self.has_last_vertex
    self.segment_number += 1
def to3d(self, vertex)
Expand source code
def to3d(self, vertex):
    if self.z_axis == 'X':
        return Vector((0, vertex[0], vertex[1]))
    elif self.z_axis == 'Y':
        return Vector((vertex[0], 0, vertex[1]))
    else: # self.z_axis == 'Z':
        return Vector((vertex[0], vertex[1], 0))
def to3d_np(self, vertex)
Expand source code
def to3d_np(self, vertex):
    if self.z_axis == 'X':
        return np.array((0, vertex[0], vertex[1]))
    elif self.z_axis == 'Y':
        return np.array((vertex[0], 0, vertex[1]))
    else: # self.z_axis == 'Z':
        return np.array((vertex[0], vertex[1], 0))
class LineTo (is_abs, pairs, num_segments, close)
Expand source code
class LineTo(Statement):
    def __init__(self, is_abs, pairs, num_segments, close):
        self.is_abs = is_abs
        self.pairs = pairs
        self.num_segments = num_segments
        self.close = close

    def get_variables(self):
        variables = set()
        for x, y in self.pairs:
            variables.update(x.get_variables())
            variables.update(y.get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "L" if self.is_abs else "l"
        return "{} {} n={} {}".format(letter, self.pairs, self.num_segments, self.close)
    
    def __eq__(self, other):
        return isinstance(other, LineTo) and \
                self.is_abs == other.is_abs and \
                self.pairs == other.pairs and \
                self.num_segments == other.num_segments and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()
        v0 = interpreter.position
        if interpreter.has_last_vertex:
            prev_index = interpreter.get_last_vertex()
        else:
            prev_index = interpreter.new_vertex(*v0)

        if self.num_segments is not None:
            num_segments = interpreter.eval_(self.num_segments, variables)
        else:
            num_segments = None

        for i, (x_expr, y_expr) in enumerate(self.pairs):
            v1 = interpreter.calc_vertex(self.is_abs, x_expr, y_expr, variables)
            interpreter.position = v1
            for vertex in self._interpolate(v0, v1, num_segments)[1:]:
                v_index = interpreter.new_vertex(*vertex)
                interpreter.new_edge(prev_index, v_index)
                prev_index = v_index
            interpreter.new_line_segment(v0, v1)
            v0 = v1
            interpreter.new_knot("L#.{}".format(i), *v1)

        if self.close:
            interpreter.close_segment(v_index)

        interpreter.has_last_vertex = True

Ancestors

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    variables = set()
    for x, y in self.pairs:
        variables.update(x.get_variables())
        variables.update(y.get_variables())
    if self.num_segments:
        variables.update(self.num_segments.get_variables())
    return variables
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    interpreter.assert_not_closed()
    interpreter.start_new_segment()
    v0 = interpreter.position
    if interpreter.has_last_vertex:
        prev_index = interpreter.get_last_vertex()
    else:
        prev_index = interpreter.new_vertex(*v0)

    if self.num_segments is not None:
        num_segments = interpreter.eval_(self.num_segments, variables)
    else:
        num_segments = None

    for i, (x_expr, y_expr) in enumerate(self.pairs):
        v1 = interpreter.calc_vertex(self.is_abs, x_expr, y_expr, variables)
        interpreter.position = v1
        for vertex in self._interpolate(v0, v1, num_segments)[1:]:
            v_index = interpreter.new_vertex(*vertex)
            interpreter.new_edge(prev_index, v_index)
            prev_index = v_index
        interpreter.new_line_segment(v0, v1)
        v0 = v1
        interpreter.new_knot("L#.{}".format(i), *v1)

    if self.close:
        interpreter.close_segment(v_index)

    interpreter.has_last_vertex = True
class MoveTo (is_abs, x, y)
Expand source code
class MoveTo(Statement):
    def __init__(self, is_abs, x, y):
        self.is_abs = is_abs
        self.x = x
        self.y = y

    def __repr__(self):
        letter = "M" if self.is_abs else "m"
        return "{} {} {}".format(letter, self.x, self.y)

    def __eq__(self, other):
        return isinstance(other, MoveTo) and \
                self.is_abs == other.is_abs and \
                self.x == other.x and \
                self.y == other.y

    def get_variables(self):
        variables = set()
        variables.update(self.x.get_variables())
        variables.update(self.y.get_variables())
        return variables

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()
        pos = interpreter.calc_vertex(self.is_abs, self.x, self.y, variables)
        interpreter.position = pos
        interpreter.new_knot("M.#", *pos)
        interpreter.has_last_vertex = False

Ancestors

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    variables = set()
    variables.update(self.x.get_variables())
    variables.update(self.y.get_variables())
    return variables
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    interpreter.assert_not_closed()
    interpreter.start_new_segment()
    pos = interpreter.calc_vertex(self.is_abs, self.x, self.y, variables)
    interpreter.position = pos
    interpreter.new_knot("M.#", *pos)
    interpreter.has_last_vertex = False
class NegatedVariable (name)
Expand source code
class NegatedVariable(Variable):
    @classmethod
    def from_string(cls, string):
        return NegatedVariable(string)

    def eval_(self, variables):
        value = variables.get(self.name, None)
        if value is not None:
            return -value
        else:
            raise SyntaxError("Unknown variable: " + self.name)

    def __repr__(self):
        return "NegatedVariable({})".format(self.name)

    def __eq__(self, other):
        return isinstance(other, NegatedVariable) and self.name == other.name

Ancestors

Static methods

def from_string(string)
Expand source code
@classmethod
def from_string(cls, string):
    return NegatedVariable(string)

Methods

def eval_(self, variables)
Expand source code
def eval_(self, variables):
    value = variables.get(self.name, None)
    if value is not None:
        return -value
    else:
        raise SyntaxError("Unknown variable: " + self.name)
class QuadraticCurveTo (is_abs, segments, num_segments, close)
Expand source code
class QuadraticCurveTo(Statement):
    class Segment(object):
        def __init__(self, control, knot2):
            self.control = control
            self.knot2 = knot2

        def __repr__(self):
            return "{} {}".format(self.control, self.knot2)

        def __eq__(self, other):
            return self.control == other.control and \
                    self.knot2 == other.knot2

    def __init__(self, is_abs, segments, num_segments, close):
        self.is_abs = is_abs
        self.segments = segments
        self.num_segments = num_segments
        self.close = close

    def get_variables(self):
        variables = set()
        for segment in self.segments:
            variables.update(segment.control[0].get_variables())
            variables.update(segment.control[1].get_variables())
            variables.update(segment.knot2[0].get_variables())
            variables.update(segment.knot2[1].get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "Q" if self.is_abs else "q"
        segments = " ".join(str(segment) for segment in self.segments)
        return "{} {} n={} {}".format(letter, segments, self.num_segments, self.close)

    def __eq__(self, other):
        return isinstance(other, QuadraticCurveTo) and \
                self.is_abs == other.is_abs and \
                self.segments == other.segments and \
                self.num_segments == other.num_segments and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        vec = lambda v: Vector((v[0], v[1], 0))

        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        knot1 = None
        for i, segment in enumerate(self.segments):
            # For first segment, knot1 is initial pen position;
            # for the following, knot1 is knot2 of previous segment.
            if knot1 is None:
                knot1 = interpreter.position
            else:
                knot1 = knot2

            handle = interpreter.calc_vertex(self.is_abs, segment.control[0], segment.control[1], variables)
            knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
            interpreter.position = knot2

            if self.num_segments is not None:
                r = interpreter.eval_(self.num_segments, variables)
            else:
                r = interpreter.dflt_num_verts

            curve = SvBezierCurve([vec(knot1), vec(handle), vec(knot2)])
            interpreter.new_curve(curve, self)

            points = interpolate_quadratic_bezier(vec(knot1), vec(handle), vec(knot2), r)

            interpreter.new_knot("Q#.{}.h".format(i), *handle)
            interpreter.new_knot("Q#.{}.k".format(i), *knot2)

            interpreter.prev_quad_bezier_knot = handle

            for point in points[1:]:
                v1_index = interpreter.new_vertex(point.x, point.y)
                interpreter.new_edge(v0_index, v1_index)
                v0_index = v1_index

        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

Ancestors

Class variables

var Segment

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    variables = set()
    for segment in self.segments:
        variables.update(segment.control[0].get_variables())
        variables.update(segment.control[1].get_variables())
        variables.update(segment.knot2[0].get_variables())
        variables.update(segment.knot2[1].get_variables())
    if self.num_segments:
        variables.update(self.num_segments.get_variables())
    return variables
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    vec = lambda v: Vector((v[0], v[1], 0))

    interpreter.assert_not_closed()
    interpreter.start_new_segment()

    v0 = interpreter.position
    if interpreter.has_last_vertex:
        v0_index = interpreter.get_last_vertex()
    else:
        v0_index = interpreter.new_vertex(*v0)

    knot1 = None
    for i, segment in enumerate(self.segments):
        # For first segment, knot1 is initial pen position;
        # for the following, knot1 is knot2 of previous segment.
        if knot1 is None:
            knot1 = interpreter.position
        else:
            knot1 = knot2

        handle = interpreter.calc_vertex(self.is_abs, segment.control[0], segment.control[1], variables)
        knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
        interpreter.position = knot2

        if self.num_segments is not None:
            r = interpreter.eval_(self.num_segments, variables)
        else:
            r = interpreter.dflt_num_verts

        curve = SvBezierCurve([vec(knot1), vec(handle), vec(knot2)])
        interpreter.new_curve(curve, self)

        points = interpolate_quadratic_bezier(vec(knot1), vec(handle), vec(knot2), r)

        interpreter.new_knot("Q#.{}.h".format(i), *handle)
        interpreter.new_knot("Q#.{}.k".format(i), *knot2)

        interpreter.prev_quad_bezier_knot = handle

        for point in points[1:]:
            v1_index = interpreter.new_vertex(point.x, point.y)
            interpreter.new_edge(v0_index, v1_index)
            v0_index = v1_index

    if self.close:
        interpreter.close_segment(v1_index)

    interpreter.has_last_vertex = True
class SmoothCurveTo (is_abs, segments, num_segments, close)
Expand source code
class SmoothCurveTo(Statement):
    class Segment(object):
        def __init__(self, control2, knot2):
            self.control2 = control2
            self.knot2 = knot2

        def __repr__(self):
            return "{} {}".format(self.control2, self.knot2)

        def __eq__(self, other):
            return self.control2 == other.control2 and \
                    self.knot2 == other.knot2

    def __init__(self, is_abs, segments, num_segments, close):
        self.is_abs = is_abs
        self.segments = segments
        self.num_segments = num_segments
        self.close = close

    def get_variables(self):
        variables = set()
        for segment in self.segments:
            variables.update(segment.control2[0].get_variables())
            variables.update(segment.control2[1].get_variables())
            variables.update(segment.knot2[0].get_variables())
            variables.update(segment.knot2[1].get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "S" if self.is_abs else "s"
        segments = " ".join(str(segment) for segment in self.segments)
        return "{} {} n={} {}".format(letter, segments, self.num_segments, self.close)

    def __eq__(self, other):
        return isinstance(other, SmoothCurveTo) and \
                self.is_abs == other.is_abs and \
                self.segments == other.segments and \
                self.num_segments == other.num_segments and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        vec = lambda v: Vector((v[0], v[1], 0))

        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        knot1 = None
        for i, segment in enumerate(self.segments):
            # For first segment, knot1 is initial pen position;
            # for the following, knot1 is knot2 of previous segment.
            if knot1 is None:
                knot1 = interpreter.position
            else:
                knot1 = knot2

            if interpreter.prev_bezier_knot is None:
                # If there is no previous command or if the previous command was
                # not an C, c, S or s, assume the first control point is coincident
                # with the current point.
                handle1 = knot1
            else:
                # The first control point is assumed to be the reflection of the
                # second control point on the previous command relative to the
                # current point. 
                prev_knot_x, prev_knot_y = interpreter.prev_bezier_knot
                x0, y0 = knot1
                dx, dy = x0 - prev_knot_x, y0 - prev_knot_y
                handle1 = x0 + dx, y0 + dy

            # I assume that handle2 should be relative to knot1, not to handle1.
            # interpreter.position = handle1
            handle2 = interpreter.calc_vertex(self.is_abs, segment.control2[0], segment.control2[1], variables)
            # interpreter.position = handle2
            knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
            interpreter.position = knot2

            if self.num_segments is not None:
                r = interpreter.eval_(self.num_segments, variables)
            else:
                r = interpreter.dflt_num_verts

            curve = SvCubicBezierCurve(vec(knot1), vec(handle1), vec(handle2), vec(knot2))
            interpreter.new_curve(curve, self)

            points = interpolate_bezier(vec(knot1), vec(handle1), vec(handle2), vec(knot2), r)

            interpreter.new_knot("S#.{}.h1".format(i), *handle1)
            interpreter.new_knot("S#.{}.h2".format(i), *handle2)
            interpreter.new_knot("S#.{}.k".format(i), *knot2)

            interpreter.prev_bezier_knot = handle2

            for point in points[1:]:
                v1_index = interpreter.new_vertex(point.x, point.y)
                interpreter.new_edge(v0_index, v1_index)
                v0_index = v1_index

        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

Ancestors

Class variables

var Segment

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    variables = set()
    for segment in self.segments:
        variables.update(segment.control2[0].get_variables())
        variables.update(segment.control2[1].get_variables())
        variables.update(segment.knot2[0].get_variables())
        variables.update(segment.knot2[1].get_variables())
    if self.num_segments:
        variables.update(self.num_segments.get_variables())
    return variables
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    vec = lambda v: Vector((v[0], v[1], 0))

    interpreter.assert_not_closed()
    interpreter.start_new_segment()

    v0 = interpreter.position
    if interpreter.has_last_vertex:
        v0_index = interpreter.get_last_vertex()
    else:
        v0_index = interpreter.new_vertex(*v0)

    knot1 = None
    for i, segment in enumerate(self.segments):
        # For first segment, knot1 is initial pen position;
        # for the following, knot1 is knot2 of previous segment.
        if knot1 is None:
            knot1 = interpreter.position
        else:
            knot1 = knot2

        if interpreter.prev_bezier_knot is None:
            # If there is no previous command or if the previous command was
            # not an C, c, S or s, assume the first control point is coincident
            # with the current point.
            handle1 = knot1
        else:
            # The first control point is assumed to be the reflection of the
            # second control point on the previous command relative to the
            # current point. 
            prev_knot_x, prev_knot_y = interpreter.prev_bezier_knot
            x0, y0 = knot1
            dx, dy = x0 - prev_knot_x, y0 - prev_knot_y
            handle1 = x0 + dx, y0 + dy

        # I assume that handle2 should be relative to knot1, not to handle1.
        # interpreter.position = handle1
        handle2 = interpreter.calc_vertex(self.is_abs, segment.control2[0], segment.control2[1], variables)
        # interpreter.position = handle2
        knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
        interpreter.position = knot2

        if self.num_segments is not None:
            r = interpreter.eval_(self.num_segments, variables)
        else:
            r = interpreter.dflt_num_verts

        curve = SvCubicBezierCurve(vec(knot1), vec(handle1), vec(handle2), vec(knot2))
        interpreter.new_curve(curve, self)

        points = interpolate_bezier(vec(knot1), vec(handle1), vec(handle2), vec(knot2), r)

        interpreter.new_knot("S#.{}.h1".format(i), *handle1)
        interpreter.new_knot("S#.{}.h2".format(i), *handle2)
        interpreter.new_knot("S#.{}.k".format(i), *knot2)

        interpreter.prev_bezier_knot = handle2

        for point in points[1:]:
            v1_index = interpreter.new_vertex(point.x, point.y)
            interpreter.new_edge(v0_index, v1_index)
            v0_index = v1_index

    if self.close:
        interpreter.close_segment(v1_index)

    interpreter.has_last_vertex = True
class SmoothQuadraticCurveTo (is_abs, segments, num_segments, close)
Expand source code
class SmoothQuadraticCurveTo(Statement):
    class Segment(object):
        def __init__(self, knot2):
            self.knot2 = knot2

        def __repr__(self):
            return str(self.knot2)

        def __eq__(self, other):
            return self.knot2 == other.knot2

    def __init__(self, is_abs, segments, num_segments, close):
        self.is_abs = is_abs
        self.segments = segments
        self.num_segments = num_segments
        self.close = close

    def get_variables(self):
        variables = set()
        for segment in self.segments:
            variables.update(segment.knot2[0].get_variables())
            variables.update(segment.knot2[1].get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "T" if self.is_abs else "t"
        segments = " ".join(str(segment) for segment in self.segments)
        return "{} {} n={} {}".format(letter, segments, self.num_segments, self.close)

    def __eq__(self, other):
        return isinstance(other, SmoothQuadraticCurveTo) and \
                self.is_abs == other.is_abs and \
                self.segments == other.segments and \
                self.num_segments == other.num_segments and \
                self.close == other.close

    def interpret(self, interpreter, variables):
        vec = lambda v: Vector((v[0], v[1], 0))

        interpreter.assert_not_closed()
        interpreter.start_new_segment()

        v0 = interpreter.position
        if interpreter.has_last_vertex:
            v0_index = interpreter.get_last_vertex()
        else:
            v0_index = interpreter.new_vertex(*v0)

        knot1 = None
        for i, segment in enumerate(self.segments):
            # For first segment, knot1 is initial pen position;
            # for the following, knot1 is knot2 of previous segment.
            if knot1 is None:
                knot1 = interpreter.position
            else:
                knot1 = knot2

            if interpreter.prev_quad_bezier_knot is None:
                # If there is no previous command or if the previous command was
                # not a Q, q, T or t, assume the control point is coincident with
                # the current point.
                handle = knot1
            else:
                # The first control point is assumed to be the reflection of the
                # second control point on the previous command relative to the
                # current point. 
                prev_knot_x, prev_knot_y = interpreter.prev_quad_bezier_knot
                x0, y0 = knot1
                dx, dy = x0 - prev_knot_x, y0 - prev_knot_y
                handle = x0 + dx, y0 + dy

            knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
            interpreter.position = knot2

            if self.num_segments is not None:
                r = interpreter.eval_(self.num_segments, variables)
            else:
                r = interpreter.dflt_num_verts

            curve = SvBezierCurve([vec(knot1), vec(handle), vec(knot2)])
            interpreter.new_curve(curve, self)

            points = interpolate_quadratic_bezier(vec(knot1), vec(handle), vec(knot2), r)

            interpreter.new_knot("T#.{}.h".format(i), *handle)
            interpreter.new_knot("T#.{}.k".format(i), *knot2)

            interpreter.prev_quad_bezier_knot = handle

            for point in points[1:]:
                v1_index = interpreter.new_vertex(point.x, point.y)
                interpreter.new_edge(v0_index, v1_index)
                v0_index = v1_index

        if self.close:
            interpreter.close_segment(v1_index)

        interpreter.has_last_vertex = True

Ancestors

Class variables

var Segment

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    variables = set()
    for segment in self.segments:
        variables.update(segment.knot2[0].get_variables())
        variables.update(segment.knot2[1].get_variables())
    if self.num_segments:
        variables.update(self.num_segments.get_variables())
    return variables
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    vec = lambda v: Vector((v[0], v[1], 0))

    interpreter.assert_not_closed()
    interpreter.start_new_segment()

    v0 = interpreter.position
    if interpreter.has_last_vertex:
        v0_index = interpreter.get_last_vertex()
    else:
        v0_index = interpreter.new_vertex(*v0)

    knot1 = None
    for i, segment in enumerate(self.segments):
        # For first segment, knot1 is initial pen position;
        # for the following, knot1 is knot2 of previous segment.
        if knot1 is None:
            knot1 = interpreter.position
        else:
            knot1 = knot2

        if interpreter.prev_quad_bezier_knot is None:
            # If there is no previous command or if the previous command was
            # not a Q, q, T or t, assume the control point is coincident with
            # the current point.
            handle = knot1
        else:
            # The first control point is assumed to be the reflection of the
            # second control point on the previous command relative to the
            # current point. 
            prev_knot_x, prev_knot_y = interpreter.prev_quad_bezier_knot
            x0, y0 = knot1
            dx, dy = x0 - prev_knot_x, y0 - prev_knot_y
            handle = x0 + dx, y0 + dy

        knot2 = interpreter.calc_vertex(self.is_abs, segment.knot2[0], segment.knot2[1], variables)
        interpreter.position = knot2

        if self.num_segments is not None:
            r = interpreter.eval_(self.num_segments, variables)
        else:
            r = interpreter.dflt_num_verts

        curve = SvBezierCurve([vec(knot1), vec(handle), vec(knot2)])
        interpreter.new_curve(curve, self)

        points = interpolate_quadratic_bezier(vec(knot1), vec(handle), vec(knot2), r)

        interpreter.new_knot("T#.{}.h".format(i), *handle)
        interpreter.new_knot("T#.{}.k".format(i), *knot2)

        interpreter.prev_quad_bezier_knot = handle

        for point in points[1:]:
            v1_index = interpreter.new_vertex(point.x, point.y)
            interpreter.new_edge(v0_index, v1_index)
            v0_index = v1_index

    if self.close:
        interpreter.close_segment(v1_index)

    interpreter.has_last_vertex = True
class Statement
Expand source code
class Statement(object):
    
    def get_variables(self):
        return set()

    def get_hidden_inputs(self):
        return set()

    def get_optional_inputs(self):
        return set()

    def _interpolate(self, v0, v1, num_segments):
        if num_segments is None or num_segments <= 1:
            return [v0, v1]
        dx_total, dy_total = v1[0] - v0[0], v1[1] - v0[1]
        dx, dy = dx_total / float(num_segments), dy_total / float(num_segments)
        x, y = v0
        dt = 1.0 / float(num_segments)
        result = []
        t = 0
        for i in range(round(num_segments)):
            result.append((x,y))
            x = x + dx
            y = y + dy
        result.append(v1)
        return result

Subclasses

Methods

def get_hidden_inputs(self)
Expand source code
def get_hidden_inputs(self):
    return set()
def get_optional_inputs(self)
Expand source code
def get_optional_inputs(self):
    return set()
def get_variables(self)
Expand source code
def get_variables(self):
    return set()
class Variable (name)
Expand source code
class Variable(Expression):
    def __init__(self, name):
        self.name = name

    @classmethod
    def from_string(cls, string):
        return Variable(string)

    def __repr__(self):
        return "Variable({})".format(self.name)

    def __eq__(self, other):
        return isinstance(other, Variable) and self.name == other.name

    def eval_(self, variables):
        value = variables.get(self.name, None)
        if value is not None:
            return value
        else:
            raise SyntaxError("Unknown variable: " + self.name)

    def get_variables(self):
        return set([self.name])

Ancestors

Subclasses

Static methods

def from_string(string)
Expand source code
@classmethod
def from_string(cls, string):
    return Variable(string)

Methods

def eval_(self, variables)
Expand source code
def eval_(self, variables):
    value = variables.get(self.name, None)
    if value is not None:
        return value
    else:
        raise SyntaxError("Unknown variable: " + self.name)
def get_variables(self)
Expand source code
def get_variables(self):
    return set([self.name])
class VerticalLineTo (is_abs, ys, num_segments)
Expand source code
class VerticalLineTo(Statement):
    def __init__(self, is_abs, ys, num_segments):
        self.is_abs = is_abs
        self.ys = ys
        self.num_segments = num_segments

    def get_variables(self):
        variables = set()
        for y in self.ys:
            variables.update(y.get_variables())
        if self.num_segments:
            variables.update(self.num_segments.get_variables())
        return variables

    def __repr__(self):
        letter = "V" if self.is_abs else "v"
        return "{} {} n={};".format(letter, self.ys, self.num_segments)

    def __eq__(self, other):
        return isinstance(other, VerticalLineTo) and \
                self.is_abs == other.is_abs and \
                self.num_segments == other.num_segments and \
                self.ys == other.ys

    def interpret(self, interpreter, variables):
        interpreter.assert_not_closed()
        interpreter.start_new_segment()
        v0 = interpreter.position
        if interpreter.has_last_vertex:
            prev_index = interpreter.get_last_vertex()
        else:
            prev_index = interpreter.new_vertex(*v0)

        if self.num_segments is not None:
            num_segments = interpreter.eval_(self.num_segments, variables)
        else:
            num_segments = None

        for i, y_expr in enumerate(self.ys):
            x0,y0 = interpreter.position
            y = interpreter.eval_(y_expr, variables)
            if not self.is_abs:
                y = y0 + y
            v1 = (x0, y)
            interpreter.position = v1
            for vertex in self._interpolate(v0, v1, num_segments)[1:]:
                v_index = interpreter.new_vertex(*vertex)
                interpreter.new_edge(prev_index, v_index)
                prev_index = v_index
            interpreter.new_line_segment(v0, v1)
            v0 = v1
            interpreter.new_knot("V#.{}".format(i), *v1)

        interpreter.has_last_vertex = True

Ancestors

Methods

def get_variables(self)
Expand source code
def get_variables(self):
    variables = set()
    for y in self.ys:
        variables.update(y.get_variables())
    if self.num_segments:
        variables.update(self.num_segments.get_variables())
    return variables
def interpret(self, interpreter, variables)
Expand source code
def interpret(self, interpreter, variables):
    interpreter.assert_not_closed()
    interpreter.start_new_segment()
    v0 = interpreter.position
    if interpreter.has_last_vertex:
        prev_index = interpreter.get_last_vertex()
    else:
        prev_index = interpreter.new_vertex(*v0)

    if self.num_segments is not None:
        num_segments = interpreter.eval_(self.num_segments, variables)
    else:
        num_segments = None

    for i, y_expr in enumerate(self.ys):
        x0,y0 = interpreter.position
        y = interpreter.eval_(y_expr, variables)
        if not self.is_abs:
            y = y0 + y
        v1 = (x0, y)
        interpreter.position = v1
        for vertex in self._interpolate(v0, v1, num_segments)[1:]:
            v_index = interpreter.new_vertex(*vertex)
            interpreter.new_edge(prev_index, v_index)
            prev_index = v_index
        interpreter.new_line_segment(v0, v1)
        v0 = v1
        interpreter.new_knot("V#.{}".format(i), *v1)

    interpreter.has_last_vertex = True