paintListener implementation started
This commit is contained in:
parent
cdc2b263f4
commit
7ed6d0228c
@ -1,2 +1,7 @@
|
|||||||
# virtualPainter
|
# virtualPainter
|
||||||
|
|
||||||
|
Building:
|
||||||
|
|
||||||
|
cd "C:\Program Files\Blender Foundation\Blender 4.5"
|
||||||
|
|
||||||
|
.\blender --command extension build --source-dir C:\Users\MegaA\OneDrive\Documents\Blender\add-on_dev\virtualPainter --output-dir C:\Users\MegaA\OneDrive\Documents\Blender\add-on_dev\test_builds
|
150
paintListener.py
150
paintListener.py
@ -1,7 +1,153 @@
|
|||||||
import bpy
|
import bpy
|
||||||
|
import mathutils
|
||||||
|
import bpy_extras
|
||||||
|
from bpy_extras import view3d_utils
|
||||||
|
|
||||||
|
image_name = "vPainter_buffer"
|
||||||
|
image_width = 1024
|
||||||
|
image_height = 1024
|
||||||
|
|
||||||
|
class vPainterOperator(bpy.types.Operator):
|
||||||
|
"""Set brush color based on unmodified surface normal"""
|
||||||
|
bl_idname = "paint.virtual_painter"
|
||||||
|
bl_label = "Normal-Based Paint Color"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
# normal_color = bpy.context.scene.normal_color_global
|
||||||
|
normal_color = bpy.props.FloatVectorProperty(size=3, default=(1.0, 1.0, 1.0))
|
||||||
|
|
||||||
|
_timer = None # Timer for modal operator
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
if context.area.type == 'VIEW_3D':
|
||||||
|
|
||||||
|
# create image buffer
|
||||||
|
if (image_name in bpy.data.images):
|
||||||
|
img = bpy.data.images[image_name]
|
||||||
|
else:
|
||||||
|
img = bpy.data.images.new(name=image_name, width=image_width, height=image_height, alpha=True)
|
||||||
|
|
||||||
|
# set buffer active for painting
|
||||||
|
bpy.context.tool_settings.image_paint.canvas = img
|
||||||
|
|
||||||
|
# add brush callback
|
||||||
|
bpy.app.handlers.depsgraph_update_post.append(brushstroke_callback)
|
||||||
|
print("callback added")
|
||||||
|
|
||||||
|
# start timer
|
||||||
|
self._timer = context.window_manager.event_timer_add(0.1, window=context.window)
|
||||||
|
|
||||||
|
# start modal
|
||||||
|
context.window_manager.modal_handler_add(self)
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
else:
|
||||||
|
self.report({'WARNING'}, "Please run this operator in the 3D View.")
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
def modal(self, context, event):
|
||||||
|
# vPainter disabled?
|
||||||
|
if not context.scene.vPainter_settings.is_vPainter_enabled:
|
||||||
|
|
||||||
|
# remove image buffer
|
||||||
|
img = bpy.data.images.get(image_name)
|
||||||
|
bpy.data.images.remove(img)
|
||||||
|
|
||||||
|
# remove brush callback
|
||||||
|
if brushstroke_callback in bpy.app.handlers.depsgraph_update_post:
|
||||||
|
print("callback removed")
|
||||||
|
bpy.app.handlers.depsgraph_update_post.remove(brushstroke_callback)
|
||||||
|
|
||||||
|
# stop timer
|
||||||
|
context.window_manager.event_timer_remove(self._timer)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
if context.area:
|
||||||
|
context.area.tag_redraw()
|
||||||
|
|
||||||
|
# Stroke starts?
|
||||||
|
if event.type == 'LEFTMOUSE' and event.value == 'PRESS' and context.area and context.area.type == 'VIEW_3D':
|
||||||
|
print("painting begon")
|
||||||
|
self.normal_color = self.get_normal_color(context, event)
|
||||||
|
context.scene.normal_color_global = self.normal_color
|
||||||
|
print("color retrieved")
|
||||||
|
|
||||||
|
return {'PASS_THROUGH'}
|
||||||
|
|
||||||
|
def get_normal_color(self, context, event):
|
||||||
|
"""Compute the normal at the painting location and convert it to a color."""
|
||||||
|
rv3d = context.space_data.region_3d
|
||||||
|
|
||||||
|
# Find the main 3D region ('WINDOW') in the 3D Viewport
|
||||||
|
region = next((r for r in context.area.regions if r.type == 'WINDOW'), None)
|
||||||
|
|
||||||
|
# Get mouse position in 3D space
|
||||||
|
# mouse_coords = (event.mouse_region_x, event.mouse_region_y)
|
||||||
|
# Adjust mouse coordinates to match the main region
|
||||||
|
mouse_coords = (
|
||||||
|
event.mouse_x - region.x,
|
||||||
|
event.mouse_y - region.y
|
||||||
|
)
|
||||||
|
view_vector = bpy_extras.view3d_utils.region_2d_to_vector_3d(region, rv3d, mouse_coords)
|
||||||
|
ray_origin = bpy_extras.view3d_utils.region_2d_to_origin_3d(region, rv3d, mouse_coords)
|
||||||
|
|
||||||
|
# print(mouse_coords)
|
||||||
|
# print(view_vector)
|
||||||
|
# print(ray_origin)
|
||||||
|
|
||||||
|
# Perform a ray cast to find the hit location
|
||||||
|
# obj = context.object
|
||||||
|
|
||||||
|
# Ensure there is a selected object
|
||||||
|
selected_objects = context.selected_objects
|
||||||
|
if not selected_objects:
|
||||||
|
self.report({'WARNING'}, "No selected object to perform ray cast on.")
|
||||||
|
return mathutils.Vector((1.0, 1.0, 1.0)) # Default white
|
||||||
|
|
||||||
|
# Use the first selected object
|
||||||
|
obj = selected_objects[0]
|
||||||
|
|
||||||
|
if obj.type == 'MESH':
|
||||||
|
matrix_world = obj.matrix_world
|
||||||
|
matrix_world_inv = matrix_world.inverted()
|
||||||
|
|
||||||
|
# Transform ray origin and direction to local space
|
||||||
|
ray_origin_local = matrix_world_inv @ ray_origin
|
||||||
|
ray_target_local = matrix_world_inv @ (ray_origin + view_vector)
|
||||||
|
ray_direction_local = (ray_target_local - ray_origin_local).normalized()
|
||||||
|
|
||||||
|
# Perform the ray cast in local space
|
||||||
|
result, location, normal, index = obj.ray_cast(
|
||||||
|
ray_origin_local, ray_origin_local + ray_direction_local * 1000
|
||||||
|
)
|
||||||
|
# print(f"Ray cast result: {result}, Location (local): {location_local}, Normal (local): {normal_local}")
|
||||||
|
print(normal)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
# Compute the normal in world space
|
||||||
|
normal_world = (matrix_world.to_3x3() @ normal).normalized()
|
||||||
|
print(normal_world)
|
||||||
|
# Map normal components [-1, 1] to [0, 1] for color
|
||||||
|
color = (normal + mathutils.Vector((1.0, 1.0, 1.0))) * 0.5
|
||||||
|
print(color)
|
||||||
|
return color
|
||||||
|
|
||||||
|
return mathutils.Vector((1.0, 1.0, 1.0)) # Default white if no hit
|
||||||
|
|
||||||
|
def brushstroke_callback(scene, depsgraph):
|
||||||
|
|
||||||
|
active_object = bpy.context.active_object
|
||||||
|
tool_settings = bpy.context.tool_settings
|
||||||
|
|
||||||
|
# Ensure callback is valid
|
||||||
|
if not active_object or not tool_settings:
|
||||||
|
return
|
||||||
|
elif active_object.mode == 'TEXTURE_PAINT':
|
||||||
|
if tool_settings.image_paint:
|
||||||
|
print("painting ended")
|
||||||
|
#apply_color()
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
pass
|
bpy.utils.register_class(vPainterOperator)
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
pass
|
bpy.utils.unregister_class(vPainterOperator)
|
Loading…
Reference in New Issue
Block a user