Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dpg.configure_item() Ignores Translation in Transform Matrix on Draw Nodes #2382

Open
jacobnrohan opened this issue Aug 22, 2024 · 3 comments
Labels
state: pending not addressed yet type: bug bug

Comments

@jacobnrohan
Copy link

Version of Dear PyGui

Version: 1.11.1
Operating System: Debian Bookworm (also tested on Windows 11)

My Issue

For drawings on a draw node with a transform, dpg.configure_item does not work properly. In these cases, it seems to ignore the 4th column (translate values) of the 4x4 transform matrix.

Deleting and re-creating the drawing produces the expected behavior. When dpg.configure_item() is used, drawings are effected by the scale of the transform matrix, but are not property translated.

To Reproduce

Steps to reproduce the behavior: (see code)

  1. Create a draw node and apply a transformation and drawing
  2. Update that drawing using dpg.configure_item() to change its position
  3. notice how the new position is no longer effected by the transformation's translation, but is still effected by scale

Expected behavior

When dpg.configure_item() is used to change a drawing's position, it should remain fully effected by the draw node transform.

Deleting then re-drawing the item produces the expected behavior but is not a practical alternative.

Screenshots/Video

configure_item

Standalone, minimal, complete and verifiable example

import dearpygui.dearpygui as dpg
from dearpygui._dearpygui import mvMat4

class application:

    def __init__(self, ):

        # init variables for origin/dragging
        self.scale = 0
        self.origin_pre = [200.0, 200.0]
        self.dragfrom = [0.0, 0.0]
        self.dragto = [0.0, 0.0]
        self.__any_hovered__ = False

        # init dpg
        dpg.create_context()
        dpg.create_viewport(title='issue with dpg.configure_item()',vsync=False)
        dpg.setup_dearpygui()

        # init a viewport and draw node
        self.id_background = dpg.add_viewport_drawlist(front=False, tag="background")
        dpg.add_draw_node(tag="draw_node", parent="background")
        dpg.apply_transform("draw_node", self.transform)

        # init left-click-drag to move drawings
        self.dragging_mouse = False
        with dpg.handler_registry():
            dpg.add_mouse_click_handler(callback=self.mouse_click_handler)
            dpg.add_mouse_move_handler(callback=self.mouse_move_handler)
            dpg.add_mouse_release_handler(callback=self.mouse_release_handler)
            dpg.add_mouse_wheel_handler(callback=self.mouse_wheel_handler)

        # add basic drawings
        dpg.draw_circle(center=[0, 0], radius=5, fill=[255, 0, 0], color=[0, 0, 0, 0], parent="draw_node") # origin
        dpg.draw_text(pos=[0, 0], text='(0, 0)', color=[255, 0, 0], size=32, parent="draw_node")
        dpg.draw_circle(center=[200, 0], radius=5, fill=[255, 0, 0], color=[0, 0, 0, 0], parent="draw_node") # origin
        dpg.draw_text(pos=[200, 0], text='(200, 0)', color=[255, 0, 0], size=32, parent="draw_node")
        self.id = dpg.draw_rectangle(pmin=[0, 0], pmax=[100, 100], fill=[0, 0, 255, 128], parent="draw_node")
        dpg.draw_rectangle(pmin=[200, 0], pmax=[300, 100], fill=[0, 0, 0], parent="draw_node")

        # add controls to update drawing
        with dpg.window(label="move object",no_background=False):
            dpg.add_text("left-click and drag the background to update the draw_node transform.")
            dpg.add_text("scroll to zoom in and out.")
            self.button0 = dpg.add_button(label="reset", callback=self.reset)
            self.button1 = dpg.add_button(label="dpg.configure_item", callback=self.move_rectangle)
            self.button2 = dpg.add_button(label="dpg.delete_item and dpg.draw_rectangle", callback=self.replace_rectangle)
        with dpg.tooltip(self.button1, delay=0.10, hide_on_activity=True):
            dpg.add_text('actual behavior: demo shows that dpg.configure_item does not\n'\
                         'properly update drawings on draw nodes w/ transformations.')
        with dpg.tooltip(self.button2, delay=0.10, hide_on_activity=True):
            dpg.add_text('expected behavior')

        # main loop
        dpg.show_viewport()
        dpg.start_dearpygui()
        dpg.destroy_context()

    @property
    def any_hovered(self):
        # https://github.com/hoffstadt/DearPyGui/issues/2281
        return any([s["hovered"] for s in [dpg.get_item_state(w) for w in dpg.get_windows()] if ("hovered" in s.keys())])

    def mouse_click_handler(self, sender, app_data, user_data):
        if app_data == 0: # left click
            if not self.__any_hovered__ and not self.dragging_mouse:
                self.dragging_mouse = True
                xy = dpg.get_mouse_pos(local=False)
                # print('mouse_click_handler xy:', xy)
                self.dragfrom = xy
                self.dragto = xy

    def mouse_move_handler(self, sender, app_data, user_data):
        self.__any_hovered__ = self.any_hovered
        # print("any_hovered?:", self.any_hovered)
        if self.dragging_mouse:
            xy = dpg.get_mouse_pos(local=False)
            # print('mouse_move_handler xy:', xy)
            self.dragto = xy
            dpg.apply_transform("draw_node", self.transform)

    def mouse_release_handler(self, sender, app_data, user_data):
        if app_data == 0: # left click
            if not self.__any_hovered__ and self.dragging_mouse:
                self.dragging_mouse = False
                self.origin_pre = self.origin
                self.dragfrom = [0.0, 0.0]
                self.dragto = [0.0, 0.0]
                # print('mouse_release_handler')

    def mouse_wheel_handler(self, sender, app_data, user_data):
        if not self.__any_hovered__:
            if app_data > 0:
                self.scale += 1
            elif app_data < 0:
                self.scale -= 1
            dpg.apply_transform("draw_node", self.transform)        

    @property
    def origin(self):
        return [o + t - f for o, f, t in zip(self.origin_pre, self.dragfrom, self.dragto)]
    
    @property
    def transform(self):
        s = 2 ** (self.scale / 8)
        x, y = self.origin
        return mvMat4(
              s, 0.0, 0.0, x,
            0.0,  -s, 0.0, y,
            0.0, 0.0, 1.0, 0.0,
            0.0, 0.0, 0.0, 1.0)

    def move_rectangle(self, sender, app_data, user_data):
        dpg.configure_item(self.id, pmin=[200, 0], pmax=[300, 100], parent="draw_node") # TODO: NEEDS FIX

    def replace_rectangle(self, sender, app_data, user_data):
        dpg.delete_item(self.id)
        self.id = dpg.draw_rectangle(pmin=[200, 0], pmax=[300, 100], fill=[0, 0, 255, 128], parent="draw_node")

    def reset(self, sender, app_data, user_data):
        dpg.delete_item(self.id)
        self.id = dpg.draw_rectangle(pmin=[0, 0], pmax=[100, 100], fill=[0, 0, 255, 128], parent="draw_node")

