Example #1
0
    def __init__(self,
                 stroke=1,
                 fill=False,
                 color=(0, 1, 0),
                 closed=True,
                 visible=True,
                 path=[dict(x=0, y=0, z=0)],
                 front=dict(z=1),
                 backface=True,
                 **kwargs):
        Anchor.__init__(self, **kwargs)

        self.stroke = stroke
        self.fill = fill
        self.color = color
        self.closed = closed
        self.visible = visible
        self.path = path
        self.backface = backface

        self.updatePath()

        self.front = Vector(**front) if type(front) is dict else front
        self.renderFront = Vector(self.front)
        self.renderNormal = Vector()
Example #2
0
    def renderDome(self, ctx, renderer):
        if not self.visible:
            return

        contourAngle = math.atan2(self.renderNormal.y, self.renderNormal.x)
        domeRadius = self.diameter / 2  # * self.renderNormal.magnitude()

        startAngle = contourAngle + TAU / 2
        endAngle = contourAngle - TAU / 2

        a = startAngle
        pts = [
            Vector(x=domeRadius, y=0),
            Vector(x=domeRadius, y=domeRadius * 4 / 3 * tan(a / 4)),
            Vector(x=domeRadius * (cos(a) + 4 / 3 * tan(a / 4) * sin(a)),
                   y=domeRadius * (sin(a) - 4 / 3 * tan(a / 4) * cos(a))),
            Vector(x=domeRadius * cos(a), y=domeRadius * sin(a)),
        ]
        pts = [p.add(self.renderOrigin) for p in pts]

        renderer.stroke(self.stroke, self.color, self.getLineWidth())
        renderer.fill(self.fill, self.color)
        renderer.begin()
        renderer.move(pts[0])
        renderer.bezier(pts[1], pts[2], pts[3])
        # renderer.closePath()
        ctx.drawPath()
        renderer.end()
Example #3
0
    def __init__(self, method, points, previousPoint=None):

        self.method         = method
        self.points         = [ mapVectorPoint(point) for point in points ]
        self.renderPoints   = [ mapNewVector(point) for point in points ]
        self.previousPoint  = previousPoint

        # arc actions come with previous point & corner point
        # but require bezier control points
        if method == 'arc':
            self.controlPoints = [Vector(), Vector()]
Example #4
0
    def __init__(self, **kwargs):

        if 'length' in kwargs:
            self.length = kwargs['length']

        Ellipse.__init__(self, **kwargs)

        # composite shape, create child shapes
        self.apex = Anchor(
            addTo=self,
            translate={'z': self.length},
        )

        # vectors used for calculation
        self.renderApex = Vector()
        self.tangentA = Vector()
        self.tangentB = Vector()

        self.surfacePathCommands = [
            # points set in renderConeSurface
            PathCommand('move', [{}], None),
            PathCommand('line', [{}], None),
            PathCommand('line', [{}], None),
        ]
Example #5
0
class Cone(Ellipse):

    length = 1
    fill = True

    def __init__(self, **kwargs):

        if 'length' in kwargs:
            self.length = kwargs['length']

        Ellipse.__init__(self, **kwargs)

        # composite shape, create child shapes
        self.apex = Anchor(
            addTo=self,
            translate={'z': self.length},
        )

        # vectors used for calculation
        self.renderApex = Vector()
        self.tangentA = Vector()
        self.tangentB = Vector()

        self.surfacePathCommands = [
            # points set in renderConeSurface
            PathCommand('move', [{}], None),
            PathCommand('line', [{}], None),
            PathCommand('line', [{}], None),
        ]

    def __repr__(self):
        return '<zDogPy Cone>'

    def render(self, ctx, renderer):
        self.renderConeSurface(ctx, renderer)
        Ellipse.render(self, ctx, renderer)

    def renderConeSurface(self, ctx, renderer):
        if not self.visible:
            return

        self.renderApex.set(self.apex.renderOrigin)
        self.renderApex.subtract(self.renderOrigin)

        scale = self.renderNormal.magnitude()
        apexDistance = self.renderApex.magnitude2d()
        normalDistance = self.renderNormal.magnitude2d()

        # eccentricity
        eccenAngle = acos(normalDistance / scale)
        eccen = sin(eccenAngle)
        radius = self.diameter / 2 * scale

        # does apex extend beyond eclipse of face
        isApexVisible = radius * eccen < apexDistance
        if not isApexVisible:
            return

        # update tangents
        apexAngle = atan2(self.renderNormal.y, self.renderNormal.x) + TAU / 2
        projectLength = apexDistance / eccen
        projectAngle = acos(radius / projectLength)

        # set tangent points
        tangentA = self.tangentA
        tangentB = self.tangentB

        tangentA.x = cos(projectAngle) * radius * eccen
        tangentA.y = sin(projectAngle) * radius

        tangentB.set(self.tangentA)
        tangentB.y *= -1

        tangentA.rotateZ(apexAngle)
        tangentB.rotateZ(apexAngle)
        tangentA.add(self.renderOrigin)
        tangentB.add(self.renderOrigin)

        self.setSurfaceRenderPoint(0, tangentA)
        self.setSurfaceRenderPoint(1, self.apex.renderOrigin)
        self.setSurfaceRenderPoint(2, tangentB)

        # render
        renderer.stroke(self.stroke, self.color, self.getLineWidth())
        renderer.fill(self.fill, self.color)
        renderer.begin()
        renderer.renderPath(self.surfacePathCommands)
        renderer.end()

    def setSurfaceRenderPoint(self, index, point):
        renderPoint = self.surfacePathCommands[index].renderPoints[0]
        renderPoint.set(point)
