{"record":{"id":"1b87acd0a12fb743","repo":"3b1b/manim","slug":"at-least-2-mobjects-needed-for-union","errorCode":null,"errorMessage":"At least 2 mobjects needed for Union.","messagePattern":"At least 2 mobjects needed for Union\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"manimlib/mobject/boolean_ops.py","lineNumber":55,"sourceCode":"            if path_verb == PathVerb.MOVE:\r\n                for point in points:\r\n                    current_path_start = point\r\n                    vmobject.start_new_path(point)\r\n            elif path_verb == PathVerb.CUBIC:\r\n                vmobject.add_cubic_bezier_curve_to(*points)\r\n            elif path_verb == PathVerb.LINE:\r\n                vmobject.add_line_to(points[0])\r\n            elif path_verb == PathVerb.QUAD:\r\n                vmobject.add_quadratic_bezier_curve_to(*points)\r\n            else:\r\n                raise Exception(f\"Unsupported: {path_verb}\")\r\n    return vmobject.reverse_points()\r\n\r\n\r\nclass Union(VMobject):\r\n    def __init__(self, *vmobjects: VMobject, **kwargs):\r\n        if len(vmobjects) < 2:\r\n            raise ValueError(\"At least 2 mobjects needed for Union.\")\r\n        super().__init__(**kwargs)\r\n        outpen = pathops.Path()\r\n        paths = [\r\n            _convert_vmobject_to_skia_path(vmobject)\r\n            for vmobject in vmobjects\r\n        ]\r\n        pathops.union(paths, outpen.getPen())\r\n        _convert_skia_path_to_vmobject(outpen, self)\r\n\r\n\r\nclass Difference(VMobject):\r\n    def __init__(self, subject: VMobject, clip: VMobject, **kwargs):\r\n        super().__init__(**kwargs)\r\n        outpen = pathops.Path()\r\n        pathops.difference(\r\n            [_convert_vmobject_to_skia_path(subject)],\r\n            [_convert_vmobject_to_skia_path(clip)],\r\n            outpen.getPen(),\r","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/3b1b/manim/blob/dee01804d47b9f94402d71472674710dcac125b8/manimlib/mobject/boolean_ops.py#L37-L73","documentation":"ValueError raised in Union.__init__ (manimlib/mobject/boolean_ops.py:55). Union implements boolean union via skia-pathops, which requires at least two input paths to compute a meaningful result. Passing fewer than two VMobjects (zero or one) is rejected before any path conversion happens.","triggerScenarios":"Union(single_mob), Union() with no arguments, or Union(*list_of_mobjects) where the list has been emptied by earlier filtering logic.","commonSituations":"Programmatic code that does Union(*mobs) over a dynamically-built list; refactoring that accidentally drops an operand; looping over pairs where an off-by-one leaves one element.","solutions":["Pass at least two VMobjects: Union(mob_a, mob_b)","Guard dynamic calls: if len(mobs) < 2: skip or return mobs[0]","If you meant a copy/transform of one shape, use mob.copy() or mob.replicate() instead of Union"],"exampleFix":"# before\nresult = Union(*shapes)  # raises when len(shapes) < 2\n\n# after\nresult = shapes[0] if len(shapes) == 1 else (Union(*shapes) if len(shapes) >= 2 else None)","handlingStrategy":"validation","validationCode":"def safe_union(*mobs):\n    assert len(mobs) >= 2, \"Union requires at least 2 mobjects\"\n    return Union(*mobs)","typeGuard":"from manimlib.mobject.types.vectorized_mobject import VMobject\n\ndef is_union_ready(mobs: list) -> bool:\n    return len(mobs) >= 2 and all(isinstance(m, VMobject) for m in mobs)","tryCatchPattern":null,"preventionTips":["Check len(mobs) >= 2 before splatting into Union","Lint scene code for Union called with a single literal argument"],"tags":["manim","boolean-ops","validation"],"backgroundTag":null,"analyzedSha":"dee01804d47b9f94402d71472674710dcac125b8","analyzedAt":"2026-08-14T19:53:44.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}