if __name__ == "__main__":
    app = application()
@jacobnrohan jacobnrohan added state: pending not addressed yet type: bug bug labels Aug 22, 2024
@v-ein
Copy link
Contributor

v-ein commented Aug 22, 2024

A quick tip: instead of specifying parent="draw_node" on every primitive, you can do it this way:

# init a viewport and draw node
with dpg.viewport_drawlist(front=False, tag="background") as self.id_background:
    with dpg.draw_node(tag="draw_node", parent="background"):
        dpg.apply_transform("draw_node", self.transform)

        dpg.draw_circle(center=[0, 0], radius=5, fill=[255, 0, 0], color=[0, 0, 0, 0]) # origin
        dpg.draw_text(pos=[0, 0], text='(0, 0)', color=[255, 0, 0], size=32)
        dpg.draw_circle(center=[200, 0], radius=5, fill=[255, 0, 0], color=[0, 0, 0, 0]) # origin
        dpg.draw_text(pos=[200, 0], text='(200, 0)', color=[255, 0, 0], size=32)
        self.id = dpg.draw_rectangle(pmin=[0, 0], pmax=[100, 100], fill=[0, 0, 255, 128])
        dpg.draw_rectangle(pmin=[200, 0], pmax=[300, 100], fill=[0, 0, 0])

It's less typing and easier to read and edit.

@jacobnrohan
Copy link
Author

Thanks for the tip @v-ein! I've updated the code accordingly.

The issue persists. I've notice that this issue persist when using dpg.create_scale_matrix() and dpg.create_translation_matrix() instead of mvMat4.

updated code
import dearpygui.dearpygui as dpg
from dearpygui._dearpygui import mvMat4

# posted to https://github.com/hoffstadt/DearPyGui/issues/2382