Example #6
0
def mapVectorPoint(point):
    if isinstance(point, Vector):
        return point
    else:
        return Vector(**point)
Example #7
0
def mapNewVector(point):
    return Vector(**point)
Example #8
0
class Shape(Anchor):

    actionNames = [
        'move',
        'line',
        'bezier',
        'arc',
    ]

    pathCommands = []

    def __init__(self,
                 stroke=1,
                 fill=False,
                 color=(0, 1, 0),
                 closed=True,
                 visible=True,
                 path=[dict(x=0, y=0, z=0)],
                 front=dict(z=1),
                 backface=True,
                 **kwargs):
        Anchor.__init__(self, **kwargs)

        self.stroke = stroke
        self.fill = fill
        self.color = color
        self.closed = closed
        self.visible = visible
        self.path = path
        self.backface = backface

        self.updatePath()

        self.front = Vector(**front) if type(front) is dict else front
        self.renderFront = Vector(self.front)
        self.renderNormal = Vector()

    def __repr__(self):
        return '<zDogPy Shape>'

    def updatePath(self):
        self.setPath()
        self.updatePathCommands()

    def setPath(self):
        pass

    def updatePathCommands(self):
        '''Parse path into PathCommands.'''

        if not self.path:
            return

        previousPoint = None

        self.pathCommands = []
        for i, pathPart in enumerate(self.path):

            # pathPart can be just vector coordinates -> { x, y, z }
            # or path instruction -> { arc: [ {x0, y0, z0}, {x1, y1, z1} ] }
            keys = pathPart.keys()
            method = list(keys)[0]
            points = pathPart[method]

            # default to line if no instruction
            isInstruction = len(keys) == 1 and method in self.actionNames
            if not isInstruction:
                method = 'line'
                points = pathPart

            # munge single-point methods like line & move without arrays
            isLineOrMove = method == 'line' or method == 'move'
            isPointsArray = isinstance(points, list)
            if isLineOrMove and not isPointsArray:
                points = [points]

            # first action is always move
            if i == 0:
                method = 'move'
            # arcs require previous last point
            command = PathCommand(method, points, previousPoint)
            # update previousLastPoint
            previousPoint = command.endRenderPoint

            self.pathCommands.append(command)

    # ------
    # update
    # ------

    def reset(self):
        self.renderOrigin.set(self.origin)
        self.renderFront.set(self.front)
        # reset command render points
        for command in self.pathCommands:
            command.reset()

    def transform(self, translation, rotation, scale):
        # calculate render points backface visibility & cone/hemisphere shapes
        self.renderOrigin.transform(translation, rotation, scale)
        self.renderFront.transform(translation, rotation, scale)
        self.renderNormal.set(self.renderOrigin).subtract(self.renderFront)

        # transform points
        for command in self.pathCommands:
            command.transform(translation, rotation, scale)

        # transform children
        for child in self.children:
            child.transform(translation, rotation, scale)

    def updateSortValue(self):
        if not len(self.pathCommands):
            return
        sortValueTotal = 0
        for command in self.pathCommands:
            sortValueTotal += command.endRenderPoint.z
        # average sort value of all points
        # def not geometrically correct, but works for me
        self.sortValue = sortValueTotal / len(self.pathCommands)

    # ------
    # render
    # ------

    def getLineWidth(self):
        if not self.stroke:
            return 0
        if self.stroke == True:
            return 1
        return self.stroke

    def getRenderColor(self):
        # use backface color if applicable
        isBackfaceColor = isinstance(self.backface, str) and self.isFacingBack
        color = self.backface if isBackfaceColor else self.color

        if type(color) is str:
            color = hexToRGB(color)

        return color

    def render(self, ctx, renderer):

        length = len(self.pathCommands)
        if not self.visible or not length:
            return

        # do not render if hiding backface
        self.isFacingBack = self.renderNormal.z > 0
        if not self.backface and not self.isFacingBack:
            return

        if not renderer:
            print(f'Zdog renderer required. Set to {renderer}')

        # render dot or path
        isDot = length == 1
        if isDot:
            self.renderDot(ctx, renderer)
        else:
            self.renderPath(ctx, renderer)

    def renderDot(self, ctx, renderer):

        # render lines with no size as circle
        lineWidth = self.getLineWidth()
        if not lineWidth:
            return

        color = self.getRenderColor()
        point = self.pathCommands[0].endRenderPoint
        radius = lineWidth / 2

        renderer.stroke(False, color, self.getLineWidth())
        renderer.fill(True, color)
        ctx.oval(point.x - radius, point.y - radius, radius * 2, radius * 2)

    def renderPath(self, ctx, renderer):

        isTwoPoints = len(
            self.pathCommands) == 2 and self.pathCommands[1].method == 'line'
        isClosed = not isTwoPoints and self.closed
        color = self.getRenderColor()

        renderer.stroke(self.stroke, color, self.getLineWidth())
        renderer.fill(self.fill, color)
        renderer.renderPath(self.pathCommands, isClosed)
        renderer.end()

    # ----
    # copy
    # ----

    copyAttrs = [
        'stroke',
        'fill',
        'color',
        'closed',
        'visible',
        'path',
        'front',
        'backface',
        'renderFront',
        'renderNormal',
    ] + Anchor.copyAttrs
