From 03fd95bf37fb848325ac2dac7140e10fffbbe9f9 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Mon, 10 Nov 2025 11:31:22 +0100 Subject: [PATCH 1/9] Testing --- manim/mobject/svg/svg_mobject.py | 134 ++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/manim/mobject/svg/svg_mobject.py b/manim/mobject/svg/svg_mobject.py index bd494c0211..4f69b5c9ca 100644 --- a/manim/mobject/svg/svg_mobject.py +++ b/manim/mobject/svg/svg_mobject.py @@ -21,7 +21,7 @@ from ..geometry.line import Line from ..geometry.polygram import Polygon, Rectangle, RoundedRectangle from ..opengl.opengl_compatibility import ConvertToOpenGL -from ..types.vectorized_mobject import VMobject +from ..types.vectorized_mobject import VGroup, VMobject __all__ = ["SVGMobject", "VMobjectFromSVGPath"] @@ -29,6 +29,38 @@ SVG_HASH_TO_MOB_MAP: dict[int, VMobject] = {} +def indent(elem, level=0): + # Add indentation + indent_size = " " + i = "\n" + level * indent_size + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + indent_size + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + indent(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + +def pretty_print_xml_elementtree(xml_string): + # Parse the XML string + root = ET.fromstring(xml_string) + + # Indent the XML + indent(root) + + # Convert the XML element back to a string + pretty_xml = ET.tostring(root, encoding="unicode") + + # Print the pretty XML + print(pretty_xml) + + def _convert_point_to_3d(x: float, y: float) -> np.ndarray: return np.array([x, y, 0.0]) @@ -198,11 +230,25 @@ def generate_mobject(self) -> None: new_tree = self.modify_xml_tree(element_tree) # type: ignore[arg-type] # Create a temporary svg file to dump modified svg to be parsed modified_file_path = file_path.with_name(f"{file_path.stem}_{file_path.suffix}") + print("modified_file_path") + print(modified_file_path) new_tree.write(modified_file_path) svg = se.SVG.parse(modified_file_path) + print("svg") modified_file_path.unlink() + print("parsed version of svg file") + print("==============================================") + print(svg.string_xml()) + print("==============================================") + print('svg.get_element_by_id("unique01")') + print(svg.get_element_by_id("unique01").string_xml()) + print("==============================================") + print('svg.get_element_by_id("unique02")') + print(svg.get_element_by_id("unique02").string_xml()) + print("==============================================") + mobjects = self.get_mobjects_from(svg) self.add(*mobjects) self.flip(RIGHT) # Flip y @@ -259,6 +305,7 @@ def generate_config_style_dict(self) -> dict[str, str]: return result def get_mobjects_from(self, svg: se.SVG) -> list[VMobject]: + print("") """Convert the elements of the SVG to a list of mobjects. Parameters @@ -267,9 +314,94 @@ def get_mobjects_from(self, svg: se.SVG) -> list[VMobject]: The parsed SVG file. """ result: list[VMobject] = [] + stack: list[tuple[se.SVGElement, int]] = [] + stack.append((svg, 1)) + group_id_number = 0 + vgroup_stack: list[str] = ["root"] + vgroup_names: list[str] = ["root"] + vgroups: dict[str, VGroup] = {"root": VGroup()} + while len(stack) > 0: + element, depth = stack.pop() + print(" " * depth, "vgroup_stack: ", vgroup_stack) + # Reduce stack heights + vgroup_stack = vgroup_stack[0:(depth)] + print(" " * depth, type(element)) + try: + group_name = str(element.values["id"]) + print(" " * depth, "id: ", group_name) + except: + group_name = "numbered_group_%d" % group_id_number + group_id_number += 1 + pass + if isinstance(element, se.Group): + print(" " * depth, "create a vgroup with the name: '%s'" % group_name) + vg = VGroup() + vgroups[group_name] = vg + vgroup_names.append(group_name) + vgroup_stack.append(group_name) + print(" " * depth, "depth: %d" % depth) + print(" " * depth, "vgroup_stack: ", vgroup_stack) + parent_name = vgroup_stack[depth - 1] + print(" " * depth, "parent_name: '%s'" % parent_name) + vgroups[parent_name].add(vgroups[group_name]) + print(" " * depth, "Added to vgroup '%s'" % parent_name) + + if isinstance(element, (se.Group, se.Use)): + for subelement in element[::-1]: + stack.append((subelement, depth + 1)) + # Add element to the parent vgroup + try: + parent_name = vgroup_stack[depth - 2] + print(" " * depth, "parent: ", parent_name) + if isinstance(element, se.Path): + mob: VMobject = self.path_to_mobject(element) + self.apply_style_to_mobject(mob, element) + if isinstance(element, se.Transformable) and element.apply: + self.handle_transform(mob, element.transform) + vgroups[parent_name].add(mob) + print( + " " * depth, + "vgroups['%s']" % parent_name, + vgroups[parent_name], + ) + print(" " * depth, "Added to vgroup '%s'" % parent_name) + + except Exception as e: + print(e) + print("depth: ", depth) + + print("") + # assert False + for group_name in vgroup_names: + print("'%s' %s" % (group_name, vgroups[group_name])) + if isinstance(vgroups[group_name], VGroup): + for subelement in vgroups[group_name]: + print(" ", subelement) + if isinstance(subelement, VGroup): + for subsubelement in subelement: + print(" ", subsubelement) + if isinstance(subsubelement, VGroup): + for subsubsubelement in subsubelement: + print(" ", subsubsubelement) + return vgroups["root"] + for shape in svg.elements(): + print("") + print("shape:") + print(type(shape)) + # print(shape.values) + if shape.values.get("id"): + print("id: ", shape.values["id"]) + # print(shape.string_xml()) # can we combine the two continue cases into one? if isinstance(shape, se.Group): # noqa: SIM114 + # pretty_print_xml_elementtree(shape.string_xml()) + print("Elements inside group") + for idx, temp_shape in enumerate(shape.select()): + print("Element %d - %s" % (idx, type(temp_shape))) + if temp_shape.values.get("id"): + print("id: ", temp_shape.values["id"]) + # pretty_print_xml_elementtree(temp_shape.string_xml()) continue elif isinstance(shape, se.Path): mob: VMobject = self.path_to_mobject(shape) From ca0fec72a94a320466cd9ede33a78c0da4cd782e Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Mon, 10 Nov 2025 12:30:53 +0100 Subject: [PATCH 2/9] Updates --- manim/mobject/svg/svg_mobject.py | 139 ++++++++----------------------- 1 file changed, 33 insertions(+), 106 deletions(-) diff --git a/manim/mobject/svg/svg_mobject.py b/manim/mobject/svg/svg_mobject.py index 4f69b5c9ca..a20f20929d 100644 --- a/manim/mobject/svg/svg_mobject.py +++ b/manim/mobject/svg/svg_mobject.py @@ -29,38 +29,6 @@ SVG_HASH_TO_MOB_MAP: dict[int, VMobject] = {} -def indent(elem, level=0): - # Add indentation - indent_size = " " - i = "\n" + level * indent_size - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + indent_size - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - indent(elem, level + 1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i - - -def pretty_print_xml_elementtree(xml_string): - # Parse the XML string - root = ET.fromstring(xml_string) - - # Indent the XML - indent(root) - - # Convert the XML element back to a string - pretty_xml = ET.tostring(root, encoding="unicode") - - # Print the pretty XML - print(pretty_xml) - - def _convert_point_to_3d(x: float, y: float) -> np.ndarray: return np.array([x, y, 0.0]) @@ -235,19 +203,8 @@ def generate_mobject(self) -> None: new_tree.write(modified_file_path) svg = se.SVG.parse(modified_file_path) - print("svg") modified_file_path.unlink() - print("parsed version of svg file") - print("==============================================") - print(svg.string_xml()) - print("==============================================") - print('svg.get_element_by_id("unique01")') - print(svg.get_element_by_id("unique01").string_xml()) - print("==============================================") - print('svg.get_element_by_id("unique02")') - print(svg.get_element_by_id("unique02").string_xml()) - print("==============================================") mobjects = self.get_mobjects_from(svg) self.add(*mobjects) @@ -313,7 +270,6 @@ def get_mobjects_from(self, svg: se.SVG) -> list[VMobject]: svg The parsed SVG file. """ - result: list[VMobject] = [] stack: list[tuple[se.SVGElement, int]] = [] stack.append((svg, 1)) group_id_number = 0 @@ -329,12 +285,12 @@ def get_mobjects_from(self, svg: se.SVG) -> list[VMobject]: try: group_name = str(element.values["id"]) print(" " * depth, "id: ", group_name) - except: - group_name = "numbered_group_%d" % group_id_number + except Exception: + group_name = f"numbered_group_{group_id_number}" group_id_number += 1 pass if isinstance(element, se.Group): - print(" " * depth, "create a vgroup with the name: '%s'" % group_name) + print(" " * depth, f"create a vgroup with the name: '{ group_name }'") vg = VGroup() vgroups[group_name] = vg vgroup_names.append(group_name) @@ -370,65 +326,36 @@ def get_mobjects_from(self, svg: se.SVG) -> list[VMobject]: print(e) print("depth: ", depth) - print("") - # assert False - for group_name in vgroup_names: - print("'%s' %s" % (group_name, vgroups[group_name])) - if isinstance(vgroups[group_name], VGroup): - for subelement in vgroups[group_name]: - print(" ", subelement) - if isinstance(subelement, VGroup): - for subsubelement in subelement: - print(" ", subsubelement) - if isinstance(subsubelement, VGroup): - for subsubsubelement in subsubelement: - print(" ", subsubsubelement) - return vgroups["root"] - - for shape in svg.elements(): - print("") - print("shape:") - print(type(shape)) - # print(shape.values) - if shape.values.get("id"): - print("id: ", shape.values["id"]) - # print(shape.string_xml()) - # can we combine the two continue cases into one? - if isinstance(shape, se.Group): # noqa: SIM114 - # pretty_print_xml_elementtree(shape.string_xml()) - print("Elements inside group") - for idx, temp_shape in enumerate(shape.select()): - print("Element %d - %s" % (idx, type(temp_shape))) - if temp_shape.values.get("id"): - print("id: ", temp_shape.values["id"]) - # pretty_print_xml_elementtree(temp_shape.string_xml()) - continue - elif isinstance(shape, se.Path): - mob: VMobject = self.path_to_mobject(shape) - elif isinstance(shape, se.SimpleLine): - mob = self.line_to_mobject(shape) - elif isinstance(shape, se.Rect): - mob = self.rect_to_mobject(shape) - elif isinstance(shape, (se.Circle, se.Ellipse)): - mob = self.ellipse_to_mobject(shape) - elif isinstance(shape, se.Polygon): - mob = self.polygon_to_mobject(shape) - elif isinstance(shape, se.Polyline): - mob = self.polyline_to_mobject(shape) - elif isinstance(shape, se.Text): - mob = self.text_to_mobject(shape) - elif isinstance(shape, se.Use) or type(shape) is se.SVGElement: - continue - else: - logger.warning(f"Unsupported element type: {type(shape)}") - continue - if mob is None or not mob.has_points(): - continue - self.apply_style_to_mobject(mob, shape) - if isinstance(shape, se.Transformable) and shape.apply: - self.handle_transform(mob, shape.transform) - result.append(mob) - return result + return vgroups["root"], vgroups + + def get_mob_from_shape_element(self, shape): + if isinstance(shape, se.Group): # noqa: SIM114 + assert False, "Should never get here" + elif isinstance(shape, se.Path): + mob: VMobject = self.path_to_mobject(shape) + elif isinstance(shape, se.SimpleLine): + mob = self.line_to_mobject(shape) + elif isinstance(shape, se.Rect): + mob = self.rect_to_mobject(shape) + elif isinstance(shape, (se.Circle, se.Ellipse)): + mob = self.ellipse_to_mobject(shape) + elif isinstance(shape, se.Polygon): + mob = self.polygon_to_mobject(shape) + elif isinstance(shape, se.Polyline): + mob = self.polyline_to_mobject(shape) + elif isinstance(shape, se.Text): + mob = self.text_to_mobject(shape) + elif isinstance(shape, se.Use) or type(shape) is se.SVGElement: + assert False, "Should never get here - se.Use or se.SVGElement" + else: + logger.warning(f"Unsupported element type: {type(shape)}") + assert False, f"Unsupported element type: {type(shape)}" + if mob is None or not mob.has_points(): + assert False, f"mob is empty or have no points" + self.apply_style_to_mobject(mob, shape) + if isinstance(shape, se.Transformable) and shape.apply: + self.handle_transform(mob, shape.transform) + return mob @staticmethod def handle_transform(mob: VMobject, matrix: se.Matrix) -> VMobject: From d540aed5adaf0dbaadf24b330fc10b81346beddc Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Mon, 10 Nov 2025 12:35:29 +0100 Subject: [PATCH 3/9] Can now access elements from svg by id --- manim/mobject/svg/svg_mobject.py | 41 ++++++++++++-------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/manim/mobject/svg/svg_mobject.py b/manim/mobject/svg/svg_mobject.py index a20f20929d..e7ea099c8e 100644 --- a/manim/mobject/svg/svg_mobject.py +++ b/manim/mobject/svg/svg_mobject.py @@ -127,6 +127,7 @@ def __init__( self.stroke_color = stroke_color self.stroke_opacity = stroke_opacity # type: ignore[assignment] self.stroke_width = stroke_width # type: ignore[assignment] + self.id_to_vgroup_dict: dict[str, VGroup] = {} if self.stroke_width is None: self.stroke_width = 0 @@ -205,9 +206,11 @@ def generate_mobject(self) -> None: svg = se.SVG.parse(modified_file_path) modified_file_path.unlink() + mobjects_dict = self.get_mobjects_from(svg) + for key, value in mobjects_dict.items(): + self.id_to_vgroup_dict[key] = value + self.add(value) - mobjects = self.get_mobjects_from(svg) - self.add(*mobjects) self.flip(RIGHT) # Flip y def get_file_path(self) -> Path: @@ -261,7 +264,7 @@ def generate_config_style_dict(self) -> dict[str, str]: result[svg_key] = str(svg_default_dict[style_key]) return result - def get_mobjects_from(self, svg: se.SVG) -> list[VMobject]: + def get_mobjects_from(self, svg: se.SVG) -> dict[str, VGroup]: print("") """Convert the elements of the SVG to a list of mobjects. @@ -290,17 +293,13 @@ def get_mobjects_from(self, svg: se.SVG) -> list[VMobject]: group_id_number += 1 pass if isinstance(element, se.Group): - print(" " * depth, f"create a vgroup with the name: '{ group_name }'") + print(" " * depth, f"create a vgroup with the name: '{group_name}'") vg = VGroup() vgroups[group_name] = vg vgroup_names.append(group_name) vgroup_stack.append(group_name) - print(" " * depth, "depth: %d" % depth) - print(" " * depth, "vgroup_stack: ", vgroup_stack) parent_name = vgroup_stack[depth - 1] - print(" " * depth, "parent_name: '%s'" % parent_name) vgroups[parent_name].add(vgroups[group_name]) - print(" " * depth, "Added to vgroup '%s'" % parent_name) if isinstance(element, (se.Group, se.Use)): for subelement in element[::-1]: @@ -308,29 +307,19 @@ def get_mobjects_from(self, svg: se.SVG) -> list[VMobject]: # Add element to the parent vgroup try: parent_name = vgroup_stack[depth - 2] - print(" " * depth, "parent: ", parent_name) - if isinstance(element, se.Path): - mob: VMobject = self.path_to_mobject(element) - self.apply_style_to_mobject(mob, element) - if isinstance(element, se.Transformable) and element.apply: - self.handle_transform(mob, element.transform) + if isinstance(element, (se.Path)): + mob = self.get_mob_from_shape_element(element) vgroups[parent_name].add(mob) - print( - " " * depth, - "vgroups['%s']" % parent_name, - vgroups[parent_name], - ) - print(" " * depth, "Added to vgroup '%s'" % parent_name) except Exception as e: print(e) print("depth: ", depth) - return vgroups["root"], vgroups + return vgroups - def get_mob_from_shape_element(self, shape): + def get_mob_from_shape_element(self, shape: se.SVGElement) -> VMobject: if isinstance(shape, se.Group): # noqa: SIM114 - assert False, "Should never get here" + raise Exception("Should never get here") elif isinstance(shape, se.Path): mob: VMobject = self.path_to_mobject(shape) elif isinstance(shape, se.SimpleLine): @@ -346,12 +335,12 @@ def get_mob_from_shape_element(self, shape): elif isinstance(shape, se.Text): mob = self.text_to_mobject(shape) elif isinstance(shape, se.Use) or type(shape) is se.SVGElement: - assert False, "Should never get here - se.Use or se.SVGElement" + raise Exception("Should never get here - se.Use or se.SVGElement") else: logger.warning(f"Unsupported element type: {type(shape)}") - assert False, f"Unsupported element type: {type(shape)}" + raise Exception(f"Unsupported element type: {type(shape)}") if mob is None or not mob.has_points(): - assert False, f"mob is empty or have no points" + raise Exception("mob is empty or have no points") self.apply_style_to_mobject(mob, shape) if isinstance(shape, se.Transformable) and shape.apply: self.handle_transform(mob, shape.transform) From dcf3e16260d2897dbb279c15ef944a42113b0c97 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Mon, 10 Nov 2025 12:37:34 +0100 Subject: [PATCH 4/9] Removed debug output. --- manim/mobject/svg/svg_mobject.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/manim/mobject/svg/svg_mobject.py b/manim/mobject/svg/svg_mobject.py index e7ea099c8e..09ab31a36c 100644 --- a/manim/mobject/svg/svg_mobject.py +++ b/manim/mobject/svg/svg_mobject.py @@ -281,19 +281,14 @@ def get_mobjects_from(self, svg: se.SVG) -> dict[str, VGroup]: vgroups: dict[str, VGroup] = {"root": VGroup()} while len(stack) > 0: element, depth = stack.pop() - print(" " * depth, "vgroup_stack: ", vgroup_stack) # Reduce stack heights vgroup_stack = vgroup_stack[0:(depth)] - print(" " * depth, type(element)) try: group_name = str(element.values["id"]) - print(" " * depth, "id: ", group_name) except Exception: group_name = f"numbered_group_{group_id_number}" group_id_number += 1 - pass if isinstance(element, se.Group): - print(" " * depth, f"create a vgroup with the name: '{group_name}'") vg = VGroup() vgroups[group_name] = vg vgroup_names.append(group_name) @@ -310,10 +305,8 @@ def get_mobjects_from(self, svg: se.SVG) -> dict[str, VGroup]: if isinstance(element, (se.Path)): mob = self.get_mob_from_shape_element(element) vgroups[parent_name].add(mob) - except Exception as e: print(e) - print("depth: ", depth) return vgroups From 4f398513b2bfe967c98ec8308529a89c5b696ad4 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Mon, 10 Nov 2025 12:42:28 +0100 Subject: [PATCH 5/9] Cleanup. --- manim/mobject/svg/svg_mobject.py | 1 - 1 file changed, 1 deletion(-) diff --git a/manim/mobject/svg/svg_mobject.py b/manim/mobject/svg/svg_mobject.py index 09ab31a36c..328288493d 100644 --- a/manim/mobject/svg/svg_mobject.py +++ b/manim/mobject/svg/svg_mobject.py @@ -265,7 +265,6 @@ def generate_config_style_dict(self) -> dict[str, str]: return result def get_mobjects_from(self, svg: se.SVG) -> dict[str, VGroup]: - print("") """Convert the elements of the SVG to a list of mobjects. Parameters From 18f96a9c213fcbcf7d00484b418b06fa206453e9 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Mon, 10 Nov 2025 13:05:31 +0100 Subject: [PATCH 6/9] Handle more element types from svg --- manim/mobject/svg/svg_mobject.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/manim/mobject/svg/svg_mobject.py b/manim/mobject/svg/svg_mobject.py index 328288493d..75820c2718 100644 --- a/manim/mobject/svg/svg_mobject.py +++ b/manim/mobject/svg/svg_mobject.py @@ -301,7 +301,19 @@ def get_mobjects_from(self, svg: se.SVG) -> dict[str, VGroup]: # Add element to the parent vgroup try: parent_name = vgroup_stack[depth - 2] - if isinstance(element, (se.Path)): + if isinstance( + element, + ( + se.Path, + se.SimpleLine, + se.Rect, + se.Circle, + se.Ellipse, + se.Polygon, + se.Polyline, + se.Text, + ), + ): mob = self.get_mob_from_shape_element(element) vgroups[parent_name].add(mob) except Exception as e: From 5371cdbf5944b64c4d80ac14eef9ba1def4c3686 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Mon, 10 Nov 2025 13:47:25 +0100 Subject: [PATCH 7/9] Removed debug code. --- manim/mobject/svg/svg_mobject.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/manim/mobject/svg/svg_mobject.py b/manim/mobject/svg/svg_mobject.py index 75820c2718..278b3d78e5 100644 --- a/manim/mobject/svg/svg_mobject.py +++ b/manim/mobject/svg/svg_mobject.py @@ -199,8 +199,6 @@ def generate_mobject(self) -> None: new_tree = self.modify_xml_tree(element_tree) # type: ignore[arg-type] # Create a temporary svg file to dump modified svg to be parsed modified_file_path = file_path.with_name(f"{file_path.stem}_{file_path.suffix}") - print("modified_file_path") - print(modified_file_path) new_tree.write(modified_file_path) svg = se.SVG.parse(modified_file_path) From 137606a79b0bde6cb238adfe1e1f17562f32e3c5 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Tue, 11 Nov 2025 10:31:40 +0100 Subject: [PATCH 8/9] Fix issue related to determining the name of the parent group --- manim/mobject/svg/svg_mobject.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manim/mobject/svg/svg_mobject.py b/manim/mobject/svg/svg_mobject.py index 278b3d78e5..00b97037cf 100644 --- a/manim/mobject/svg/svg_mobject.py +++ b/manim/mobject/svg/svg_mobject.py @@ -290,7 +290,8 @@ def get_mobjects_from(self, svg: se.SVG) -> dict[str, VGroup]: vgroups[group_name] = vg vgroup_names.append(group_name) vgroup_stack.append(group_name) - parent_name = vgroup_stack[depth - 1] + parent_name = vgroup_stack[-2] + assert parent_name != group_name vgroups[parent_name].add(vgroups[group_name]) if isinstance(element, (se.Group, se.Use)): From aa310c9ea5a5d751d94908bd015bd0532926b523 Mon Sep 17 00:00:00 2001 From: Henrik Skov Midtiby Date: Tue, 11 Nov 2025 12:04:07 +0100 Subject: [PATCH 9/9] Set default color of SVGMobjects --- manim/mobject/svg/svg_mobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/mobject/svg/svg_mobject.py b/manim/mobject/svg/svg_mobject.py index 00b97037cf..a616ce2438 100644 --- a/manim/mobject/svg/svg_mobject.py +++ b/manim/mobject/svg/svg_mobject.py @@ -133,7 +133,7 @@ def __init__( if svg_default is None: svg_default = { - "color": None, + "color": VMobject().color, "opacity": None, "fill_color": None, "fill_opacity": None,