class application:

    def __init__(self, ):

        # init variables for origin/dragging
        self.scale = 0
        self.origin_pre = [200.0, 200.0]
        self.dragfrom = [0.0, 0.0]
        self.dragto = [0.0, 0.0]
        self.__any_hovered__ = False

        # init dpg
        dpg.create_context()
        dpg.create_viewport(title='issue with dpg.configure_item()',vsync=False)
        dpg.setup_dearpygui()

        # init a viewport and draw node
        with dpg.viewport_drawlist(front=False, tag="background") as id_background:
            self.id_background = id_background
            with dpg.draw_node(tag="draw_node", parent="background"):
                dpg.apply_transform("draw_node", self.transform)

                dpg.draw_circle(center=[0, 0], radius=5, fill=[255, 0, 0], color=[0, 0, 0, 0]) # origin
                dpg.draw_text(pos=[0, 0], text='(0, 0)', color=[255, 0, 0], size=32)
                dpg.draw_circle(center=[200, 0], radius=5, fill=[255, 0, 0], color=[0, 0, 0, 0]) # origin
                dpg.draw_text(pos=[200, 0], text='(200, 0)', color=[255, 0, 0], size=32)
                self.id = dpg.draw_rectangle(pmin=[0, 0], pmax=[100, 100], fill=[0, 0, 255, 128])
                dpg.draw_rectangle(pmin=[200, 0], pmax=[300, 100], fill=[0, 0, 0])

        # init left-click-drag to move drawings
        self.dragging_mouse = False
        with dpg.handler_registry():
            dpg.add_mouse_click_handler(callback=self.mouse_click_handler)
            dpg.add_mouse_move_handler(callback=self.mouse_move_handler)
            dpg.add_mouse_release_handler(callback=self.mouse_release_handler)
            dpg.add_mouse_wheel_handler(callback=self.mouse_wheel_handler)

        # add controls to update drawing
        with dpg.window(label="move object",no_background=False):
            dpg.add_text("left-click and drag the background to update the draw_node transform.")
            dpg.add_text("scroll to zoom in and out.")
            self.button0 = dpg.add_button(label="reset", callback=self.reset)
            self.button1 = dpg.add_button(label="dpg.configure_item", callback=self.move_rectangle)
            self.button2 = dpg.add_button(label="dpg.delete_item and dpg.draw_rectangle", callback=self.replace_rectangle)

        # with dpg.tooltip(self.button0, delay=0.10, hide_on_activity=True):
        #     dpg.add_text('reset the demo')
        with dpg.tooltip(self.button1, delay=0.10, hide_on_activity=True):
            dpg.add_text('actual behavior: demo shows that dpg.configure_item does not\n'\
                         'properly update drawings on draw nodes w/ transformations.')
        with dpg.tooltip(self.button2, delay=0.10, hide_on_activity=True):
            dpg.add_text('expected behavior')

        # main loop
        dpg.show_viewport()
        dpg.start_dearpygui()
        dpg.destroy_context()

    @property
    def any_hovered(self):
        # https://github.com/hoffstadt/DearPyGui/issues/2281
        return any([s["hovered"] for s in [dpg.get_item_state(w) for w in dpg.get_windows()] if ("hovered" in s.keys())])

    def mouse_click_handler(self, sender, app_data, user_data):
        if app_data == 0: # left click
            if not self.__any_hovered__ and not self.dragging_mouse:
                self.dragging_mouse = True
                xy = dpg.get_mouse_pos(local=False)
                # print('mouse_click_handler xy:', xy)
                self.dragfrom = xy
                self.dragto = xy

    def mouse_move_handler(self, sender, app_data, user_data):
        self.__any_hovered__ = self.any_hovered
        # print("any_hovered?:", self.any_hovered)
        if self.dragging_mouse:
            xy = dpg.get_mouse_pos(local=False)
            # print('mouse_move_handler xy:', xy)
            self.dragto = xy
            dpg.apply_transform("draw_node", self.transform)

    def mouse_release_handler(self, sender, app_data, user_data):
        if app_data == 0: # left click
            if not self.__any_hovered__ and self.dragging_mouse:
                self.dragging_mouse = False
                self.origin_pre = self.origin
                self.dragfrom = [0.0, 0.0]
                self.dragto = [0.0, 0.0]
                # print('mouse_release_handler')

    def mouse_wheel_handler(self, sender, app_data, user_data):
        if not self.__any_hovered__:
            if app_data > 0:
                self.scale += 1
            elif app_data < 0:
                self.scale -= 1
            dpg.apply_transform("draw_node", self.transform)        

    @property
    def origin(self):
        return [o + t - f for o, f, t in zip(self.origin_pre, self.dragfrom, self.dragto)]
    
    @property
    def transform(self):
        s = 2 ** (self.scale / 8)
        x, y = self.origin
        # return mvMat4(
        #       s, 0.0, 0.0, x,
        #     0.0,   s, 0.0, y,
        #     0.0, 0.0, 1.0, 0.0,
        #     0.0, 0.0, 0.0, 1.0)
        return dpg.create_scale_matrix(scales=[s, s, 1, 1]) * dpg.create_translation_matrix([x, y])

    def move_rectangle(self, sender, app_data, user_data):
        dpg.configure_item(self.id, pmin=[200, 0], pmax=[300, 100], parent="draw_node") # TODO: NEEDS FIX

    def replace_rectangle(self, sender, app_data, user_data):
        dpg.delete_item(self.id)
        self.id = dpg.draw_rectangle(pmin=[200, 0], pmax=[300, 100], fill=[0, 0, 255, 128], parent="draw_node")

    def reset(self, sender, app_data, user_data):
        dpg.delete_item(self.id)
        self.id = dpg.draw_rectangle(pmin=[0, 0], pmax=[100, 100], fill=[0, 0, 255, 128], parent="draw_node")

if __name__ == "__main__":
    app = application()

@v-ein
Copy link
Contributor

v-ein commented Aug 28, 2024

Yep, I was able to recreate it too. Not sure why it happens; needs some research.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
state: pending not addressed yet type: bug bug
Projects
None yet
Development

No branches or pull requests

2 participants