Example #9
0
from math import sqrt, acos
from zDogPy.illustration import Illustration
from zDogPy.anchor import Anchor
from zDogPy.cylinder import Cylinder
from zDogPy.hemisphere import Hemisphere
from zDogPy.boilerplate import TAU
from zDogPy.vector import Vector
from zDogPy.cone import Cone
from zDogPy.polygon import Polygon

sceneSize = 24
ROOT3 = sqrt(3)
ROOT5 = sqrt(5)
PHI = (1 + ROOT5) / 2

viewRotation = Vector()

eggplant = '#636'
garnet = '#C25'
orange = '#E62'
gold = '#EA0'
yellow = '#ED0'

I = Illustration()
I.setSize(sceneSize, sceneSize)

solids = []

hourglass = Anchor(
    addTo=I,
    translate={
Example #10
0
    def __init__(self,
                 rotate=None,
                 translate=None,
                 scale=None,
                 addTo=None,
                 **kwargs):

        self.addTo = addTo
        self.flatGraph = []
        self.sortValue = 0

        # transform

        if translate is None:
            self.translate = Vector()
        elif isinstance(translate, Vector):
            self.translate = translate
        elif isinstance(translate, dict):
            self.translate = Vector(**translate)
        elif isinstance(translate, float) or isinstance(translate, int):
            self.translate = Vector(translate)

        if rotate is None:
            self.rotate = Vector()
        elif isinstance(translate, Vector):
            self.rotate = rotate
        elif isinstance(rotate, dict):
            self.rotate = Vector(**rotate)
        elif isinstance(rotate, float) or isinstance(rotate, int):
            self.rotate = Vector(rotate)

        if scale is None:
            self.scale = Vector()
        elif isinstance(translate, Vector):
            self.scale = scale
        elif isinstance(scale, dict):
            self.scale = Vector(**scale)
        elif isinstance(scale, float) or isinstance(scale, int):
            self.scale = Vector(scale)

        # origin
        self.origin = Vector()
        self.renderOrigin = Vector()

        # children
        self.children = []
        if self.addTo:
            self.addTo.addChild(self)
Example #11
0
class Anchor(Vector):

    # TODO: make setter/getter for transformation vectors
    # int or float / Vector / dict
    # - _translate
    # - _rotate
    # - _scale

    def __init__(self,
                 rotate=None,
                 translate=None,
                 scale=None,
                 addTo=None,
                 **kwargs):

        self.addTo = addTo
        self.flatGraph = []
        self.sortValue = 0

        # transform

        if translate is None:
            self.translate = Vector()
        elif isinstance(translate, Vector):
            self.translate = translate
        elif isinstance(translate, dict):
            self.translate = Vector(**translate)
        elif isinstance(translate, float) or isinstance(translate, int):
            self.translate = Vector(translate)

        if rotate is None:
            self.rotate = Vector()
        elif isinstance(translate, Vector):
            self.rotate = rotate
        elif isinstance(rotate, dict):
            self.rotate = Vector(**rotate)
        elif isinstance(rotate, float) or isinstance(rotate, int):
            self.rotate = Vector(rotate)

        if scale is None:
            self.scale = Vector()
        elif isinstance(translate, Vector):
            self.scale = scale
        elif isinstance(scale, dict):
            self.scale = Vector(**scale)
        elif isinstance(scale, float) or isinstance(scale, int):
            self.scale = Vector(scale)

        # origin
        self.origin = Vector()
        self.renderOrigin = Vector()

        # children
        self.children = []
        if self.addTo:
            self.addTo.addChild(self)

    def __repr__(self):
        return f'<zDogPy Anchor {self.x} {self.y} {self.z}>'

    def setOptions(self, options):
        if not options:
            return
        for key, value in options.items():
            if hasattr(self, key):
                setattr(self, key, value)

    def addChild(self, shape):
        if shape in self.children:
            # remove previous parent
            shape.remove()
        # keep parent reference
        shape.addTo = self
        self.children.append(shape)

    def removeChild(self, shape):
        index = self.children.index(shape)
        del self.children[index]

    def remove(self):
        if self.addTo:
            self.addTo.removeChild(self)

    # ------
    # update
    # ------

    def update(self):
        # update self
        self.reset()
        # update children
        for child in self.children:
            child.update()
        self.transform(self.translate, self.rotate, self.scale)

    def reset(self):
        self.renderOrigin.set(self.origin)

    def transform(self, translation, rotation, scale):
        self.renderOrigin.transform(translation, rotation, scale)
        # transform children
        for i, child in enumerate(self.children):
            child.transform(translation, rotation, scale)

    def updateGraph(self):
        self.update()
        self.checkFlatGraph()
        for item in self.flatGraph:
            item.updateSortValue()
        # z-sort
        self.flatGraph.sort(key=cmp_to_key(shapeSorter))

    def checkFlatGraph(self):
        if not self.flatGraph:
            self.updateFlatGraph()

    def updateFlatGraph(self):
        self.flatGraph = self.getFlatGraph()

    def getFlatGraph(self):
        # return list of self & all child graph items
        flatGraph = [self]
        for child in self.children:
            childFlatGraph = child.getFlatGraph()
            flatGraph += childFlatGraph
        return flatGraph

    def updateSortValue(self):
        self.sortValue = self.renderOrigin.z

    # ------
    # render
    # ------

    def render(self, ctx, renderer):
        pass

    def renderGraphDrawBot(self):
        self.checkFlatGraph()
        for item in self.flatGraph:
            item.render(ctx, DrawBotRenderer())

    # ----
    # misc
    # ----

    copyAttrs = [
        'addTo',
        'flatGraph',
        'sortValue',
        'origin',
        'renderOrigin',
        'children',
        # 'translate', 'rotate', 'scale'
    ]

    def copy(self, **kwargs):

        attrsDict = {attr: getattr(self, attr) for attr in self.copyAttrs}

        for attr, value in kwargs.items():
            attrsDict[attr] = value

        copyClass = self.__class__
        copyObject = copyClass(**attrsDict)

        return copyObject

    def copyGraph(self, **kwargs):
        clone = self.copy(**kwargs)
        for child in self.children:
            print(child.translate, child.rotate, child.scale)
            child.copyGraph(addTo=clone)
            clone
        return clone

    def normalizeRotate(self):
        self.rotate.x = self.rotate.x % TAU
        self.rotate.y = self.rotate.y % TAU
        self.rotate.z = self.rotate.z % TAU