From bbb4fa155c26b8d3fd2f15b5915d1ce15d8421ac Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 13:14:19 +0800 Subject: [PATCH 01/15] fix the depth of svg tag --- manimlib/mobject/svg/svg_mobject.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 3a5260a7..1ec87714 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -71,8 +71,9 @@ class SVGMobject(VMobject): doc = minidom.parse(self.file_path) self.ref_to_element = {} - for svg in doc.getElementsByTagName("svg"): - mobjects = self.get_mobjects_from(svg) + for child in doc.childNodes: + if child.tagName != 'svg': continue + mobjects = self.get_mobjects_from(child) if self.unpack_groups: self.add(*mobjects) else: From 33f720c73a8ce6bc0fc86d901158390e6602e398 Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 13:15:53 +0800 Subject: [PATCH 02/15] fix typo --- manimlib/mobject/svg/svg_mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 1ec87714..650252ac 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -228,7 +228,7 @@ class SVGMobject(VMobject): stroke_width=stroke_width, stroke_color=stroke_color, fill_color=fill_color, - fill_opacity=opacity, + fill_opacity=fill_opacity, corner_radius=corner_radius ) From f4eb2724c5f7621f85935929fd6135d429539a24 Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 14:04:35 +0800 Subject: [PATCH 03/15] refactor SVGMobject.handle_transforms --- manimlib/mobject/svg/svg_mobject.py | 107 +++++++++++++++------------- 1 file changed, 58 insertions(+), 49 deletions(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 650252ac..dd94a634 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -236,7 +236,6 @@ class SVGMobject(VMobject): return mob def handle_transforms(self, element, mobject): - # TODO, this could use some cleaning... x, y = 0, 0 try: x = self.attribute_to_float(element.getAttribute('x')) @@ -246,56 +245,66 @@ class SVGMobject(VMobject): except Exception: pass - transform = element.getAttribute('transform') + transform_names = [ + "matrix", + "translate", "translateX", "translateY", + "scale", "scaleX", "scaleY", + "rotate", + "skew", "skewX", "skewY" + ] + transform_pattern = re.compile("|".join([x + r"[^)]*\)" for x in transform_names])) + number_pattern = re.compile(r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?") + transforms = transform_pattern.findall(element.getAttribute('transform'))[::-1] - try: # transform matrix - prefix = "matrix(" - suffix = ")" - if not transform.startswith(prefix) or not transform.endswith(suffix): - raise Exception() - transform = transform[len(prefix):-len(suffix)] - transform = string_to_numbers(transform) - transform = np.array(transform).reshape([3, 2]) - x = transform[2][0] - y = -transform[2][1] - matrix = np.identity(self.dim) - matrix[:2, :2] = transform[:2, :] - matrix[1] *= -1 - matrix[:, 1] *= -1 + for transform in transforms: + op_name, op_args = transform.split("(") + op_name = op_name.strip() + op_args = [float(x) for x in number_pattern.findall(op_args)] + + if op_name == "matrix": + self._handle_matrix_transform(mobject, op_name, op_args) + elif op_name.startswith("translate"): + self._handle_translate_transform(mobject, op_name, op_args) + elif op_name.startswith("scale"): + self._handle_scale_transform(mobject, op_name, op_args) + + def _handle_matrix_transform(self, mobject, op_name, op_args): + transform = np.array(op_args).reshape([3, 2]) + x = transform[2][0] + y = -transform[2][1] + matrix = np.identity(self.dim) + matrix[:2, :2] = transform[:2, :] + matrix[1] *= -1 + matrix[:, 1] *= -1 + for mob in mobject.family_members_with_points(): + mob.apply_matrix(matrix.T) + mobject.shift(x * RIGHT + y * UP) - for mob in mobject.family_members_with_points(): - mob.apply_matrix(matrix.T) - mobject.shift(x * RIGHT + y * UP) - except: - pass - - try: # transform scale - prefix = "scale(" - suffix = ")" - if not transform.startswith(prefix) or not transform.endswith(suffix): - raise Exception() - transform = transform[len(prefix):-len(suffix)] - scale_values = string_to_numbers(transform) - if len(scale_values) == 2: - scale_x, scale_y = scale_values - mobject.scale(np.array([scale_x, scale_y, 1]), about_point=ORIGIN) - elif len(scale_values) == 1: - scale = scale_values[0] - mobject.scale(np.array([scale, scale, 1]), about_point=ORIGIN) - except: - pass - - try: # transform translate - prefix = "translate(" - suffix = ")" - if not transform.startswith(prefix) or not transform.endswith(suffix): - raise Exception() - transform = transform[len(prefix):-len(suffix)] - x, y = string_to_numbers(transform) - mobject.shift(x * RIGHT + y * DOWN) - except: - pass - # TODO, ... + def _handle_translate_transform(self, mobject, op_name, op_args): + if op_name.endswith("X"): + x, y = op_args[0], 0 + elif op_name.endswith("Y"): + x, y = 0, op_args[0] + else: + x, y = op_args + mobject.shift(x * RIGHT + y * DOWN) + + def _handle_scale_transform(self, mobject, op_name, op_args): + if op_name.endswith("X"): + sx, sy = op_args[0], 1 + elif op_name.endswith("Y"): + sx, sy = 1, op_args[0] + elif len(op_args) == 2: + sx, sy = op_args + else: + sx = sy = op_args[0] + if sx < 0: + mobject.flip(UP) + sx = -sx + if sy < 0: + mobject.flip(RIGHT) + sy = -sy + mobject.scale(np.array([sx, sy, 1]), about_point=ORIGIN) def flatten(self, input_list): output_list = [] From 1658438feffb703c3cb17f27ae38dbd0d025a964 Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 14:05:32 +0800 Subject: [PATCH 04/15] allow Mobject.scale receive iterable scale_factor --- manimlib/mobject/mobject.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/mobject.py b/manimlib/mobject/mobject.py index 1050c3ee..b1621c4f 100644 --- a/manimlib/mobject/mobject.py +++ b/manimlib/mobject/mobject.py @@ -4,6 +4,7 @@ import random import sys import moderngl from functools import wraps +from collections import Iterable import numpy as np @@ -596,7 +597,10 @@ class Mobject(object): Otherwise, if about_point is given a value, scaling is done with respect to that point. """ - scale_factor = max(scale_factor, min_scale_factor) + if isinstance(scale_factor, Iterable): + scale_factor = np.array(scale_factor).clip(min=min_scale_factor) + else: + scale_factor = max(scale_factor, min_scale_factor) self.apply_points_function( lambda points: scale_factor * points, about_point=about_point, From dd13559b11179ed3676443ecb1d60f636032838e Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 14:09:05 +0800 Subject: [PATCH 05/15] replace warnings.warn with log.warning --- manimlib/mobject/svg/svg_mobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index dd94a634..de23cfa7 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -1,7 +1,6 @@ import itertools as it import re import string -import warnings import os import hashlib @@ -23,6 +22,7 @@ from manimlib.utils.config_ops import digest_config from manimlib.utils.directories import get_mobject_data_dir from manimlib.utils.images import get_full_vector_image_path from manimlib.utils.simple_functions import clip +from manimlib.logger import log def string_to_numbers(num_string): @@ -132,7 +132,7 @@ class SVGMobject(VMobject): # Remove initial "#" character ref = use_element.getAttribute("xlink:href")[1:] if ref not in self.ref_to_element: - warnings.warn(f"{ref} not recognized") + log.warning(f"{ref} not recognized") return VGroup() return self.get_mobjects_from( self.ref_to_element[ref] From 11379283aa0ab295aaba5fde7e1cbac569c021b1 Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 14:29:47 +0800 Subject: [PATCH 06/15] add support for rotate transform --- manimlib/mobject/svg/svg_mobject.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index de23cfa7..72b877e6 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -7,7 +7,7 @@ import hashlib from xml.dom import minidom from manimlib.constants import DEFAULT_STROKE_WIDTH -from manimlib.constants import ORIGIN, UP, DOWN, LEFT, RIGHT +from manimlib.constants import ORIGIN, UP, DOWN, LEFT, RIGHT, IN from manimlib.constants import BLACK from manimlib.constants import WHITE from manimlib.constants import DEGREES, PI @@ -267,6 +267,8 @@ class SVGMobject(VMobject): self._handle_translate_transform(mobject, op_name, op_args) elif op_name.startswith("scale"): self._handle_scale_transform(mobject, op_name, op_args) + elif op_name == "rotate": + self._handle_rotate_transform(mobject, op_name, op_args) def _handle_matrix_transform(self, mobject, op_name, op_args): transform = np.array(op_args).reshape([3, 2]) @@ -305,6 +307,13 @@ class SVGMobject(VMobject): mobject.flip(RIGHT) sy = -sy mobject.scale(np.array([sx, sy, 1]), about_point=ORIGIN) + + def _handle_rotate_transform(self, mobject, op_name, op_args): + if len(op_args) == 1: + mobject.rotate(op_args[0] * DEGREES, axis=IN, about_point=ORIGIN) + else: + deg, x, y = op_args + mobject.rotate(deg * DEGREES, axis=IN, about_point=np.array([x, y, 0])) def flatten(self, input_list): output_list = [] From d694aed452b7d8cd847a14647fcab57df7178161 Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 14:40:02 +0800 Subject: [PATCH 07/15] add support for skewX and skewY transform --- manimlib/mobject/svg/svg_mobject.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 72b877e6..d8f1bec0 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -250,7 +250,7 @@ class SVGMobject(VMobject): "translate", "translateX", "translateY", "scale", "scaleX", "scaleY", "rotate", - "skew", "skewX", "skewY" + "skewX", "skewY" ] transform_pattern = re.compile("|".join([x + r"[^)]*\)" for x in transform_names])) number_pattern = re.compile(r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?") @@ -269,6 +269,8 @@ class SVGMobject(VMobject): self._handle_scale_transform(mobject, op_name, op_args) elif op_name == "rotate": self._handle_rotate_transform(mobject, op_name, op_args) + elif op_name.startswith("skew"): + self._handle_skew_transform(mobject, op_name, op_args) def _handle_matrix_transform(self, mobject, op_name, op_args): transform = np.array(op_args).reshape([3, 2]) @@ -314,6 +316,15 @@ class SVGMobject(VMobject): else: deg, x, y = op_args mobject.rotate(deg * DEGREES, axis=IN, about_point=np.array([x, y, 0])) + + def _handle_skew_transform(self, mobject, op_name, op_args): + rad = op_args[0] * DEGREES + if op_name == "skewX": + tana = np.tan(rad) + self._handle_matrix_transform(mobject, None, [1., 0., tana, 1., 0., 0.]) + elif op_name == "skewY": + tana = np.tan(rad) + self._handle_matrix_transform(mobject, None, [1., tana, 0., 1., 0., 0.]) def flatten(self, input_list): output_list = [] From 416cc8e6d518969ae56aa0e570bd1e44692cfc97 Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 14:41:11 +0800 Subject: [PATCH 08/15] add warning for unsupported element type --- manimlib/mobject/svg/svg_mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index d8f1bec0..5915bc4a 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -108,8 +108,8 @@ class SVGMobject(VMobject): elif element.tagName in ['polygon', 'polyline']: result.append(self.polygon_to_mobject(element)) else: + log.warning(f"Unsupported element type: {element.tagName}") pass # TODO - # warnings.warn("Unknown element type: " + element.tagName) result = [m for m in result if m is not None] self.handle_transforms(element, VGroup(*result)) if len(result) > 1 and not self.unpack_groups: From 6a74c241b8929af90522844829a3ef6fb18a14d6 Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 16:28:23 +0800 Subject: [PATCH 09/15] fix bug of node which is not an element --- manimlib/mobject/svg/svg_mobject.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 5915bc4a..c5acb956 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -72,6 +72,7 @@ class SVGMobject(VMobject): self.ref_to_element = {} for child in doc.childNodes: + if not isinstance(child, minidom.Element): continue if child.tagName != 'svg': continue mobjects = self.get_mobjects_from(child) if self.unpack_groups: From 565763a2ffaf397b09404c6d1e5251377848918a Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 19:44:42 +0800 Subject: [PATCH 10/15] reconstruct path parser --- manimlib/mobject/svg/svg_mobject.py | 59 ++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index c5acb956..ff3c88b5 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -409,7 +409,8 @@ class VMobjectFromSVGPathstring(VMobject): number_types = np.array(list(number_types_str)) n_numbers = len(number_types_str) - number_groups = np.array(string_to_numbers(coord_string)).reshape((-1, n_numbers)) + number_list = _PathStringParser(coord_string, number_types_str).args + number_groups = np.array(number_list).reshape((-1, n_numbers)) for numbers in number_groups: if command.islower(): @@ -551,9 +552,63 @@ class VMobjectFromSVGPathstring(VMobject): "S": (self.add_smooth_cubic_curve_to, "xyxy"), "Q": (self.add_quadratic_bezier_curve_to, "xyxy"), "T": (self.add_smooth_curve_to, "xy"), - "A": (self.add_elliptical_arc_to, "-----xy"), + "A": (self.add_elliptical_arc_to, "uuaffxy"), "Z": (self.close_path, ""), } def get_original_path_string(self): return self.path_string + + +class InvalidPathError(ValueError): + pass + + +class _PathStringParser: + def __init__(self, arguments, rules): + self.args = [] + arguments = bytearray(arguments, "ascii") + while arguments: + for rule in rules: + self._rule_to_function_map[rule](arguments) + + @property + def _rule_to_function_map(self): + return { + "x": self._get_number, + "y": self._get_number, + "a": self._get_number, + "u": self._get_unsigned_number, + "f": self._get_flag, + } + + def _strip_array(self, arg_array): + while arg_array and arg_array[0] in [0x9, 0x20, 0xA, 0xC, 0xD, 0x2C]: + arg_array[0:1] = b"" + + def _get_number(self, arg_array): + pattern = re.compile(rb"^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?") + res = pattern.search(arg_array) + if not res: + raise InvalidPathError(f"Expected a number, got '{arg_array}'") + number = float(res.group()) + self.args.append(number) + arg_array[res.start():res.end()] = b"" + self._strip_array(arg_array) + return number + + def _get_unsigned_number(self, arg_array): + number = self._get_number(arg_array) + if number < 0: + raise InvalidPathError(f"Expected an unsigned number, got '{number}'") + return number + + def _get_flag(self, arg_array): + flag = arg_array[0] + if flag != 48 and flag != 49: + raise InvalidPathError(f"Expected a flag (0/1), got '{chr(flag)}'") + flag -= 48 + self.args.append(flag) + arg_array[0:1] = b"" + self._strip_array(arg_array) + return flag From 925f2e123f33f1523686c6d244d5bc9945b7a9cd Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 19:54:19 +0800 Subject: [PATCH 11/15] add comments --- manimlib/mobject/svg/svg_mobject.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index ff3c88b5..ccfb48d6 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -565,6 +565,7 @@ class InvalidPathError(ValueError): class _PathStringParser: + # modified from https://github.com/regebro/svg.path/ def __init__(self, arguments, rules): self.args = [] arguments = bytearray(arguments, "ascii") @@ -583,6 +584,8 @@ class _PathStringParser: } def _strip_array(self, arg_array): + # wsp: (0x9, 0x20, 0xA, 0xC, 0xD) with comma 0x2C + # https://www.w3.org/TR/SVG/paths.html#PathDataBNF while arg_array and arg_array[0] in [0x9, 0x20, 0xA, 0xC, 0xD, 0x2C]: arg_array[0:1] = b"" From 05b3c9852e3695f2ed59ccf49cd86f074014f167 Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 20:06:00 +0800 Subject: [PATCH 12/15] fix add_smooth_cubic_curve_to when have only one point --- manimlib/mobject/svg/svg_mobject.py | 2 +- manimlib/mobject/types/vectorized_mobject.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index ccfb48d6..196a94af 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -371,7 +371,7 @@ class VMobjectFromSVGPathstring(VMobject): points_filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}_points.npy") tris_filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}_tris.npy") - if os.path.exists(points_filepath) and os.path.exists(tris_filepath): + if os.path.exists(points_filepath) and os.path.exists(tris_filepath) and False: self.set_points(np.load(points_filepath)) self.triangulation = np.load(tris_filepath) self.needs_new_triangulation = False diff --git a/manimlib/mobject/types/vectorized_mobject.py b/manimlib/mobject/types/vectorized_mobject.py index a63a190b..3c7a4326 100644 --- a/manimlib/mobject/types/vectorized_mobject.py +++ b/manimlib/mobject/types/vectorized_mobject.py @@ -382,7 +382,10 @@ class VMobject(Mobject): def add_smooth_cubic_curve_to(self, handle, point): self.throw_error_if_no_points() - new_handle = self.get_reflection_of_last_handle() + if self.get_num_points() == 1: + new_handle = self.get_points()[-1] + else: + new_handle = self.get_reflection_of_last_handle() self.add_cubic_bezier_curve_to(new_handle, handle, point) def has_new_path_started(self): From 8205edcc4c8a28e0ebf13aaeebef0a8b2fa26143 Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 20:13:20 +0800 Subject: [PATCH 13/15] fix a small bug --- manimlib/mobject/svg/svg_mobject.py | 1 + 1 file changed, 1 insertion(+) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 196a94af..00b19603 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -569,6 +569,7 @@ class _PathStringParser: def __init__(self, arguments, rules): self.args = [] arguments = bytearray(arguments, "ascii") + self._strip_array(arguments) while arguments: for rule in rules: self._rule_to_function_map[rule](arguments) From 790bf0a104b9afd9afe6f568ad514701de6ce077 Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 20:25:30 +0800 Subject: [PATCH 14/15] fix typo --- manimlib/mobject/svg/svg_mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 00b19603..5ca04b01 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -371,7 +371,7 @@ class VMobjectFromSVGPathstring(VMobject): points_filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}_points.npy") tris_filepath = os.path.join(get_mobject_data_dir(), f"{path_hash}_tris.npy") - if os.path.exists(points_filepath) and os.path.exists(tris_filepath) and False: + if os.path.exists(points_filepath) and os.path.exists(tris_filepath): self.set_points(np.load(points_filepath)) self.triangulation = np.load(tris_filepath) self.needs_new_triangulation = False From 8db1164ece9c99ba0691e675ac791c8b678f12f7 Mon Sep 17 00:00:00 2001 From: TonyCrane Date: Tue, 25 Jan 2022 21:48:04 +0800 Subject: [PATCH 15/15] some refactors --- manimlib/mobject/svg/svg_mobject.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/manimlib/mobject/svg/svg_mobject.py b/manimlib/mobject/svg/svg_mobject.py index 5ca04b01..82afa227 100644 --- a/manimlib/mobject/svg/svg_mobject.py +++ b/manimlib/mobject/svg/svg_mobject.py @@ -237,14 +237,13 @@ class SVGMobject(VMobject): return mob def handle_transforms(self, element, mobject): - x, y = 0, 0 - try: - x = self.attribute_to_float(element.getAttribute('x')) - # Flip y - y = -self.attribute_to_float(element.getAttribute('y')) - mobject.shift([x, y, 0]) - except Exception: - pass + x, y = ( + self.attribute_to_float(element.getAttribute(key)) + if element.hasAttribute(key) + else 0.0 + for key in ("x", "y") + ) + mobject.shift(x * RIGHT + y * DOWN) transform_names = [ "matrix",