123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- from __future__ import annotations
- import numpy as np
- import pathops
- from manimlib.mobject.types.vectorized_mobject import VMobject
- # Boolean operations between 2D mobjects
- # Borrowed from from https://github.com/ManimCommunity/manim/
- def _convert_vmobject_to_skia_path(vmobject: VMobject) -> pathops.Path:
- path = pathops.Path()
- for submob in vmobject.family_members_with_points():
- for subpath in submob.get_subpaths():
- quads = vmobject.get_bezier_tuples_from_points(subpath)
- start = subpath[0]
- path.moveTo(*start[:2])
- for p0, p1, p2 in quads:
- path.quadTo(*p1[:2], *p2[:2])
- if vmobject.consider_points_equal(subpath[0], subpath[-1]):
- path.close()
- return path
- def _convert_skia_path_to_vmobject(
- path: pathops.Path,
- vmobject: VMobject
- ) -> VMobject:
- PathVerb = pathops.PathVerb
- current_path_start = np.array([0.0, 0.0, 0.0])
- for path_verb, points in path:
- if path_verb == PathVerb.CLOSE:
- vmobject.add_line_to(current_path_start)
- else:
- points = np.hstack((np.array(points), np.zeros((len(points), 1))))
- if path_verb == PathVerb.MOVE:
- for point in points:
- current_path_start = point
- vmobject.start_new_path(point)
- elif path_verb == PathVerb.CUBIC:
- vmobject.add_cubic_bezier_curve_to(*points)
- elif path_verb == PathVerb.LINE:
- vmobject.add_line_to(points[0])
- elif path_verb == PathVerb.QUAD:
- vmobject.add_quadratic_bezier_curve_to(*points)
- else:
- raise Exception(f"Unsupported: {path_verb}")
- return vmobject.reverse_points()
- class Union(VMobject):
- def __init__(self, *vmobjects: VMobject, **kwargs):
- if len(vmobjects) < 2:
- raise ValueError("At least 2 mobjects needed for Union.")
- super().__init__(**kwargs)
- outpen = pathops.Path()
- paths = [
- _convert_vmobject_to_skia_path(vmobject)
- for vmobject in vmobjects
- ]
- pathops.union(paths, outpen.getPen())
- _convert_skia_path_to_vmobject(outpen, self)
- class Difference(VMobject):
- def __init__(self, subject: VMobject, clip: VMobject, **kwargs):
- super().__init__(**kwargs)
- outpen = pathops.Path()
- pathops.difference(
- [_convert_vmobject_to_skia_path(subject)],
- [_convert_vmobject_to_skia_path(clip)],
- outpen.getPen(),
- )
- _convert_skia_path_to_vmobject(outpen, self)
- class Intersection(VMobject):
- def __init__(self, *vmobjects: VMobject, **kwargs):
- if len(vmobjects) < 2:
- raise ValueError("At least 2 mobjects needed for Intersection.")
- super().__init__(**kwargs)
- outpen = pathops.Path()
- pathops.intersection(
- [_convert_vmobject_to_skia_path(vmobjects[0])],
- [_convert_vmobject_to_skia_path(vmobjects[1])],
- outpen.getPen(),
- )
- new_outpen = outpen
- for _i in range(2, len(vmobjects)):
- new_outpen = pathops.Path()
- pathops.intersection(
- [outpen],
- [_convert_vmobject_to_skia_path(vmobjects[_i])],
- new_outpen.getPen(),
- )
- outpen = new_outpen
- _convert_skia_path_to_vmobject(outpen, self)
- class Exclusion(VMobject):
- def __init__(self, *vmobjects: VMobject, **kwargs):
- if len(vmobjects) < 2:
- raise ValueError("At least 2 mobjects needed for Exclusion.")
- super().__init__(**kwargs)
- outpen = pathops.Path()
- pathops.xor(
- [_convert_vmobject_to_skia_path(vmobjects[0])],
- [_convert_vmobject_to_skia_path(vmobjects[1])],
- outpen.getPen(),
- )
- new_outpen = outpen
- for _i in range(2, len(vmobjects)):
- new_outpen = pathops.Path()
- pathops.xor(
- [outpen],
- [_convert_vmobject_to_skia_path(vmobjects[_i])],
- new_outpen.getPen(),
- )
- outpen = new_outpen
- _convert_skia_path_to_vmobject(outpen, self)
|