Skip to content
Snippets Groups Projects
import_svg.py 49.5 KiB
Newer Older
# SPDX-License-Identifier: GPL-2.0-or-later

import re
import xml.dom.minidom
from math import cos, sin, tan, atan2, pi, ceil

import bpy
from mathutils import Vector, Matrix

from . import svg_colors
from .svg_util import (units,
                       srgb_to_linearrgb,
                       parse_array_of_floats,
                       read_float)

#### Common utilities ####

SVGEmptyStyles = {'useFill': None,
                  'fill': None}

Sergey Sharybin's avatar
Sergey Sharybin committed

Sergey Sharybin's avatar
Sergey Sharybin committed
def SVGCreateCurve(context):
    """
    Create new curve object to hold splines in
    """

    cu = bpy.data.curves.new("Curve", 'CURVE')
    obj = bpy.data.objects.new("Curve", cu)
Sergey Sharybin's avatar
Sergey Sharybin committed

    context['collection'].objects.link(obj)

    return obj


def SVGFinishCurve():
    """
    Finish curve creation
    """

    pass


def SVGFlipHandle(x, y, x1, y1):
    """
    Flip handle around base point
    """

    x = x + (x - x1)
    y = y + (y - y1)

    return x, y


def SVGParseCoord(coord, size):
    """
    Parse coordinate component to common basis

    Needed to handle coordinates set in cm, mm, inches.
    token, last_char = read_float(coord)
Sergey Sharybin's avatar
Sergey Sharybin committed
    val = float(token)
    unit = coord[last_char:].strip()  # strip() in case there is a space

    if unit == '%':
        return float(size) / 100.0 * val
    return val * units[unit]


def SVGRectFromNode(node, context):
    """
    Get display rectangle from node
    """

    w = context['rect'][0]
    h = context['rect'][1]

    if node.getAttribute('viewBox'):
        viewBox = node.getAttribute('viewBox').replace(',', ' ').split()
        w = SVGParseCoord(viewBox[2], w)
        h = SVGParseCoord(viewBox[3], h)
    else:
        if node.getAttribute('width'):
            w = SVGParseCoord(node.getAttribute('width'), w)

        if node.getAttribute('height'):
            h = SVGParseCoord(node.getAttribute('height'), h)

    return (w, h)


def SVGMatrixFromNode(node, context):
    """
    Get transformation matrix from given node
    """

Sergey Sharybin's avatar
Sergey Sharybin committed
    tagName = node.tagName.lower()
    tags = ['svg:svg', 'svg:use', 'svg:symbol']

    if tagName not in tags and 'svg:' + tagName not in tags:
        return Matrix()

    rect = context['rect']
    has_user_coordinate = (len(context['rects']) > 1)

    m = Matrix()
    x = SVGParseCoord(node.getAttribute('x') or '0', rect[0])
    y = SVGParseCoord(node.getAttribute('y') or '0', rect[1])
    w = SVGParseCoord(node.getAttribute('width') or str(rect[0]), rect[0])
    h = SVGParseCoord(node.getAttribute('height') or str(rect[1]), rect[1])

    m = Matrix.Translation(Vector((x, y, 0.0)))
        if rect[0] != 0 and rect[1] != 0:
            m = m @ Matrix.Scale(w / rect[0], 4, Vector((1.0, 0.0, 0.0)))
            m = m @ Matrix.Scale(h / rect[1], 4, Vector((0.0, 1.0, 0.0)))

    if node.getAttribute('viewBox'):
        viewBox = node.getAttribute('viewBox').replace(',', ' ').split()
        vx = SVGParseCoord(viewBox[0], w)
        vy = SVGParseCoord(viewBox[1], h)
        vw = SVGParseCoord(viewBox[2], w)
        vh = SVGParseCoord(viewBox[3], h)

        if vw == 0 or vh == 0:
            return m

        if has_user_coordinate or (w != 0 and h != 0):
            sx = w / vw
            sy = h / vh
            scale = min(sx, sy)
        else:
            scale = 1.0
            w = vw
            h = vh
Sergey Sharybin's avatar
Sergey Sharybin committed

        tx = (w - vw * scale) / 2
        ty = (h - vh * scale) / 2
        m = m @ Matrix.Translation(Vector((tx, ty, 0.0)))
Sergey Sharybin's avatar
Sergey Sharybin committed

        m = m @ Matrix.Translation(Vector((-vx, -vy, 0.0)))
        m = m @ Matrix.Scale(scale, 4, Vector((1.0, 0.0, 0.0)))
        m = m @ Matrix.Scale(scale, 4, Vector((0.0, 1.0, 0.0)))

    return m


def SVGParseTransform(transform):
    """
    Parse transform string and return transformation matrix
    """

    m = Matrix()
    r = re.compile(r'\s*([A-z]+)\s*\((.*?)\)')

    for match in r.finditer(transform):
        func = match.group(1)
        params = match.group(2)
        params = params.replace(',', ' ').split()

        proc = SVGTransforms.get(func)
        if proc is None:
            raise Exception('Unknown trasnform function: ' + func)

        m = m @ proc(params)

    return m


def SVGGetMaterial(color, context):
    """
    Get material for specified color
    """

    materials = context['materials']
    rgb_re = re.compile(r'^\s*rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,(\d+)\s*\)\s*$')

    if color in materials:
        return materials[color]

    diff = None
    if color.startswith('#'):
        color = color[1:]

        if len(color) == 3:
            color = color[0] * 2 + color[1] * 2 + color[2] * 2

        diff = (int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16))
    elif color in svg_colors.SVGColors:
        diff = svg_colors.SVGColors[color]
Sergey Sharybin's avatar
Sergey Sharybin committed
    elif rgb_re.match(color):
Campbell Barton's avatar
Campbell Barton committed
        c = rgb_re.findall(color)[0]
Sergey Sharybin's avatar
Sergey Sharybin committed
        diff = (float(c[0]), float(c[1]), float(c[2]))
    diffuse_color = ([x / 255.0 for x in diff])

    if context['do_colormanage']:
Loading
Loading full blame...