IsWhat would be great was if there were some way to balancemake sure that the UI between these? I'd like to be ablePanel got to usehandle the panel without having to exitevents first and only passed them onto my operator firstif they weren't used. Maybe there's some way to make the Panel a 'child' of my Operator?
Is there some way to balance the UI between these? I'd like to be able to use the panel without having to exit my operator first.
What would be great was if there were some way to make sure that the Panel got to handle the events first and only passed them onto my operator if they weren't used. Maybe there's some way to make the Panel a 'child' of my Operator?
In the below program, if you start the operator, you cannot change the panel controlcontrols until you press enter to exit itthe operator:
import bpy
class NormalToolSettings(bpy.types.PropertyGroup):
normal : bpy.props.FloatVectorProperty(
name="Normal",
description="Direction of normal in Fixed mode",
default = (1, 0, 0),
subtype="DIRECTION"
)
#-------------------------------
class ModalDrawOperator(bpy.types.Operator):
"""Adjust normals"""
bl_idname = "kitfox.normal_tool"
bl_label = "Normal Tool Kitfox"
dragging = False
def mouse_move(self, context, event):
mouse_pos = (event.mouse_region_x, event.mouse_region_y)
ctx = bpy.context
if self.dragging:
print("mouse drag to " + str(event.mouse_region_x) + ", " + str(event.mouse_region_ymouse_pos))
def mouse_down(self, context, event):
if event.value == "PRESS":
self.dragging = True
elif event.value == "RELEASE":
self.dragging = False
def modal(self, context, event):
context.area.tag_redraw()
if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
# allow navigation
return {'PASS_THROUGH'}
elif event.type == 'MOUSEMOVE':
self.mouse_move(context, event)
if self.dragging:
return {'RUNNING_MODAL'}
else:
return {'PASS_THROUGH'}
elif event.type == 'LEFTMOUSE':
self.mouse_down(context, event)
# return {'PASS_THROUGH'}
return {'RUNNING_MODAL'}
elif event.type in {'RET'}:
return {'FINISHED'}
elif event.type in {'PAGE_UP', 'RIGHT_BRACKET'}:
if event.value == "PRESS":
brush_radius = context.scene.my_tool.radius
brush_radius = brush_radius + .1
context.scene.my_tool.radius = brush_radius
return {'RUNNING_MODAL'}
elif event.type in {'PAGE_DOWN', 'LEFT_BRACKET'}:
if event.value == "PRESS":
brush_radius = context.scene.my_tool.radius
brush_radius = max(brush_radius - .1, .1)
context.scene.my_tool.radius = brush_radius
return {'RUNNING_MODAL'}
elif event.type in {'RIGHTMOUSE', 'ESC'}:
return {'CANCELLED'}
return {'PASS_THROUGH'}
def invoke(self, context, event):
if context.area.type == 'VIEW_3D':
# the arguments we pass the the callback
args = (self, context)
# Add the region OpenGL drawing callback
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
self._context = context
context.area.tag_redraw()
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
else:
self.report({'WARNING'}, "View3D not found, cannot run operator")
return {'CANCELLED'}
#-------------------------------
class NormalToolPropsPanel(bpy.types.Panel):
"""Properties Panel for the Normal Tool on tool shelf"""
bl_label = "Normal Tool"
bl_idname = "OBJECT_PT_normal_tool_props"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Kitfox2"
def draw(self, context):
layout = self.layout
scene = context.scene
settings = scene.my_tool
col = layout.column();
col.operator("kitfox.normal_tool", text="Start Normal Tool")
col.prop(settings, "normal")
def register():
bpy.utils.register_class(NormalToolSettings)
bpy.utils.register_class(ModalDrawOperator)
bpy.utils.register_class(NormalToolPropsPanel)
bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=NormalToolSettings)
def unregister():
bpy.utils.unregister_class(NormalToolSettings)
bpy.utils.unregister_class(ModalDrawOperator)
bpy.utils.unregister_class(NormalToolPropsPanel)
del bpy.types.Scene.my_tool
if __name__ == "__main__":
register()
```
In the below program, if you start the operator, you cannot change the panel control until you press enter to exit it:
import bpy
class NormalToolSettings(bpy.types.PropertyGroup):
normal : bpy.props.FloatVectorProperty(
name="Normal",
description="Direction of normal in Fixed mode",
default = (1, 0, 0),
subtype="DIRECTION"
)
#-------------------------------
class ModalDrawOperator(bpy.types.Operator):
"""Adjust normals"""
bl_idname = "kitfox.normal_tool"
bl_label = "Normal Tool Kitfox"
dragging = False
def mouse_move(self, context, event):
mouse_pos = (event.mouse_region_x, event.mouse_region_y)
ctx = bpy.context
if self.dragging:
print("mouse drag to " + str(event.mouse_region_x) + ", " + str(event.mouse_region_y))
def mouse_down(self, context, event):
if event.value == "PRESS":
self.dragging = True
elif event.value == "RELEASE":
self.dragging = False
def modal(self, context, event):
context.area.tag_redraw()
if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
# allow navigation
return {'PASS_THROUGH'}
elif event.type == 'MOUSEMOVE':
self.mouse_move(context, event)
if self.dragging:
return {'RUNNING_MODAL'}
else:
return {'PASS_THROUGH'}
elif event.type == 'LEFTMOUSE':
self.mouse_down(context, event)
# return {'PASS_THROUGH'}
return {'RUNNING_MODAL'}
elif event.type in {'RET'}:
return {'FINISHED'}
elif event.type in {'PAGE_UP', 'RIGHT_BRACKET'}:
if event.value == "PRESS":
brush_radius = context.scene.my_tool.radius
brush_radius = brush_radius + .1
context.scene.my_tool.radius = brush_radius
return {'RUNNING_MODAL'}
elif event.type in {'PAGE_DOWN', 'LEFT_BRACKET'}:
if event.value == "PRESS":
brush_radius = context.scene.my_tool.radius
brush_radius = max(brush_radius - .1, .1)
context.scene.my_tool.radius = brush_radius
return {'RUNNING_MODAL'}
elif event.type in {'RIGHTMOUSE', 'ESC'}:
return {'CANCELLED'}
return {'PASS_THROUGH'}
def invoke(self, context, event):
if context.area.type == 'VIEW_3D':
# the arguments we pass the the callback
args = (self, context)
# Add the region OpenGL drawing callback
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
self._context = context
context.area.tag_redraw()
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
else:
self.report({'WARNING'}, "View3D not found, cannot run operator")
return {'CANCELLED'}
#-------------------------------
class NormalToolPropsPanel(bpy.types.Panel):
"""Properties Panel for the Normal Tool on tool shelf"""
bl_label = "Normal Tool"
bl_idname = "OBJECT_PT_normal_tool_props"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Kitfox2"
def draw(self, context):
layout = self.layout
scene = context.scene
settings = scene.my_tool
col = layout.column();
col.operator("kitfox.normal_tool", text="Start Normal Tool")
col.prop(settings, "normal")
def register():
bpy.utils.register_class(NormalToolSettings)
bpy.utils.register_class(ModalDrawOperator)
bpy.utils.register_class(NormalToolPropsPanel)
bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=NormalToolSettings)
def unregister():
bpy.utils.unregister_class(NormalToolSettings)
bpy.utils.unregister_class(ModalDrawOperator)
bpy.utils.unregister_class(NormalToolPropsPanel)
del bpy.types.Scene.my_tool
if __name__ == "__main__":
register()
```
In the below program, if you start the operator, you cannot change the panel controls until you press enter to exit the operator:
import bpy
class NormalToolSettings(bpy.types.PropertyGroup):
normal : bpy.props.FloatVectorProperty(
name="Normal",
description="Direction of normal in Fixed mode",
default = (1, 0, 0),
subtype="DIRECTION"
)
#-------------------------------
class ModalDrawOperator(bpy.types.Operator):
"""Adjust normals"""
bl_idname = "kitfox.normal_tool"
bl_label = "Normal Tool Kitfox"
dragging = False
def mouse_move(self, context, event):
mouse_pos = (event.mouse_region_x, event.mouse_region_y)
ctx = bpy.context
if self.dragging:
print("mouse drag to " + str(mouse_pos))
def mouse_down(self, context, event):
if event.value == "PRESS":
self.dragging = True
elif event.value == "RELEASE":
self.dragging = False
def modal(self, context, event):
context.area.tag_redraw()
if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
# allow navigation
return {'PASS_THROUGH'}
elif event.type == 'MOUSEMOVE':
self.mouse_move(context, event)
if self.dragging:
return {'RUNNING_MODAL'}
else:
return {'PASS_THROUGH'}
elif event.type == 'LEFTMOUSE':
self.mouse_down(context, event)
# return {'PASS_THROUGH'}
return {'RUNNING_MODAL'}
elif event.type in {'RET'}:
return {'FINISHED'}
elif event.type in {'PAGE_UP', 'RIGHT_BRACKET'}:
if event.value == "PRESS":
brush_radius = context.scene.my_tool.radius
brush_radius = brush_radius + .1
context.scene.my_tool.radius = brush_radius
return {'RUNNING_MODAL'}
elif event.type in {'PAGE_DOWN', 'LEFT_BRACKET'}:
if event.value == "PRESS":
brush_radius = context.scene.my_tool.radius
brush_radius = max(brush_radius - .1, .1)
context.scene.my_tool.radius = brush_radius
return {'RUNNING_MODAL'}
elif event.type in {'RIGHTMOUSE', 'ESC'}:
return {'CANCELLED'}
return {'PASS_THROUGH'}
def invoke(self, context, event):
if context.area.type == 'VIEW_3D':
# the arguments we pass the the callback
args = (self, context)
# Add the region OpenGL drawing callback
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
self._context = context
context.area.tag_redraw()
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
else:
self.report({'WARNING'}, "View3D not found, cannot run operator")
return {'CANCELLED'}
#-------------------------------
class NormalToolPropsPanel(bpy.types.Panel):
"""Properties Panel for the Normal Tool on tool shelf"""
bl_label = "Normal Tool"
bl_idname = "OBJECT_PT_normal_tool_props"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Kitfox2"
def draw(self, context):
layout = self.layout
scene = context.scene
settings = scene.my_tool
col = layout.column();
col.operator("kitfox.normal_tool", text="Start Normal Tool")
col.prop(settings, "normal")
def register():
bpy.utils.register_class(NormalToolSettings)
bpy.utils.register_class(ModalDrawOperator)
bpy.utils.register_class(NormalToolPropsPanel)
bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=NormalToolSettings)
def unregister():
bpy.utils.unregister_class(NormalToolSettings)
bpy.utils.unregister_class(ModalDrawOperator)
bpy.utils.unregister_class(NormalToolPropsPanel)
del bpy.types.Scene.my_tool
if __name__ == "__main__":
register()
```
How to make a Panel accessible while modal operator running?
In my script, I've created an Operator, a PropertiesGroup which holds the properties of my operator and a Panel which lets the user change the properties and start the operator running.
My operator uses mouse input, so it returns RUNNING_MODAL
for the LEFTMOUSE
event (and many other events too). Unfortunately, this prevents the user from using the Panel while the operator is running. If I return PASS_THROUGH
from the left mouse event instead, I can use the panel but it has the side effect that my operator never gets the RELEASE
event should the user click within the panel. This has the effect of leaving my operator in a state where it thinks the mouse is permanently held down.
Is there some way to balance the UI between these? I'd like to be able to use the panel without having to exit my operator first.
In the below program, if you start the operator, you cannot change the panel control until you press enter to exit it:
import bpy
class NormalToolSettings(bpy.types.PropertyGroup):
normal : bpy.props.FloatVectorProperty(
name="Normal",
description="Direction of normal in Fixed mode",
default = (1, 0, 0),
subtype="DIRECTION"
)
#-------------------------------
class ModalDrawOperator(bpy.types.Operator):
"""Adjust normals"""
bl_idname = "kitfox.normal_tool"
bl_label = "Normal Tool Kitfox"
dragging = False
def mouse_move(self, context, event):
mouse_pos = (event.mouse_region_x, event.mouse_region_y)
ctx = bpy.context
if self.dragging:
print("mouse drag to " + str(event.mouse_region_x) + ", " + str(event.mouse_region_y))
def mouse_down(self, context, event):
if event.value == "PRESS":
self.dragging = True
elif event.value == "RELEASE":
self.dragging = False
def modal(self, context, event):
context.area.tag_redraw()
if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'}:
# allow navigation
return {'PASS_THROUGH'}
elif event.type == 'MOUSEMOVE':
self.mouse_move(context, event)
if self.dragging:
return {'RUNNING_MODAL'}
else:
return {'PASS_THROUGH'}
elif event.type == 'LEFTMOUSE':
self.mouse_down(context, event)
# return {'PASS_THROUGH'}
return {'RUNNING_MODAL'}
elif event.type in {'RET'}:
return {'FINISHED'}
elif event.type in {'PAGE_UP', 'RIGHT_BRACKET'}:
if event.value == "PRESS":
brush_radius = context.scene.my_tool.radius
brush_radius = brush_radius + .1
context.scene.my_tool.radius = brush_radius
return {'RUNNING_MODAL'}
elif event.type in {'PAGE_DOWN', 'LEFT_BRACKET'}:
if event.value == "PRESS":
brush_radius = context.scene.my_tool.radius
brush_radius = max(brush_radius - .1, .1)
context.scene.my_tool.radius = brush_radius
return {'RUNNING_MODAL'}
elif event.type in {'RIGHTMOUSE', 'ESC'}:
return {'CANCELLED'}
return {'PASS_THROUGH'}
def invoke(self, context, event):
if context.area.type == 'VIEW_3D':
# the arguments we pass the the callback
args = (self, context)
# Add the region OpenGL drawing callback
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
self._context = context
context.area.tag_redraw()
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
else:
self.report({'WARNING'}, "View3D not found, cannot run operator")
return {'CANCELLED'}
#-------------------------------
class NormalToolPropsPanel(bpy.types.Panel):
"""Properties Panel for the Normal Tool on tool shelf"""
bl_label = "Normal Tool"
bl_idname = "OBJECT_PT_normal_tool_props"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Kitfox2"
def draw(self, context):
layout = self.layout
scene = context.scene
settings = scene.my_tool
col = layout.column();
col.operator("kitfox.normal_tool", text="Start Normal Tool")
col.prop(settings, "normal")
def register():
bpy.utils.register_class(NormalToolSettings)
bpy.utils.register_class(ModalDrawOperator)
bpy.utils.register_class(NormalToolPropsPanel)
bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=NormalToolSettings)
def unregister():
bpy.utils.unregister_class(NormalToolSettings)
bpy.utils.unregister_class(ModalDrawOperator)
bpy.utils.unregister_class(NormalToolPropsPanel)
del bpy.types.Scene.my_tool
if __name__ == "__main__":
register()
```