manim-data-structures
manim-data-structures copied to clipboard
Overloading operators for MVariable, MArray and MArrayPointer classes.
Discussed in https://github.com/drageelr/manim-data-structures/discussions/17
Originally posted by ttzytt February 22, 2023 Found some places to improve when using the package to implement the animation of a simple binary search algorithm:
class MyScene(Scene):
def construct(self):
arr = MArray(self, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
arr.shift(LEFT * 4 + UP * 2)
tar_val = MVariable(self, 3, label="target value")
tar_val.shift(DOWN * 2)
l = MArrayPointer(self, arr, 0, label="left")
r = MArrayPointer(self, arr, 9, label="right")
self.play(Create(arr))
self.play(Create(tar_val))
self.play(Create(l))
self.play(Create(r))
mid = MArrayPointer(self, arr, (l.fetch_index() + r.fetch_index()) // 2, label="mid")
self.play(Create(mid))
# find first larger or equal to target value
while (l.fetch_index() <= r.fetch_index()):
anims = []
if (mid.fetch_index() < tar_val.fetch_value()):
anims.append(l.shift_to_elem(mid.fetch_index() + 1, play_anim=False))
else:
anims.append(r.shift_to_elem(mid.fetch_index() - 1, play_anim=False))
anims.append(mid.shift_to_elem((l.fetch_index() + r.fetch_index()) // 2, play_anim=False))
self.play(AnimationGroup(*anims))
self.wait(1)
For every comparison between MArrayPointer
, you have to call fetch_index()
to get the position where the array is pointing. I think it will be better to overload comparators in MArrayPointer so that the implementation will be easier.
The same works for other operators like add and subtract: the addition or subtraction between MArrayPointer
an integer or another MArrayPointer
should give the addition and subtraction between the integer and the index that the pointer is pointing to in a MArray
. That way, the operation of pointers will be much easier, and code like the following will be simplified:
r.shift_to_elem(mid.fetch_index() - 1, play_anim=False
mid.shift_to_elem((l.fetch_index() + r.fetch_index()) // 2
Another possible improvement from overloading operators is to overload __getitem__
and __setitem__
for MArray
. Using these two methods, we could directly access the MArrayElement
inside the MArray
through the bracket operator, thus making animations on MArrayElement
in an easier way.
I'm not sure about your ideas for these modifications, but if you think they are helpful, I can make a PR on that.
@ttzytt Please provide details for which classes and operators you think should be overloaded.
Met some problems when trying to implement the __eq__
method for MArrayPointer.
If there are no user-defined __eq__
functions for a class, python will use it directly for hashing. And if I implemented this function without implementing __hash__
, and try to run the following code:
class MyScene(Scene):
def construct(self):
arr = MArray(self, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
arr.shift(LEFT * 4 + UP * 2)
tar_val = MVariable(self, 3, label="target value")
tar_val.shift(DOWN * 2)
l = MArrayPointer(self, arr, 0, label="left")
r = MArrayPointer(self, arr, 9, label="right")
self.play(Create(arr))
self.play(Create(tar_val))
self.play(Create(l))
self.play(Create(r))
mid = MArrayPointer(self, arr, (l + r) // 2, label="mid")
self.play(Create(mid))
# find first larger or equal to target value
while (l <= r):
anims = []
if (tar_val > mid):
anims.append(l.shift_to_elem(mid + 1, play_anim=False))
else:
anims.append(r.shift_to_elem(mid - 1, play_anim=False))
anims.append(mid.shift_to_elem((l + r) // 2, play_anim=False))
self.play(AnimationGroup(*anims))
self.wait(1)
The following error is shown:
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ E:\pythons\3.11\Lib\site-packages\manim\cli\render\commands.py:115 in render │
│ │
│ 112 │ │ │ try: │
│ 113 │ │ │ │ with tempconfig({}): │
│ 114 │ │ │ │ │ scene = SceneClass() │
│ ❱ 115 │ │ │ │ │ scene.render() │
│ 116 │ │ │ except Exception: │
│ 117 │ │ │ │ error_console.print_exception() │
│ 118 │ │ │ │ sys.exit(1) │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\scene\scene.py:223 in render │
│ │
│ 220 │ │ """ │
│ 221 │ │ self.setup() │
│ 222 │ │ try: │
│ ❱ 223 │ │ │ self.construct() │
│ 224 │ │ except EndSceneEarlyException: │
│ 225 │ │ │ pass │
│ 226 │ │ except RerunSceneException as e: │
│ │
│ E:\prog\python\manim\manim-data-structures\tests\__init__.py:17 in construct │
│ │
│ 14 │ │ r = MArrayPointer(self, arr, 9, label="right") │
│ 15 │ │ │
│ 16 │ │ self.play(Create(arr)) │
│ ❱ 17 │ │ self.play(Create(tar_val)) │
│ 18 │ │ self.play(Create(l)) │
│ 19 │ │ self.play(Create(r)) │
│ 20 │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\scene\scene.py:1033 in play │
│ │
│ 1030 │ │ │ return │
│ 1031 │ │ │
│ 1032 │ │ start_time = self.renderer.time │
│ ❱ 1033 │ │ self.renderer.play(self, *args, **kwargs) │
│ 1034 │ │ run_time = self.renderer.time - start_time │
│ 1035 │ │ if subcaption: │
│ 1036 │ │ │ if subcaption_duration is None: │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\renderer\cairo_renderer.py:93 in play │
│ │
│ 90 │ │ ) │
│ 91 │ │ │
│ 92 │ │ self.file_writer.begin_animation(not self.skip_animations) │
│ ❱ 93 │ │ scene.begin_animations() │
│ 94 │ │ │
│ 95 │ │ # Save a static image, to avoid rendering non moving objects. │
│ 96 │ │ self.save_static_frame_data(scene, scene.static_mobjects) │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\scene\scene.py:1162 in begin_animations │
│ │
│ 1159 │ def begin_animations(self) -> None: │
│ 1160 │ │ """Start the animations of the scene.""" │
│ 1161 │ │ for animation in self.animations: │
│ ❱ 1162 │ │ │ animation._setup_scene(self) │
│ 1163 │ │ │ animation.begin() │
│ 1164 │ │ │
│ 1165 │ │ if config.renderer == RendererType.CAIRO: │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\animation\animation.py:248 in _setup_scene │
│ │
│ 245 │ │ │ return │
│ 246 │ │ if ( │
│ 247 │ │ │ self.is_introducer() │
│ ❱ 248 │ │ │ and self.mobject not in scene.get_mobject_family_members() │
PS E:\prog\python\manim\manim-data-structures> manim .\tests\__init__.py -pql
Manim Community v0.17.2
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "E:\pythons\3.11\Scripts\manim.exe\__main__.py", line 7, in <module>
File "E:\pythons\3.11\Lib\site-packages\click\core.py", line 1130, in __call__
return self.main(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\pythons\3.11\Lib\site-packages\click\core.py", line 1055, in main
rv = self.invoke(ctx)
^^^^^^^^^^^^^^^^
File "E:\pythons\3.11\Lib\site-packages\click\core.py", line 1657, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\pythons\3.11\Lib\site-packages\click\core.py", line 1404, in invoke
return ctx.invoke(self.callback, **ctx.params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\pythons\3.11\Lib\site-packages\click\core.py", line 760, in invoke
return __callback(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\pythons\3.11\Lib\site-packages\manim\cli\render\commands.py", line 111, in render
PS E:\prog\python\manim\manim-data-structures> manim .\tests\__init__.py -pql
Manim Community v0.17.2
[02/23/23 14:21:01] INFO Animation 0 : Using cached data (hash : 4266129954_2149614056_223132457) cairo_renderer.py:78
[02/23/23 14:21:02] INFO Animation 1 : Using cached data (hash : 1442284246_4206486446_917344461) cairo_renderer.py:78
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ E:\pythons\3.11\Lib\site-packages\manim\cli\render\commands.py:115 in render │
│ │
│ 112 │ │ │ try: │
│ 113 │ │ │ │ with tempconfig({}): │
│ 114 │ │ │ │ │ scene = SceneClass() │
│ ❱ 115 │ │ │ │ │ scene.render() │
│ 116 │ │ │ except Exception: │
│ 117 │ │ │ │ error_console.print_exception() │
│ 118 │ │ │ │ sys.exit(1) │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\scene\scene.py:223 in render │
│ │
│ 220 │ │ """ │
│ 221 │ │ self.setup() │
│ 222 │ │ try: │
│ ❱ 223 │ │ │ self.construct() │
│ 224 │ │ except EndSceneEarlyException: │
│ 225 │ │ │ pass │
│ 226 │ │ except RerunSceneException as e: │
│ │
│ E:\prog\python\manim\manim-data-structures\tests\__init__.py:18 in construct │
│ │
│ 15 │ │ │
│ 16 │ │ self.play(Create(arr)) │
│ 17 │ │ self.play(Create(tar_val)) │
│ ❱ 18 │ │ self.play(Create(l)) │
│ 19 │ │ self.play(Create(r)) │
│ 20 │ │ │
│ 21 │ │ mid = MArrayPointer(self, arr, (l + r) // 2, label="mid") │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\scene\scene.py:1033 in play │
│ │
│ 1030 │ │ │ return │
│ 1031 │ │ │
│ 1032 │ │ start_time = self.renderer.time │
│ ❱ 1033 │ │ self.renderer.play(self, *args, **kwargs) │
│ 1034 │ │ run_time = self.renderer.time - start_time │
│ 1035 │ │ if subcaption: │
│ 1036 │ │ │ if subcaption_duration is None: │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\renderer\cairo_renderer.py:93 in play │
│ │
│ 90 │ │ ) │
│ 91 │ │ │
│ 92 │ │ self.file_writer.begin_animation(not self.skip_animations) │
│ ❱ 93 │ │ scene.begin_animations() │
│ 94 │ │ │
│ 95 │ │ # Save a static image, to avoid rendering non moving objects. │
│ 96 │ │ self.save_static_frame_data(scene, scene.static_mobjects) │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\scene\scene.py:1162 in begin_animations │
│ │
│ 1159 │ def begin_animations(self) -> None: │
│ 1160 │ │ """Start the animations of the scene.""" │
│ 1161 │ │ for animation in self.animations: │
│ ❱ 1162 │ │ │ animation._setup_scene(self) │
│ 1163 │ │ │ animation.begin() │
│ 1164 │ │ │
│ 1165 │ │ if config.renderer == RendererType.CAIRO: │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\animation\animation.py:250 in _setup_scene │
│ │
│ 247 │ │ │ self.is_introducer() │
│ 248 │ │ │ and self.mobject not in scene.get_mobject_family_members() │
│ 249 │ │ ): │
│ ❱ 250 │ │ │ scene.add(self.mobject) │
│ 251 │ │
│ 252 │ def create_starting_mobject(self) -> Mobject: │
│ 253 │ │ # Keep track of where the mobject starts │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\scene\scene.py:468 in add │
│ │
│ 465 │ │ │ self.meshes += new_meshes │
│ 466 │ │ elif config.renderer == RendererType.CAIRO: │
│ 467 │ │ │ mobjects = [*mobjects, *self.foreground_mobjects] │
│ ❱ 468 │ │ │ self.restructure_mobjects(to_remove=mobjects) │
│ 469 │ │ │ self.mobjects += mobjects │
│ 470 │ │ │ if self.moving_mobjects: │
│ 471 │ │ │ │ self.restructure_mobjects( │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\scene\scene.py:602 in restructure_mobjects │
│ │
│ 599 │ │ │ The Scene mobject with restructured Mobjects. │
│ 600 │ │ """ │
│ 601 │ │ if extract_families: │
│ ❱ 602 │ │ │ to_remove = extract_mobject_family_members( │
│ 603 │ │ │ │ to_remove, │
│ 604 │ │ │ │ use_z_index=self.renderer.camera.use_z_index, │
│ 605 │ │ │ ) │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\utils\family.py:37 in extract_mobject_family_members │
│ │
│ 34 │ else: │
│ 35 │ │ method = Mobject.get_family │
│ 36 │ extracted_mobjects = remove_list_redundancies( │
│ ❱ 37 │ │ list(it.chain(*(method(m) for m in mobjects))), │
│ 38 │ ) │
│ 39 │ if use_z_index: │
│ 40 │ │ return sorted(extracted_mobjects, key=lambda m: m.z_index) │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\utils\family.py:37 in <genexpr> │
│ │
│ 34 │ else: │
│ 35 │ │ method = Mobject.get_family │
│ 36 │ extracted_mobjects = remove_list_redundancies( │
│ ❱ 37 │ │ list(it.chain(*(method(m) for m in mobjects))), │
│ 38 │ ) │
│ 39 │ if use_z_index: │
│ 40 │ │ return sorted(extracted_mobjects, key=lambda m: m.z_index) │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\mobject\mobject.py:2147 in get_family │
│ │
│ 2144 │ def get_family(self, recurse=True): │
│ 2145 │ │ sub_families = list(map(Mobject.get_family, self.submobjects)) │
│ 2146 │ │ all_mobjects = [self] + list(it.chain(*sub_families)) │
│ ❱ 2147 │ │ return remove_list_redundancies(all_mobjects) │
│ 2148 │ │
│ 2149 │ def family_members_with_points(self): │
│ 2150 │ │ return [m for m in self.get_family() if m.get_num_points() > 0] │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\utils\iterables.py:230 in remove_list_redundancies │
│ │
│ 227 │ reversed_result = [] │
│ 228 │ used = set() │
│ 229 │ for x in reversed(lst): │
│ ❱ 230 │ │ if x not in used: │
│ 231 │ │ │ reversed_result.append(x) │
│ 232 │ │ │ used.add(x) │
│ 233 │ reversed_result.reverse() │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
TypeError: unhashable type: 'MArrayPointer'
So it seems like somewhere in the Manim library, the hash function is used.
Then I tried to implement the hash function. In my idea, the hash function should return the same value if all the attributes are the same. So I use the following code as my implementation:
def __key(self) -> tuple:
# return the tuple of all attributues, used for hashing
return (self.__scene, self.__arr, self.__index, self.__label, self.__arrow_len, self.__arrow_gap, self.__label_gap,
self.__pointer_pos, self.__updater_pos
)
def __hash__(self) -> int:
return hash(self.__key())
And the following error is shown after run the initial code:
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ E:\pythons\3.11\Lib\site-packages\manim\cli\render\commands.py:115 in render │
│ │
│ 112 │ │ │ try: │
│ 113 │ │ │ │ with tempconfig({}): │
│ 114 │ │ │ │ │ scene = SceneClass() │
│ ❱ 115 │ │ │ │ │ scene.render() │
│ 116 │ │ │ except Exception: │
│ 117 │ │ │ │ error_console.print_exception() │
│ 118 │ │ │ │ sys.exit(1) │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\scene\scene.py:223 in render │
│ │
│ 220 │ │ """ │
│ 221 │ │ self.setup() │
│ 222 │ │ try: │
│ ❱ 223 │ │ │ self.construct() │
│ 224 │ │ except EndSceneEarlyException: │
│ 225 │ │ │ pass │
│ 226 │ │ except RerunSceneException as e: │
│ │
│ E:\prog\python\manim\manim-data-structures\tests\__init__.py:18 in construct │
│ │
│ 15 │ │ │
│ 16 │ │ self.play(Create(arr)) │
│ 17 │ │ self.play(Create(tar_val)) │
│ ❱ 18 │ │ self.play(Create(l)) │
│ 19 │ │ self.play(Create(r)) │
│ 20 │ │ │
│ 21 │ │ mid = MArrayPointer(self, arr, (l + r) // 2, label="mid") │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\scene\scene.py:1033 in play │
│ │
│ 1030 │ │ │ return │
│ 1031 │ │ │
│ 1032 │ │ start_time = self.renderer.time │
│ ❱ 1033 │ │ self.renderer.play(self, *args, **kwargs) │
│ 1034 │ │ run_time = self.renderer.time - start_time │
│ 1035 │ │ if subcaption: │
│ 1036 │ │ │ if subcaption_duration is None: │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\renderer\cairo_renderer.py:93 in play │
│ │
│ 90 │ │ ) │
│ 91 │ │ │
│ 92 │ │ self.file_writer.begin_animation(not self.skip_animations) │
│ ❱ 93 │ │ scene.begin_animations() │
│ 94 │ │ │
│ 95 │ │ # Save a static image, to avoid rendering non moving objects. │
│ 96 │ │ self.save_static_frame_data(scene, scene.static_mobjects) │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\scene\scene.py:1163 in begin_animations │
│ │
│ 1160 │ │ """Start the animations of the scene.""" │
│ 1161 │ │ for animation in self.animations: │
│ 1162 │ │ │ animation._setup_scene(self) │
│ ❱ 1163 │ │ │ animation.begin() │
│ 1164 │ │ │
│ 1165 │ │ if config.renderer == RendererType.CAIRO: │
│ 1166 │ │ │ # Paint all non-moving objects onto the screen, so they don't │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\animation\animation.py:203 in begin │
│ │
│ 200 │ │ │ # the internal updaters of self.starting_mobject, │
│ 201 │ │ │ # or any others among self.get_all_mobjects() │
│ 202 │ │ │ self.mobject.suspend_updating() │
│ ❱ 203 │ │ self.interpolate(0) │
│ 204 │ │
│ 205 │ def finish(self) -> None: │
│ 206 │ │ # TODO: begin and finish should require a scene as parameter. │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\animation\animation.py:323 in interpolate │
│ │
│ 320 │ │ │ The relative time to set the animation to, 0 meaning the start, 1 meaning │
│ 321 │ │ │ the end. │
│ 322 │ │ """ │
│ ❱ 323 │ │ self.interpolate_mobject(alpha) │
│ 324 │ │
│ 325 │ def interpolate_mobject(self, alpha: float) -> None: │
│ 326 │ │ """Interpolates the mobject of the :class:`Animation` based on alpha value. │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\animation\animation.py:335 in interpolate_mobject │
│ │
│ 332 │ │ │ is completed. For example, alpha-values of 0, 0.5, and 1 correspond │
│ 333 │ │ │ to the animation being completed 0%, 50%, and 100%, respectively. │
│ 334 │ │ """ │
│ ❱ 335 │ │ families = list(self.get_all_families_zipped()) │
│ 336 │ │ for i, mobs in enumerate(families): │
│ 337 │ │ │ sub_alpha = self.get_sub_alpha(alpha, i, len(families)) │
│ 338 │ │ │ self.interpolate_submobject(*mobs, sub_alpha) │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\animation\animation.py:271 in get_all_families_zipped │
│ │
│ 268 │ def get_all_families_zipped(self) -> Iterable[tuple]: │
│ 269 │ │ if config["renderer"] == RendererType.OPENGL: │
│ 270 │ │ │ return zip(*(mob.get_family() for mob in self.get_all_mobjects())) │
│ ❱ 271 │ │ return zip( │
│ 272 │ │ │ *(mob.family_members_with_points() for mob in self.get_all_mobjects()) │
│ 273 │ │ ) │
│ 274 │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\animation\animation.py:272 in <genexpr> │
│ │
│ 269 │ │ if config["renderer"] == RendererType.OPENGL: │
│ 270 │ │ │ return zip(*(mob.get_family() for mob in self.get_all_mobjects())) │
│ 271 │ │ return zip( │
│ ❱ 272 │ │ │ *(mob.family_members_with_points() for mob in self.get_all_mobjects()) │
│ 273 │ │ ) │
│ 274 │ │
│ 275 │ def update_mobjects(self, dt: float) -> None: │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\mobject\mobject.py:2150 in family_members_with_points │
│ │
│ 2147 │ │ return remove_list_redundancies(all_mobjects) │
│ 2148 │ │
│ 2149 │ def family_members_with_points(self): │
│ ❱ 2150 │ │ return [m for m in self.get_family() if m.get_num_points() > 0] │
│ 2151 │ │
│ 2152 │ def arrange( │
│ 2153 │ │ self, │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\mobject\mobject.py:2147 in get_family │
│ │
│ 2144 │ def get_family(self, recurse=True): │
│ 2145 │ │ sub_families = list(map(Mobject.get_family, self.submobjects)) │
│ 2146 │ │ all_mobjects = [self] + list(it.chain(*sub_families)) │
│ ❱ 2147 │ │ return remove_list_redundancies(all_mobjects) │
│ 2148 │ │
│ 2149 │ def family_members_with_points(self): │
│ 2150 │ │ return [m for m in self.get_family() if m.get_num_points() > 0] │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\utils\iterables.py:230 in remove_list_redundancies │
│ │
│ 227 │ reversed_result = [] │
│ 228 │ used = set() │
│ 229 │ for x in reversed(lst): │
│ ❱ 230 │ │ if x not in used: │
│ 231 │ │ │ reversed_result.append(x) │
│ 232 │ │ │ used.add(x) │
│ 233 │ reversed_result.reverse() │
│ │
│ E:\prog\python\manim\manim-data-structures\./src\manim_data_structures\m_array_pointer.py:658 in │
│ __hash__ │
│ │
│ 655 │ │ │ self.__pointer_pos, self.__updater_pos │
│ 656 │ │ │ │ ) │
│ 657 │ def __hash__(self) -> int: │
│ ❱ 658 │ │ return hash(self.__key()) │
│ 659 │
│ │
│ E:\prog\python\manim\manim-data-structures\./src\manim_data_structures\m_array_pointer.py:654 in │
│ __key │
│ │
│ 651 │ │
│ 652 │ def __key(self) -> tuple: │
│ 653 │ │ # return the tuple of all attributues, used for hashing │
│ ❱ 654 │ │ return (self.__scene, self.__arr, self.__index, self.__label, self.__arrow_len, │
│ 655 │ │ │ self.__pointer_pos, self.__updater_pos │
│ 656 │ │ │ │ ) │
│ 657 │ def __hash__(self) -> int: │
│ │
│ E:\pythons\3.11\Lib\site-packages\manim\mobject\mobject.py:660 in __getattr__ │
│ │
│ 657 │ │ │ return types.MethodType(setter, self) │
│ 658 │ │ │
│ 659 │ │ # Unhandled attribute, therefore error │
│ ❱ 660 │ │ raise AttributeError(f"{type(self).__name__} object has no attribute '{attr}'") │
│ 661 │ │
│ 662 │ @property │
│ 663 │ def width(self): │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
AttributeError: MArrayPointer object has no attribute '_MArrayPointer__scene'
Basically it says there is no attributes named __scene
.
Now I used this implementation in my forked repo:
def __hash__(self) -> int:
return id(self)
But the problem is that it will not give the same value even if all the attributes are the same since the address of the object in memory is not the same.
So I'm wondering what the correct way of implementing this. if I used id()
way, It might be possible that the same thing (but a different object) is being rendered multiple times.