mirror of
https://github.com/3b1b/manim.git
synced 2025-09-01 00:48:45 +00:00
Merge pull request #1 from 3b1b/master
This commit is contained in:
commit
37f1130de6
17 changed files with 376 additions and 273 deletions
14
.github/ISSUE_TEMPLATE.md
vendored
14
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,14 +0,0 @@
|
||||||
### If this is a support request:
|
|
||||||
|
|
||||||
**Please attempt to solve the problem on your own before opening an issue.**
|
|
||||||
Between old issues, StackOverflow, and Google, you should be able to find
|
|
||||||
solutions to most of the common problems.
|
|
||||||
|
|
||||||
Include at least:
|
|
||||||
1. Steps to reproduce the issue (e.g. the command you ran)
|
|
||||||
2. The unexpected behavior that occurred (e.g. error messages or screenshots)
|
|
||||||
3. The environment (e.g. operating system and version of manim)
|
|
||||||
|
|
||||||
|
|
||||||
### If this is a feature request:
|
|
||||||
Include the motivation for making this change.
|
|
20
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Describe the bug
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
**Code**:
|
||||||
|
<!-- The code you run which reflect the bug. -->
|
||||||
|
|
||||||
|
**Wrong display or Error traceback**:
|
||||||
|
<!-- the wrong display result of the code you run, or the error Traceback -->
|
||||||
|
|
||||||
|
### Additional context
|
||||||
|
<!-- Add any other context about the problem here. -->
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Ask A Question
|
||||||
|
url: https://github.com/3b1b/manim/discussions/categories/q-a
|
||||||
|
about: Please ask questions you encountered here.
|
23
.github/ISSUE_TEMPLATE/error-when-using.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/error-when-using.md
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
name: Error when using
|
||||||
|
about: The error you encountered while using manim
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Describe the error
|
||||||
|
<!-- A clear and concise description of what you want to make. -->
|
||||||
|
|
||||||
|
### Code and Error
|
||||||
|
**Code**:
|
||||||
|
<!-- The code you run -->
|
||||||
|
|
||||||
|
**Error**:
|
||||||
|
<!-- The error traceback you get when run your code -->
|
||||||
|
|
||||||
|
### Environment
|
||||||
|
**OS System**:
|
||||||
|
**manim version**: master <!-- make sure you are using the latest version of master branch -->
|
||||||
|
**python version**:
|
23
.github/PULL_REQUEST_TEMPLATE.md
vendored
23
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -1,9 +1,18 @@
|
||||||
Thanks for contributing to manim!
|
<!-- Thanks for contributing to manim!
|
||||||
|
Please ensure that your pull request works with the latest version of manim.
|
||||||
|
-->
|
||||||
|
|
||||||
**Please ensure that your pull request works with the latest version of manim.**
|
## Motivation
|
||||||
You should also include:
|
<!-- Outline your motivation: In what way do your changes improve the library? -->
|
||||||
|
|
||||||
1. The motivation for making this change (or link the relevant issues)
|
## Proposed changes
|
||||||
2. How you tested the new behavior (e.g. a minimal working example, before/after
|
<!-- What you changed in those files -->
|
||||||
screenshots, gifs, commands, etc.) This is rather informal at the moment, but
|
-
|
||||||
the goal is to show us how you know the pull request works as intended.
|
-
|
||||||
|
-
|
||||||
|
|
||||||
|
## Test
|
||||||
|
<!-- How do you test your changes -->
|
||||||
|
**Code**:
|
||||||
|
|
||||||
|
**Result**:
|
|
@ -497,7 +497,8 @@ class ControlsExample(Scene):
|
||||||
self.add(MotionMobject(text))
|
self.add(MotionMobject(text))
|
||||||
|
|
||||||
self.textbox.set_value("Manim")
|
self.textbox.set_value("Manim")
|
||||||
self.embed()
|
# self.wait(60)
|
||||||
|
# self.embed()
|
||||||
|
|
||||||
|
|
||||||
# See https://github.com/3b1b/videos for many, many more
|
# See https://github.com/3b1b/videos for many, many more
|
||||||
|
|
6
manimlib/event_handler/__init__.py
Normal file
6
manimlib/event_handler/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from manimlib.event_handler.event_dispatcher import EventDispatcher
|
||||||
|
|
||||||
|
|
||||||
|
# This is supposed to be a Singleton
|
||||||
|
# i.e., during runtime there should be only one object of Event Dispatcher
|
||||||
|
EVENT_DISPATCHER = EventDispatcher()
|
92
manimlib/event_handler/event_dispatcher.py
Normal file
92
manimlib/event_handler/event_dispatcher.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from manimlib.event_handler.event_type import EventType
|
||||||
|
from manimlib.event_handler.event_listner import EventListner
|
||||||
|
|
||||||
|
|
||||||
|
class EventDispatcher(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.event_listners = {
|
||||||
|
event_type: []
|
||||||
|
for event_type in EventType
|
||||||
|
}
|
||||||
|
self.mouse_point = np.array((0., 0., 0.))
|
||||||
|
self.mouse_drag_point = np.array((0., 0., 0.))
|
||||||
|
self.pressed_keys = set()
|
||||||
|
self.draggable_object_listners = []
|
||||||
|
|
||||||
|
def add_listner(self, event_listner):
|
||||||
|
assert(isinstance(event_listner, EventListner))
|
||||||
|
self.event_listners[event_listner.event_type].append(event_listner)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def remove_listner(self, event_listner):
|
||||||
|
assert(isinstance(event_listner, EventListner))
|
||||||
|
try:
|
||||||
|
while event_listner in self.event_listners[event_listner.event_type]:
|
||||||
|
self.event_listners[event_listner.event_type].remove(event_listner)
|
||||||
|
except:
|
||||||
|
# raise ValueError("Handler is not handling this event, so cannot remove it.")
|
||||||
|
pass
|
||||||
|
return self
|
||||||
|
|
||||||
|
def dispatch(self, event_type, **event_data):
|
||||||
|
|
||||||
|
if event_type == EventType.MouseMotionEvent:
|
||||||
|
self.mouse_point = event_data["point"]
|
||||||
|
elif event_type == EventType.MouseDragEvent:
|
||||||
|
self.mouse_drag_point = event_data["point"]
|
||||||
|
elif event_type == EventType.KeyPressEvent:
|
||||||
|
self.pressed_keys.add(event_data["symbol"]) # Modifiers?
|
||||||
|
elif event_type == EventType.KeyReleaseEvent:
|
||||||
|
self.pressed_keys.difference_update({event_data["symbol"]}) # Modifiers?
|
||||||
|
elif event_type == EventType.MousePressEvent:
|
||||||
|
self.draggable_object_listners = [
|
||||||
|
listner
|
||||||
|
for listner in self.event_listners[EventType.MouseDragEvent]
|
||||||
|
if listner.mobject.is_point_touching(self.mouse_point)
|
||||||
|
]
|
||||||
|
elif event_type == EventType.MouseReleaseEvent:
|
||||||
|
self.draggable_object_listners = []
|
||||||
|
|
||||||
|
propagate_event = None
|
||||||
|
|
||||||
|
if event_type == EventType.MouseDragEvent:
|
||||||
|
for listner in self.draggable_object_listners:
|
||||||
|
assert(isinstance(listner, EventListner))
|
||||||
|
propagate_event = listner.callback(listner.mobject, event_data)
|
||||||
|
if propagate_event is not None and propagate_event is False:
|
||||||
|
return propagate_event
|
||||||
|
|
||||||
|
elif event_type.value.startswith('mouse'):
|
||||||
|
for listner in self.event_listners[event_type]:
|
||||||
|
if listner.mobject.is_point_touching(self.mouse_point):
|
||||||
|
propagate_event = listner.callback(
|
||||||
|
listner.mobject, event_data)
|
||||||
|
if propagate_event is not None and propagate_event is False:
|
||||||
|
return propagate_event
|
||||||
|
|
||||||
|
elif event_type.value.startswith('key'):
|
||||||
|
for listner in self.event_listners[event_type]:
|
||||||
|
propagate_event = listner.callback(listner.mobject, event_data)
|
||||||
|
if propagate_event is not None and propagate_event is False:
|
||||||
|
return propagate_event
|
||||||
|
|
||||||
|
return propagate_event
|
||||||
|
|
||||||
|
def get_listners_count(self):
|
||||||
|
return sum([len(value) for key, value in self.event_listners.items()])
|
||||||
|
|
||||||
|
def get_mouse_point(self):
|
||||||
|
return self.mouse_point
|
||||||
|
|
||||||
|
def get_mouse_drag_point(self):
|
||||||
|
return self.mouse_drag_point
|
||||||
|
|
||||||
|
def is_key_pressed(self, symbol):
|
||||||
|
return (symbol in self.pressed_keys)
|
||||||
|
|
||||||
|
__iadd__ = add_listner
|
||||||
|
__isub__ = remove_listner
|
||||||
|
__call__ = dispatch
|
||||||
|
__len__ = get_listners_count
|
15
manimlib/event_handler/event_listner.py
Normal file
15
manimlib/event_handler/event_listner.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class EventListner(object):
|
||||||
|
def __init__(self, mobject, event_type, event_callback):
|
||||||
|
self.mobject = mobject
|
||||||
|
self.event_type = event_type
|
||||||
|
self.callback = event_callback
|
||||||
|
|
||||||
|
def __eq__(self, o: object) -> bool:
|
||||||
|
return_val = False
|
||||||
|
try:
|
||||||
|
return_val = self.callback == o.callback \
|
||||||
|
and self.mobject == o.mobject \
|
||||||
|
and self.event_type == o.event_type
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return return_val
|
11
manimlib/event_handler/event_type.py
Normal file
11
manimlib/event_handler/event_type.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class EventType(Enum):
|
||||||
|
MouseMotionEvent = 'mouse_motion_event'
|
||||||
|
MousePressEvent = 'mouse_press_event'
|
||||||
|
MouseReleaseEvent = 'mouse_release_event'
|
||||||
|
MouseDragEvent = 'mouse_drag_event'
|
||||||
|
MouseScrollEvent = 'mouse_scroll_event'
|
||||||
|
KeyPressEvent = 'key_press_event'
|
||||||
|
KeyReleaseEvent = 'key_release_event'
|
|
@ -22,50 +22,43 @@ class MotionMobject(Mobject):
|
||||||
You could hold and drag this object to any position
|
You could hold and drag this object to any position
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CONFIG = {
|
|
||||||
"listen_to_events": True
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, mobject, **kwargs):
|
def __init__(self, mobject, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
assert(isinstance(mobject, Mobject))
|
||||||
self.mobject = mobject
|
self.mobject = mobject
|
||||||
|
self.mobject.add_mouse_drag_listner(self.mob_on_mouse_drag)
|
||||||
# To avoid locking it as static mobject
|
# To avoid locking it as static mobject
|
||||||
self.mobject.add_updater(lambda mob: None)
|
self.mobject.add_updater(lambda mob: None)
|
||||||
self.add(mobject)
|
self.add(mobject)
|
||||||
|
|
||||||
def on_mouse_drag(self, point, d_point, buttons, modifiers):
|
def mob_on_mouse_drag(self, mob, event_data):
|
||||||
if self.mobject.is_point_touching(point):
|
mob.move_to(event_data["point"])
|
||||||
self.mobject.move_to(point)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Button(Mobject):
|
class Button(Mobject):
|
||||||
"""
|
"""
|
||||||
Pass any mobject and register an on_click method
|
Pass any mobject and register an on_click method
|
||||||
"""
|
|
||||||
|
|
||||||
CONFIG = {
|
The on_click method takes mobject as argument like updater
|
||||||
"listen_to_events": True
|
"""
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, mobject, on_click, **kwargs):
|
def __init__(self, mobject, on_click, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.mobject, self.on_click = mobject, on_click
|
assert(isinstance(mobject, Mobject))
|
||||||
|
self.on_click = on_click
|
||||||
|
self.mobject = mobject
|
||||||
|
self.mobject.add_mouse_press_listner(self.mob_on_mouse_press)
|
||||||
self.add(self.mobject)
|
self.add(self.mobject)
|
||||||
|
|
||||||
def on_mouse_press(self, point, button, mods):
|
def mob_on_mouse_press(self, mob, event_data):
|
||||||
if self.mobject.is_point_touching(point):
|
self.on_click(mob)
|
||||||
self.on_click()
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# Controls
|
# Controls
|
||||||
|
|
||||||
class ContolMobject(ValueTracker):
|
class ControlMobject(ValueTracker):
|
||||||
CONFIG = {
|
|
||||||
"listen_to_events": True
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, value, *mobjects, **kwargs):
|
def __init__(self, value, *mobjects, **kwargs):
|
||||||
super().__init__(value=value, **kwargs)
|
super().__init__(value=value, **kwargs)
|
||||||
self.add(*mobjects)
|
self.add(*mobjects)
|
||||||
|
@ -88,7 +81,7 @@ class ContolMobject(ValueTracker):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class EnableDisableButton(ContolMobject):
|
class EnableDisableButton(ControlMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"value_type": np.dtype(bool),
|
"value_type": np.dtype(bool),
|
||||||
"rect_kwargs": {
|
"rect_kwargs": {
|
||||||
|
@ -104,6 +97,7 @@ class EnableDisableButton(ContolMobject):
|
||||||
digest_config(self, kwargs)
|
digest_config(self, kwargs)
|
||||||
self.box = Rectangle(**self.rect_kwargs)
|
self.box = Rectangle(**self.rect_kwargs)
|
||||||
super().__init__(value, self.box, **kwargs)
|
super().__init__(value, self.box, **kwargs)
|
||||||
|
self.add_mouse_press_listner(self.on_mouse_press)
|
||||||
|
|
||||||
def assert_value(self, value):
|
def assert_value(self, value):
|
||||||
assert(isinstance(value, bool))
|
assert(isinstance(value, bool))
|
||||||
|
@ -117,12 +111,12 @@ class EnableDisableButton(ContolMobject):
|
||||||
def toggle_value(self):
|
def toggle_value(self):
|
||||||
super().set_value(not self.get_value())
|
super().set_value(not self.get_value())
|
||||||
|
|
||||||
def on_mouse_press(self, point, button, mods):
|
def on_mouse_press(self, mob, event_data):
|
||||||
self.toggle_value()
|
mob.toggle_value()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Checkbox(ContolMobject):
|
class Checkbox(ControlMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"value_type": np.dtype(bool),
|
"value_type": np.dtype(bool),
|
||||||
"rect_kwargs": {
|
"rect_kwargs": {
|
||||||
|
@ -147,6 +141,7 @@ class Checkbox(ContolMobject):
|
||||||
self.box = Rectangle(**self.rect_kwargs)
|
self.box = Rectangle(**self.rect_kwargs)
|
||||||
self.box_content = self.get_checkmark() if value else self.get_cross()
|
self.box_content = self.get_checkmark() if value else self.get_cross()
|
||||||
super().__init__(value, self.box, self.box_content, **kwargs)
|
super().__init__(value, self.box, self.box_content, **kwargs)
|
||||||
|
self.add_mouse_press_listner(self.on_mouse_press)
|
||||||
|
|
||||||
def assert_value(self, value):
|
def assert_value(self, value):
|
||||||
assert(isinstance(value, bool))
|
assert(isinstance(value, bool))
|
||||||
|
@ -160,8 +155,8 @@ class Checkbox(ContolMobject):
|
||||||
else:
|
else:
|
||||||
self.box_content.become(self.get_cross())
|
self.box_content.become(self.get_cross())
|
||||||
|
|
||||||
def on_mouse_press(self, point, button, mods):
|
def on_mouse_press(self, mob, event_data):
|
||||||
self.toggle_value()
|
mob.toggle_value()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Helper methods
|
# Helper methods
|
||||||
|
@ -191,7 +186,7 @@ class Checkbox(ContolMobject):
|
||||||
return cross
|
return cross
|
||||||
|
|
||||||
|
|
||||||
class LinearNumberSlider(ContolMobject):
|
class LinearNumberSlider(ControlMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"value_type": np.float64,
|
"value_type": np.float64,
|
||||||
"min_value": -10.0,
|
"min_value": -10.0,
|
||||||
|
@ -222,6 +217,8 @@ class LinearNumberSlider(ContolMobject):
|
||||||
self.slider_axis.set_opacity(0.0)
|
self.slider_axis.set_opacity(0.0)
|
||||||
self.slider.move_to(self.slider_axis)
|
self.slider.move_to(self.slider_axis)
|
||||||
|
|
||||||
|
self.slider.add_mouse_drag_listner(self.slider_on_mouse_drag)
|
||||||
|
|
||||||
super().__init__(value, self.bar, self.slider, self.slider_axis, ** kwargs)
|
super().__init__(value, self.bar, self.slider, self.slider_axis, ** kwargs)
|
||||||
|
|
||||||
def assert_value(self, value):
|
def assert_value(self, value):
|
||||||
|
@ -231,9 +228,8 @@ class LinearNumberSlider(ContolMobject):
|
||||||
prop = (value - self.min_value) / (self.max_value - self.min_value)
|
prop = (value - self.min_value) / (self.max_value - self.min_value)
|
||||||
self.slider.move_to(self.slider_axis.point_from_proportion(prop))
|
self.slider.move_to(self.slider_axis.point_from_proportion(prop))
|
||||||
|
|
||||||
def on_mouse_drag(self, point, d_point, buttons, modifiers):
|
def slider_on_mouse_drag(self, mob, event_data):
|
||||||
if self.slider.is_point_touching(point):
|
self.set_value(self.get_value_from_point(event_data["point"]))
|
||||||
self.set_value(self.get_value_from_point(point))
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Helper Methods
|
# Helper Methods
|
||||||
|
@ -348,7 +344,7 @@ class ColorSliders(Group):
|
||||||
return rgba[3]
|
return rgba[3]
|
||||||
|
|
||||||
|
|
||||||
class Textbox(ContolMobject):
|
class Textbox(ControlMobject):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"value_type": np.dtype(object),
|
"value_type": np.dtype(object),
|
||||||
|
|
||||||
|
@ -371,10 +367,12 @@ class Textbox(ContolMobject):
|
||||||
digest_config(self, kwargs)
|
digest_config(self, kwargs)
|
||||||
self.isActive = self.isInitiallyActive
|
self.isActive = self.isInitiallyActive
|
||||||
self.box = Rectangle(**self.box_kwargs)
|
self.box = Rectangle(**self.box_kwargs)
|
||||||
|
self.box.add_mouse_press_listner(self.box_on_mouse_press)
|
||||||
self.text = Text(value, **self.text_kwargs)
|
self.text = Text(value, **self.text_kwargs)
|
||||||
super().__init__(value, self.box, self.text, **kwargs)
|
super().__init__(value, self.box, self.text, **kwargs)
|
||||||
self.update_text(value)
|
self.update_text(value)
|
||||||
self.active_anim(self.isActive)
|
self.active_anim(self.isActive)
|
||||||
|
self.add_key_press_listner(self.on_key_press)
|
||||||
|
|
||||||
def set_value_anim(self, value):
|
def set_value_anim(self, value):
|
||||||
self.update_text(value)
|
self.update_text(value)
|
||||||
|
@ -397,16 +395,17 @@ class Textbox(ContolMobject):
|
||||||
else:
|
else:
|
||||||
self.box.set_stroke(self.deactive_color)
|
self.box.set_stroke(self.deactive_color)
|
||||||
|
|
||||||
def on_mouse_press(self, point, button, mods):
|
def box_on_mouse_press(self, mob, event_data):
|
||||||
if self.box.is_point_touching(point):
|
|
||||||
self.isActive = not self.isActive
|
self.isActive = not self.isActive
|
||||||
self.active_anim(self.isActive)
|
self.active_anim(self.isActive)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def on_key_press(self, symbol, modifiers):
|
def on_key_press(self, mob, event_data):
|
||||||
|
symbol = event_data["symbol"]
|
||||||
|
modifiers = event_data["modifiers"]
|
||||||
char = chr(symbol)
|
char = chr(symbol)
|
||||||
if self.isActive:
|
if mob.isActive:
|
||||||
old_value = self.get_value()
|
old_value = mob.get_value()
|
||||||
new_value = old_value
|
new_value = old_value
|
||||||
if char.isalnum():
|
if char.isalnum():
|
||||||
if (modifiers & PygletWindowKeys.MOD_SHIFT) or (modifiers & PygletWindowKeys.MOD_CAPSLOCK):
|
if (modifiers & PygletWindowKeys.MOD_SHIFT) or (modifiers & PygletWindowKeys.MOD_CAPSLOCK):
|
||||||
|
@ -419,13 +418,12 @@ class Textbox(ContolMobject):
|
||||||
new_value = old_value + '\t'
|
new_value = old_value + '\t'
|
||||||
elif symbol == PygletWindowKeys.BACKSPACE:
|
elif symbol == PygletWindowKeys.BACKSPACE:
|
||||||
new_value = old_value[:-1] or ''
|
new_value = old_value[:-1] or ''
|
||||||
self.set_value(new_value)
|
mob.set_value(new_value)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class ControlPanel(Group):
|
class ControlPanel(Group):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"listen_to_events": True,
|
|
||||||
"panel_kwargs": {
|
"panel_kwargs": {
|
||||||
"width": FRAME_WIDTH / 4,
|
"width": FRAME_WIDTH / 4,
|
||||||
"height": MED_SMALL_BUFF + FRAME_HEIGHT,
|
"height": MED_SMALL_BUFF + FRAME_HEIGHT,
|
||||||
|
@ -451,6 +449,7 @@ class ControlPanel(Group):
|
||||||
self.panel = Rectangle(**self.panel_kwargs)
|
self.panel = Rectangle(**self.panel_kwargs)
|
||||||
self.panel.to_corner(UP + LEFT, buff=0)
|
self.panel.to_corner(UP + LEFT, buff=0)
|
||||||
self.panel.shift(self.panel.get_height() * UP)
|
self.panel.shift(self.panel.get_height() * UP)
|
||||||
|
self.panel.add_mouse_scroll_listner(self.panel_on_mouse_scroll)
|
||||||
|
|
||||||
self.panel_opener_rect = Rectangle(**self.opener_kwargs)
|
self.panel_opener_rect = Rectangle(**self.opener_kwargs)
|
||||||
self.panel_info_text = Text(**self.opener_text_kwargs)
|
self.panel_info_text = Text(**self.opener_text_kwargs)
|
||||||
|
@ -458,6 +457,7 @@ class ControlPanel(Group):
|
||||||
|
|
||||||
self.panel_opener = Group(self.panel_opener_rect, self.panel_info_text)
|
self.panel_opener = Group(self.panel_opener_rect, self.panel_info_text)
|
||||||
self.panel_opener.next_to(self.panel, DOWN, aligned_edge=DOWN)
|
self.panel_opener.next_to(self.panel, DOWN, aligned_edge=DOWN)
|
||||||
|
self.panel_opener.add_mouse_drag_listner(self.panel_opener_on_mouse_drag)
|
||||||
|
|
||||||
self.controls = Group(*controls)
|
self.controls = Group(*controls)
|
||||||
self.controls.arrange(DOWN, center=False, aligned_edge=ORIGIN)
|
self.controls.arrange(DOWN, center=False, aligned_edge=ORIGIN)
|
||||||
|
@ -510,14 +510,14 @@ class ControlPanel(Group):
|
||||||
self.move_panel_and_controls_to_panel_opener()
|
self.move_panel_and_controls_to_panel_opener()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def on_mouse_drag(self, point, d_point, buttons, modifiers):
|
def panel_opener_on_mouse_drag(self, mob, event_data):
|
||||||
if self.panel_opener.is_point_touching(point):
|
point = event_data["point"]
|
||||||
self.panel_opener.match_y(Dot(point))
|
self.panel_opener.match_y(Dot(point))
|
||||||
self.move_panel_and_controls_to_panel_opener()
|
self.move_panel_and_controls_to_panel_opener()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def on_mouse_scroll(self, point, offset):
|
def panel_on_mouse_scroll(self, mob, event_data):
|
||||||
if self.panel.is_point_touching(point):
|
offset = event_data["offset"]
|
||||||
factor = 10 * offset[1]
|
factor = 10 * offset[1]
|
||||||
self.controls.set_y(self.controls.get_y() + factor)
|
self.controls.set_y(self.controls.get_y() + factor)
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -27,6 +27,9 @@ from manimlib.utils.space_ops import get_norm
|
||||||
from manimlib.utils.space_ops import rotation_matrix_transpose
|
from manimlib.utils.space_ops import rotation_matrix_transpose
|
||||||
from manimlib.shader_wrapper import ShaderWrapper
|
from manimlib.shader_wrapper import ShaderWrapper
|
||||||
from manimlib.shader_wrapper import get_colormap_code
|
from manimlib.shader_wrapper import get_colormap_code
|
||||||
|
from manimlib.event_handler import EVENT_DISPATCHER
|
||||||
|
from manimlib.event_handler.event_listner import EventListner
|
||||||
|
from manimlib.event_handler.event_type import EventType
|
||||||
|
|
||||||
|
|
||||||
class Mobject(object):
|
class Mobject(object):
|
||||||
|
@ -68,6 +71,7 @@ class Mobject(object):
|
||||||
self.init_data()
|
self.init_data()
|
||||||
self.init_uniforms()
|
self.init_uniforms()
|
||||||
self.init_updaters()
|
self.init_updaters()
|
||||||
|
self.init_event_listners()
|
||||||
self.init_points()
|
self.init_points()
|
||||||
self.init_colors()
|
self.init_colors()
|
||||||
self.init_shader_data()
|
self.init_shader_data()
|
||||||
|
@ -1440,35 +1444,81 @@ class Mobject(object):
|
||||||
Event handling follows the Event Bubbling model of DOM in javascript.
|
Event handling follows the Event Bubbling model of DOM in javascript.
|
||||||
Return false to stop the event bubbling.
|
Return false to stop the event bubbling.
|
||||||
To learn more visit https://www.quirksmode.org/js/events_order.html
|
To learn more visit https://www.quirksmode.org/js/events_order.html
|
||||||
|
|
||||||
|
Event Callback Argument is a callable function taking two arguments:
|
||||||
|
1. Mobject
|
||||||
|
2. EventData
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def on_mouse_motion(self, point, d_point):
|
def init_event_listners(self):
|
||||||
# To be implemented in subclasses
|
self.event_listners = []
|
||||||
pass
|
|
||||||
|
|
||||||
def on_mouse_drag(self, point, d_point, buttons, modifiers):
|
def add_event_listner(self, event_type, event_callback):
|
||||||
# To be implemented in subclasses
|
event_listner = EventListner(self, event_type, event_callback)
|
||||||
pass
|
self.event_listners.append(event_listner)
|
||||||
|
EVENT_DISPATCHER.add_listner(event_listner)
|
||||||
|
return self
|
||||||
|
|
||||||
def on_mouse_press(self, point, button, mods):
|
def remove_event_listner(self, event_type, event_callback):
|
||||||
# To be implemented in subclasses
|
event_listner = EventListner(self, event_type, event_callback)
|
||||||
pass
|
while event_listner in self.event_listners:
|
||||||
|
self.event_listners.remove(event_listner)
|
||||||
|
EVENT_DISPATCHER.remove_listner(event_listner)
|
||||||
|
return self
|
||||||
|
|
||||||
def on_mouse_release(self, point, button, mods):
|
def clear_event_listners(self, recurse=True):
|
||||||
# To be implemented in subclasses
|
self.event_listners = []
|
||||||
pass
|
if recurse:
|
||||||
|
for submob in self.submobjects:
|
||||||
|
submob.clear_event_listners(recurse=recurse)
|
||||||
|
return self
|
||||||
|
|
||||||
def on_mouse_scroll(self, point, offset):
|
def get_event_listners(self):
|
||||||
# To be implemented in subclasses
|
return self.event_listners
|
||||||
pass
|
|
||||||
|
|
||||||
def on_key_release(self, symbol, modifiers):
|
def get_family_event_listners(self):
|
||||||
# To be implemented in subclasses
|
return list(it.chain(*[sm.get_event_listners() for sm in self.get_family()]))
|
||||||
pass
|
|
||||||
|
|
||||||
def on_key_press(self, symbol, modifiers):
|
def get_has_event_listner(self):
|
||||||
# To be implemented in subclasses
|
return any(
|
||||||
pass
|
mob.get_event_listners()
|
||||||
|
for mob in self.get_family()
|
||||||
|
)
|
||||||
|
|
||||||
|
def add_mouse_motion_listner(self, callback):
|
||||||
|
self.add_event_listner(EventType.MouseMotionEvent, callback)
|
||||||
|
def remove_mouse_motion_listner(self, callback):
|
||||||
|
self.remove_event_listner(EventType.MouseMotionEvent, callback)
|
||||||
|
|
||||||
|
def add_mouse_press_listner(self, callback):
|
||||||
|
self.add_event_listner(EventType.MousePressEvent, callback)
|
||||||
|
def remove_mouse_press_listner(self, callback):
|
||||||
|
self.remove_event_listner(EventType.MousePressEvent, callback)
|
||||||
|
|
||||||
|
def add_mouse_release_listner(self, callback):
|
||||||
|
self.add_event_listner(EventType.MouseReleaseEvent, callback)
|
||||||
|
def remove_mouse_release_listner(self, callback):
|
||||||
|
self.remove_event_listner(EventType.MouseReleaseEvent, callback)
|
||||||
|
|
||||||
|
def add_mouse_drag_listner(self, callback):
|
||||||
|
self.add_event_listner(EventType.MouseDragEvent, callback)
|
||||||
|
def remove_mouse_drag_listner(self, callback):
|
||||||
|
self.remove_event_listner(EventType.MouseDragEvent, callback)
|
||||||
|
|
||||||
|
def add_mouse_scroll_listner(self, callback):
|
||||||
|
self.add_event_listner(EventType.MouseScrollEvent, callback)
|
||||||
|
def remove_mouse_scroll_listner(self, callback):
|
||||||
|
self.remove_event_listner(EventType.MouseScrollEvent, callback)
|
||||||
|
|
||||||
|
def add_key_press_listner(self, callback):
|
||||||
|
self.add_event_listner(EventType.KeyPressEvent, callback)
|
||||||
|
def remove_key_press_listner(self, callback):
|
||||||
|
self.remove_event_listner(EventType.KeyPressEvent, callback)
|
||||||
|
|
||||||
|
def add_key_release_listner(self, callback):
|
||||||
|
self.add_event_listner(EventType.KeyReleaseEvent, callback)
|
||||||
|
def remove_key_release_listner(self, callback):
|
||||||
|
self.remove_event_listner(EventType.KeyReleaseEvent, callback)
|
||||||
|
|
||||||
# Errors
|
# Errors
|
||||||
|
|
||||||
|
|
|
@ -23,35 +23,6 @@ from manimlib.utils.directories import get_mobject_data_dir
|
||||||
from manimlib.utils.images import get_full_vector_image_path
|
from manimlib.utils.images import get_full_vector_image_path
|
||||||
|
|
||||||
|
|
||||||
def check_and_fix_percent_bug(sym):
|
|
||||||
# This is an ugly patch addressing something which should be
|
|
||||||
# addressed at a deeper level.
|
|
||||||
# The svg path for percent symbols have a known bug, so this
|
|
||||||
# checks if the symbol is (probably) a percentage sign, and
|
|
||||||
# splits it so that it's displayed properly.
|
|
||||||
if len(sym.get_points()) not in [315, 324, 372, 468, 483] or len(sym.get_subpaths()) != 4:
|
|
||||||
return
|
|
||||||
|
|
||||||
sym = sym.family_members_with_points()[0]
|
|
||||||
new_sym = VMobject()
|
|
||||||
path_lengths = [len(path) for path in sym.get_subpaths()]
|
|
||||||
sym_points = sym.get_points()
|
|
||||||
if len(sym_points) in [315, 324, 372]:
|
|
||||||
n = sum(path_lengths[:2])
|
|
||||||
p1 = sym_points[:n]
|
|
||||||
p2 = sym_points[n:]
|
|
||||||
elif len(sym_points) in [468, 483]:
|
|
||||||
p1 = np.vstack([
|
|
||||||
sym_points[:path_lengths[0]],
|
|
||||||
sym_points[-path_lengths[3]:]
|
|
||||||
])
|
|
||||||
p2 = sym_points[path_lengths[0]:sum(path_lengths[:3])]
|
|
||||||
sym.set_points(p1)
|
|
||||||
new_sym.set_points(p2)
|
|
||||||
sym.add(new_sym)
|
|
||||||
sym.refresh_triangulation()
|
|
||||||
|
|
||||||
|
|
||||||
def string_to_numbers(num_string):
|
def string_to_numbers(num_string):
|
||||||
num_string = num_string.replace("-", ",-")
|
num_string = num_string.replace("-", ",-")
|
||||||
num_string = num_string.replace("e,-", "e-")
|
num_string = num_string.replace("e,-", "e-")
|
||||||
|
@ -379,7 +350,6 @@ class VMobjectFromSVGPathstring(VMobject):
|
||||||
self.stretch(-1, 1, about_point=ORIGIN)
|
self.stretch(-1, 1, about_point=ORIGIN)
|
||||||
# Save to a file for future use
|
# Save to a file for future use
|
||||||
np.save(points_filepath, self.get_points())
|
np.save(points_filepath, self.get_points())
|
||||||
check_and_fix_percent_bug(self)
|
|
||||||
|
|
||||||
def get_commands_and_coord_strings(self):
|
def get_commands_and_coord_strings(self):
|
||||||
all_commands = list(self.get_command_to_function_map().keys())
|
all_commands = list(self.get_command_to_function_map().keys())
|
||||||
|
|
|
@ -19,6 +19,8 @@ from manimlib.scene.scene_file_writer import SceneFileWriter
|
||||||
from manimlib.utils.config_ops import digest_config
|
from manimlib.utils.config_ops import digest_config
|
||||||
from manimlib.utils.family_ops import extract_mobject_family_members
|
from manimlib.utils.family_ops import extract_mobject_family_members
|
||||||
from manimlib.utils.family_ops import restructure_list_to_exclude_certain_family_members
|
from manimlib.utils.family_ops import restructure_list_to_exclude_certain_family_members
|
||||||
|
from manimlib.event_handler.event_type import EventType
|
||||||
|
from manimlib.event_handler import EVENT_DISPATCHER
|
||||||
from manimlib.window import Window
|
from manimlib.window import Window
|
||||||
|
|
||||||
|
|
||||||
|
@ -513,23 +515,12 @@ class Scene(object):
|
||||||
self.mobjects = mobjects
|
self.mobjects = mobjects
|
||||||
|
|
||||||
# Event handling
|
# Event handling
|
||||||
def get_event_listeners_mobjects(self):
|
|
||||||
"""
|
|
||||||
This method returns all the mobjects that listen to events
|
|
||||||
in reversed order. So the top most mobject's event is called first.
|
|
||||||
This helps in event bubbling.
|
|
||||||
"""
|
|
||||||
return filter(
|
|
||||||
lambda mob: mob.listen_to_events,
|
|
||||||
reversed(self.get_mobject_family_members())
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_mouse_motion(self, point, d_point):
|
def on_mouse_motion(self, point, d_point):
|
||||||
self.mouse_point.move_to(point)
|
self.mouse_point.move_to(point)
|
||||||
|
|
||||||
for mob_listener in self.get_event_listeners_mobjects():
|
event_data = {"point": point, "d_point": d_point}
|
||||||
if mob_listener.is_point_touching(point):
|
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseMotionEvent, **event_data)
|
||||||
propagate_event = mob_listener.on_mouse_motion(point, d_point)
|
|
||||||
if propagate_event is not None and propagate_event is False:
|
if propagate_event is not None and propagate_event is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -548,30 +539,26 @@ class Scene(object):
|
||||||
def on_mouse_drag(self, point, d_point, buttons, modifiers):
|
def on_mouse_drag(self, point, d_point, buttons, modifiers):
|
||||||
self.mouse_drag_point.move_to(point)
|
self.mouse_drag_point.move_to(point)
|
||||||
|
|
||||||
for mob_listener in self.get_event_listeners_mobjects():
|
event_data = {"point": point, "d_point": d_point, "buttons": buttons, "modifiers": modifiers}
|
||||||
if mob_listener.is_point_touching(point):
|
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseDragEvent, **event_data)
|
||||||
propagate_event = mob_listener.on_mouse_drag(point, d_point, buttons, modifiers)
|
|
||||||
if propagate_event is not None and propagate_event is False:
|
if propagate_event is not None and propagate_event is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
def on_mouse_press(self, point, button, mods):
|
def on_mouse_press(self, point, button, mods):
|
||||||
for mob_listener in self.get_event_listeners_mobjects():
|
event_data = {"point": point, "button": button, "mods": mods}
|
||||||
if mob_listener.is_point_touching(point):
|
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MousePressEvent, **event_data)
|
||||||
propagate_event = mob_listener.on_mouse_press(point, button, mods)
|
|
||||||
if propagate_event is not None and propagate_event is False:
|
if propagate_event is not None and propagate_event is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
def on_mouse_release(self, point, button, mods):
|
def on_mouse_release(self, point, button, mods):
|
||||||
for mob_listener in self.get_event_listeners_mobjects():
|
event_data = {"point": point, "button": button, "mods": mods}
|
||||||
if mob_listener.is_point_touching(point):
|
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseReleaseEvent, **event_data)
|
||||||
propagate_event = mob_listener.on_mouse_release(point, button, mods)
|
|
||||||
if propagate_event is not None and propagate_event is False:
|
if propagate_event is not None and propagate_event is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
def on_mouse_scroll(self, point, offset):
|
def on_mouse_scroll(self, point, offset):
|
||||||
for mob_listener in self.get_event_listeners_mobjects():
|
event_data = {"point": point, "offset": offset}
|
||||||
if mob_listener.is_point_touching(point):
|
propagate_event = EVENT_DISPATCHER.dispatch(EventType.MouseScrollEvent, **event_data)
|
||||||
propagate_event = mob_listener.on_mouse_scroll(point, offset)
|
|
||||||
if propagate_event is not None and propagate_event is False:
|
if propagate_event is not None and propagate_event is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -585,8 +572,8 @@ class Scene(object):
|
||||||
frame.shift(-20.0 * shift)
|
frame.shift(-20.0 * shift)
|
||||||
|
|
||||||
def on_key_release(self, symbol, modifiers):
|
def on_key_release(self, symbol, modifiers):
|
||||||
for mob_listener in self.get_event_listeners_mobjects():
|
event_data = {"symbol": symbol, "modifiers": modifiers}
|
||||||
propagate_event = mob_listener.on_key_release(symbol, modifiers)
|
propagate_event = EVENT_DISPATCHER.dispatch(EventType.KeyReleaseEvent, **event_data)
|
||||||
if propagate_event is not None and propagate_event is False:
|
if propagate_event is not None and propagate_event is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -597,8 +584,8 @@ class Scene(object):
|
||||||
print(" Warning: The value of the pressed key is too large.")
|
print(" Warning: The value of the pressed key is too large.")
|
||||||
return
|
return
|
||||||
|
|
||||||
for mob_listener in self.get_event_listeners_mobjects():
|
event_data = {"symbol": symbol, "modifiers": modifiers}
|
||||||
propagate_event = mob_listener.on_key_press(symbol, modifiers)
|
propagate_event = EVENT_DISPATCHER.dispatch(EventType.KeyPressEvent, **event_data)
|
||||||
if propagate_event is not None and propagate_event is False:
|
if propagate_event is not None and propagate_event is False:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ float sdf(){
|
||||||
float sgn = orientation * sign(v2);
|
float sgn = orientation * sign(v2);
|
||||||
float Fp = (p.x * p.x - p.y);
|
float Fp = (p.x * p.x - p.y);
|
||||||
if(sgn * Fp < 0){
|
if(sgn * Fp < 0){
|
||||||
return 0.0;
|
return 0;
|
||||||
}else{
|
}else{
|
||||||
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
|
return min_dist_to_curve(uv_coords, uv_b2, bezier_degree);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ void flatten_points(in vec3[3] points, out vec2[3] flat_points){
|
||||||
float angle_between_vectors(vec2 v1, vec2 v2){
|
float angle_between_vectors(vec2 v1, vec2 v2){
|
||||||
float v1_norm = length(v1);
|
float v1_norm = length(v1);
|
||||||
float v2_norm = length(v2);
|
float v2_norm = length(v2);
|
||||||
if(v1_norm == 0 || v2_norm == 0) return 0.0;
|
if(v1_norm == 0 || v2_norm == 0) return 0;
|
||||||
float dp = dot(v1, v2) / (v1_norm * v2_norm);
|
float dp = dot(v1, v2) / (v1_norm * v2_norm);
|
||||||
float angle = acos(clamp(dp, -1.0, 1.0));
|
float angle = acos(clamp(dp, -1.0, 1.0));
|
||||||
float sn = sign(cross2d(v1, v2));
|
float sn = sign(cross2d(v1, v2));
|
||||||
|
|
|
@ -349,29 +349,41 @@ def norm_squared(v):
|
||||||
|
|
||||||
# TODO, fails for polygons drawn over themselves
|
# TODO, fails for polygons drawn over themselves
|
||||||
def earclip_triangulation(verts, rings):
|
def earclip_triangulation(verts, rings):
|
||||||
|
"""
|
||||||
|
Returns a list of indices giving a triangulation
|
||||||
|
of a polygon, potentially with holes
|
||||||
|
|
||||||
|
- verts is an NxM numpy array of points with M > 2
|
||||||
|
|
||||||
|
- rings is a list of indices indicating where
|
||||||
|
the ends of new paths are
|
||||||
|
"""
|
||||||
n = len(verts)
|
n = len(verts)
|
||||||
# Establish where loop indices should be connected
|
# Establish where loop indices should be connected
|
||||||
loop_connections = dict()
|
loop_connections = dict()
|
||||||
for e0, e1 in zip(rings, rings[1:]):
|
# for e0, e1 in zip(rings, rings[1:]):
|
||||||
temp_i = e0
|
e0 = rings[0]
|
||||||
# Find closet point in the first ring (j) to
|
for e1 in rings[1:]:
|
||||||
# the first index of this ring (i)
|
# Find closet pair of points with the first
|
||||||
norms = np.array([
|
# coming from the current ring, and the second
|
||||||
[j, norm_squared(verts[temp_i] - verts[j])]
|
# coming from the next ring
|
||||||
for j in range(0, rings[0])
|
index_pairs = [
|
||||||
if j not in loop_connections
|
(i, j)
|
||||||
])
|
for i in range(0, e0)
|
||||||
j = int(norms[norms[:, 1].argmin()][0])
|
for j in range(e0, e1)
|
||||||
# Find i closest to this j
|
|
||||||
norms = np.array([
|
|
||||||
[i, norm_squared(verts[i] - verts[j])]
|
|
||||||
for i in range(e0, e1)
|
|
||||||
if i not in loop_connections
|
if i not in loop_connections
|
||||||
])
|
if j not in loop_connections
|
||||||
i = int(norms[norms[:, 1].argmin()][0])
|
]
|
||||||
|
i, j = index_pairs[np.argmin([
|
||||||
|
norm_squared(verts[i] - verts[j])
|
||||||
|
for i, j in index_pairs
|
||||||
|
])]
|
||||||
|
|
||||||
|
# Connect the polygon at these points so that
|
||||||
|
# it's treated as a single highly-convex ring
|
||||||
loop_connections[i] = j
|
loop_connections[i] = j
|
||||||
loop_connections[j] = i
|
loop_connections[j] = i
|
||||||
|
e0 = e1
|
||||||
|
|
||||||
# Setup linked list
|
# Setup linked list
|
||||||
after = []
|
after = []
|
||||||
|
@ -397,87 +409,3 @@ def earclip_triangulation(verts, rings):
|
||||||
|
|
||||||
meta_indices = earcut(verts[indices, :2], [len(indices)])
|
meta_indices = earcut(verts[indices, :2], [len(indices)])
|
||||||
return [indices[mi] for mi in meta_indices]
|
return [indices[mi] for mi in meta_indices]
|
||||||
|
|
||||||
|
|
||||||
def old_earclip_triangulation(verts, rings, orientation):
|
|
||||||
n = len(verts)
|
|
||||||
assert(n in rings)
|
|
||||||
result = []
|
|
||||||
|
|
||||||
# Establish where loop indices should be connected
|
|
||||||
loop_connections = dict()
|
|
||||||
e0 = 0
|
|
||||||
for e1 in rings:
|
|
||||||
norms = np.array([
|
|
||||||
[i, j, get_norm(verts[i] - verts[j])]
|
|
||||||
for i in range(e0, e1)
|
|
||||||
for j in it.chain(range(0, e0), range(e1, n))
|
|
||||||
])
|
|
||||||
if len(norms) == 0:
|
|
||||||
continue
|
|
||||||
i, j = norms[np.argmin(norms[:, 2])][:2].astype(int)
|
|
||||||
loop_connections[i] = j
|
|
||||||
loop_connections[j] = i
|
|
||||||
e0 = e1
|
|
||||||
|
|
||||||
# Setup bidirectional linked list
|
|
||||||
before = []
|
|
||||||
after = []
|
|
||||||
e0 = 0
|
|
||||||
for e1 in rings:
|
|
||||||
after += [*range(e0 + 1, e1), e0]
|
|
||||||
before += [e1 - 1, *range(e0, e1 - 1)]
|
|
||||||
e0 = e1
|
|
||||||
|
|
||||||
# Initialize edge triangles
|
|
||||||
edge_tris = []
|
|
||||||
i = 0
|
|
||||||
starting = True
|
|
||||||
while (i != 0 or starting):
|
|
||||||
starting = False
|
|
||||||
if i in loop_connections:
|
|
||||||
j = loop_connections[i]
|
|
||||||
edge_tris.append([before[i], i, j])
|
|
||||||
edge_tris.append([i, j, after[j]])
|
|
||||||
i = after[j]
|
|
||||||
else:
|
|
||||||
edge_tris.append([before[i], i, after[i]])
|
|
||||||
i = after[i]
|
|
||||||
|
|
||||||
# Set up a test for whether or not three indices
|
|
||||||
# form an ear of the polygon, meaning a convex corner
|
|
||||||
# which doesn't contain any other vertices
|
|
||||||
indices = list(range(n))
|
|
||||||
|
|
||||||
def is_ear(*tri_indices):
|
|
||||||
tri = [verts[i] for i in tri_indices]
|
|
||||||
v1 = tri[1] - tri[0]
|
|
||||||
v2 = tri[2] - tri[1]
|
|
||||||
cross = v1[0] * v2[1] - v2[0] * v1[1]
|
|
||||||
if orientation * cross < 0:
|
|
||||||
return False
|
|
||||||
for j in indices:
|
|
||||||
if j in tri_indices:
|
|
||||||
continue
|
|
||||||
elif is_inside_triangle(verts[j], *tri):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Loop through and clip off all the ears
|
|
||||||
n_failures = 0
|
|
||||||
i = 0
|
|
||||||
while n_failures < len(edge_tris):
|
|
||||||
n = len(edge_tris)
|
|
||||||
edge_tri = edge_tris[i % n]
|
|
||||||
if is_ear(*edge_tri):
|
|
||||||
result.extend(edge_tri)
|
|
||||||
edge_tris[(i - 1) % n][2] = edge_tri[2]
|
|
||||||
edge_tris[(i + 1) % n][0] = edge_tri[0]
|
|
||||||
if edge_tri[1] in indices:
|
|
||||||
indices.remove(edge_tri[1])
|
|
||||||
edge_tris.remove(edge_tri)
|
|
||||||
n_failures = 0
|
|
||||||
else:
|
|
||||||
n_failures += 1
|
|
||||||
i += 1
|
|
||||||
return result
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue