Skip to content

Commit 082cdbf

Browse files
authored
Merge Vulkan-Fix
- Initial support for rendering our overlay points and lines in Vulkan - Fixes #51 (mostly; we lose the ability to control width of dashed lines in multi-merge. Will keep looking in #52 ) - Incidentally fixes #39 (mostly; dashed lines still don't have anti-aliasing in multi-merge) - NOTE: We do not support the incomplete, experimental Vulkan backend in Blender 4.3 and 4.4. Proper Vulkan support begins with Blender 4.5.
2 parents ee80137 + 00620fb commit 082cdbf

File tree

3 files changed

+345
-253
lines changed

3 files changed

+345
-253
lines changed

mesh_merge_tool/__init__.py

Lines changed: 15 additions & 253 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"name": "Merge Tool",
2121
"description": "An interactive tool for merging vertices and edges.",
2222
"author": "Andreas Strømberg, Chris Kohl",
23-
"version": (1, 4, 0),
23+
"version": (1, 5, 0),
2424
"blender": (2, 93, 0),
2525
"location": "View3D > TOOLS > Merge Tool",
2626
"warning": "",
@@ -31,12 +31,17 @@
3131

3232

3333
import bpy
34-
import gpu
3534
import bmesh
3635
import os
3736
from mathutils import Vector
38-
from gpu_extras.presets import draw_circle_2d
39-
from gpu_extras.batch import batch_for_shader
37+
38+
from importlib import reload
39+
if 'shaders' in globals():
40+
reload(shaders)
41+
42+
from .shaders import draw_callback_3d, draw_callback_2d
43+
from .util import find_center, set_component
44+
4045
from bpy.props import (
4146
EnumProperty,
4247
StringProperty,
@@ -49,13 +54,6 @@
4954
icon_dir = os.path.join(os.path.dirname(__file__), "icons")
5055
t_cursor = 'PAINT_CROSS'
5156

52-
# Blender versions higher than 4.0 don't support 3D_UNIFORM_COLOR but versions below 3.4 require it
53-
if bpy.app.version[0] >= 4:
54-
shader_type = 'UNIFORM_COLOR'
55-
elif bpy.app.version[0] == 3 and bpy.app.version[1] >= 4:
56-
shader_type = 'UNIFORM_COLOR'
57-
else:
58-
shader_type = '3D_UNIFORM_COLOR'
5957

6058
classes = []
6159

@@ -104,7 +102,7 @@ class MergeToolPreferences(bpy.types.AddonPreferences):
104102
description="Size of the circle cursor (VISUAL ONLY)",
105103
default=12.0,
106104
min=6.0,
107-
max=100,
105+
max=100.0,
108106
step=1,
109107
precision=2)
110108

@@ -158,247 +156,6 @@ def draw(self, context):
158156
classes.append(MergeToolPreferences)
159157

160158

161-
vertex_shader = '''
162-
uniform mat4 u_ViewProjectionMatrix;
163-
164-
in vec3 position;
165-
in float arcLength;
166-
167-
out float v_ArcLength;
168-
169-
void main()
170-
{
171-
v_ArcLength = arcLength;
172-
gl_Position = u_ViewProjectionMatrix * vec4(position, 1.0f);
173-
}
174-
'''
175-
176-
fragment_shader = '''
177-
uniform float u_Scale;
178-
uniform vec4 u_Color;
179-
180-
in float v_ArcLength;
181-
out vec4 FragColor;
182-
183-
void main()
184-
{
185-
if (step(sin(v_ArcLength * u_Scale), 0.5) == 1) discard;
186-
FragColor = vec4(u_Color);
187-
}
188-
'''
189-
190-
191-
class DrawPoint():
192-
def __init__(self, *args, **kwargs):
193-
super().__init__(*args, **kwargs)
194-
self.shader = None
195-
self.coords = None
196-
self.color = None
197-
198-
def draw(self):
199-
batch = batch_for_shader(self.shader, 'POINTS', {"pos": self.coords})
200-
self.shader.bind()
201-
self.shader.uniform_float("color", self.color)
202-
batch.draw(self.shader)
203-
204-
def add(self, shader, coords, color):
205-
self.shader = shader
206-
if isinstance(coords, Vector):
207-
self.coords = [coords]
208-
else:
209-
self.coords = coords
210-
self.color = color
211-
self.draw()
212-
213-
214-
class DrawLine():
215-
def __init__(self, *args, **kwargs):
216-
super().__init__(*args, **kwargs)
217-
self.shader = None
218-
self.coords = None
219-
self.color = None
220-
221-
def draw(self):
222-
batch = batch_for_shader(self.shader, 'LINES', {"pos": self.coords})
223-
self.shader.bind()
224-
self.shader.uniform_float("color", self.color)
225-
batch.draw(self.shader)
226-
227-
def add(self, shader, coords, color):
228-
self.shader = shader
229-
self.coords = coords
230-
self.color = color
231-
self.draw()
232-
233-
234-
class DrawLineDashed():
235-
def __init__(self, *args, **kwargs):
236-
super().__init__(*args, **kwargs)
237-
self.shader = None
238-
self.coords = None
239-
self.color = None
240-
self.arc_lengths = None
241-
242-
def draw(self):
243-
batch = batch_for_shader(self.shader, 'LINES', {"position": self.coords, "arcLength": self.arc_lengths})
244-
self.shader.bind()
245-
matrix = bpy.context.region_data.perspective_matrix
246-
self.shader.uniform_float("u_ViewProjectionMatrix", matrix)
247-
self.shader.uniform_float("u_Scale", 50)
248-
self.shader.uniform_float("u_Color", self.color)
249-
batch.draw(self.shader)
250-
251-
def add(self, shader, coords, color):
252-
self.shader = shader
253-
self.coords = coords
254-
self.color = color
255-
self.arc_lengths = [0]
256-
for a, b in zip(self.coords[:-1], self.coords[1:]):
257-
self.arc_lengths.append(self.arc_lengths[-1] + (a - b).length)
258-
self.draw()
259-
260-
261-
def draw_callback_3d(self, context):
262-
if self.started and self.start_comp is not None:
263-
gpu.state.point_size_set(self.prefs.point_size)
264-
shader = gpu.shader.from_builtin(shader_type)
265-
if self.end_comp is not None and self.end_comp != self.start_comp:
266-
gpu.state.line_width_set(self.prefs.line_width)
267-
if not self.multi_merge:
268-
line_coords = [self.start_comp_transformed, self.end_comp_transformed]
269-
else:
270-
line_coords = []
271-
vert_coords = []
272-
if self.merge_location == 'CENTER':
273-
vert_list = [v.co for v in self.start_sel]
274-
if self.end_comp not in self.start_sel:
275-
vert_list.append(self.end_comp.co)
276-
for v in self.start_sel:
277-
line_coords.append(self.world_matrix @ v.co)
278-
line_coords.append(self.world_matrix @ find_center(vert_list))
279-
vert_coords.append(self.world_matrix @ v.co)
280-
line_coords.append(self.end_comp_transformed)
281-
line_coords.append(self.world_matrix @ find_center(vert_list))
282-
elif self.merge_location == 'LAST':
283-
for v in self.start_sel:
284-
line_coords.append(self.world_matrix @ v.co)
285-
line_coords.append(self.end_comp_transformed)
286-
vert_coords.append(self.world_matrix @ v.co)
287-
elif self.merge_location == 'FIRST':
288-
for v in self.start_sel:
289-
line_coords.append(self.world_matrix @ v.co)
290-
line_coords.append(self.start_comp_transformed)
291-
vert_coords.append(self.world_matrix @ v.co)
292-
line_coords.append(self.end_comp_transformed)
293-
line_coords.append(self.start_comp_transformed)
294-
295-
# Line that connects the start and end position (draw first so it's beneath the vertices)
296-
if not self.multi_merge:
297-
tool_line = DrawLine()
298-
tool_line.add(shader, line_coords, self.prefs.line_color)
299-
else:
300-
shader_dashed = gpu.types.GPUShader(vertex_shader, fragment_shader)
301-
tool_line = DrawLineDashed()
302-
tool_line.add(shader_dashed, line_coords, self.prefs.line_color)
303-
304-
# Ending edge
305-
if self.sel_mode == 'EDGE':
306-
gpu.state.line_width_set(self.prefs.edge_width)
307-
e1v = [self.world_matrix @ v.co for v in self.end_comp.verts]
308-
309-
end_edge = DrawLine()
310-
if self.merge_location in ('FIRST', 'CENTER'):
311-
end_edge.add(shader, e1v, self.prefs.start_color)
312-
else:
313-
end_edge.add(shader, e1v, self.prefs.end_color)
314-
315-
# Ending point
316-
end_point = DrawPoint()
317-
if self.multi_merge:
318-
end_point.add(shader, vert_coords, self.prefs.start_color)
319-
if self.merge_location in ('FIRST', 'CENTER'):
320-
end_point.add(shader, self.end_comp_transformed, self.prefs.start_color)
321-
else:
322-
end_point.add(shader, self.end_comp_transformed, self.prefs.end_color)
323-
324-
# Middle point
325-
if self.merge_location == 'CENTER':
326-
if self.sel_mode == 'VERT':
327-
if self.multi_merge:
328-
midpoint = self.world_matrix @ find_center(vert_list)
329-
else:
330-
midpoint = self.world_matrix @ find_center([self.start_comp, self.end_comp])
331-
elif self.sel_mode == 'EDGE':
332-
midpoint = self.world_matrix @ \
333-
find_center([find_center(self.start_comp), find_center(self.end_comp)])
334-
335-
mid_point = DrawPoint()
336-
mid_point.add(shader, midpoint, self.prefs.end_color)
337-
338-
# Starting edge
339-
if self.sel_mode == 'EDGE':
340-
gpu.state.line_width_set(self.prefs.edge_width)
341-
e0v = [self.world_matrix @ v.co for v in self.start_comp.verts]
342-
343-
start_edge = DrawLine()
344-
if self.merge_location == 'FIRST':
345-
start_edge.add(shader, e0v, self.prefs.end_color)
346-
else:
347-
start_edge.add(shader, e0v, self.prefs.start_color)
348-
349-
# Starting point
350-
start_point = DrawPoint()
351-
if self.merge_location == 'FIRST':
352-
start_point.add(shader, self.start_comp_transformed, self.prefs.end_color)
353-
else:
354-
start_point.add(shader, self.start_comp_transformed, self.prefs.start_color)
355-
356-
gpu.state.line_width_set(1)
357-
gpu.state.point_size_set(1)
358-
359-
360-
def draw_callback_2d(self, context):
361-
# Have to add 1 for some reason in order to get proper number of segments.
362-
# This could potentially also be a ratio with the radius.
363-
circ_segments = 8 + 1
364-
draw_circle_2d(self.m_coord, self.prefs.circ_color, self.prefs.circ_radius, segments=circ_segments)
365-
366-
367-
def find_center(source):
368-
"""Assumes that the input is an Edge or an ordered object holding vertices or Vectors"""
369-
coords = []
370-
if isinstance(source, bmesh.types.BMEdge):
371-
coords = [source.verts[0].co, source.verts[1].co]
372-
elif isinstance(source[0], bmesh.types.BMVert):
373-
coords = [v.co for v in source]
374-
elif isinstance(source[0], Vector):
375-
coords = [v for v in source]
376-
377-
offset = Vector((0.0, 0.0, 0.0))
378-
for v in coords:
379-
offset = offset + v
380-
return offset / len(coords)
381-
382-
383-
def set_component(self, mode):
384-
selected_comp = None
385-
selected_comp = self.bm.select_history.active
386-
387-
if selected_comp:
388-
if mode == 'START':
389-
self.start_comp = selected_comp # Set the start component
390-
if self.sel_mode == 'VERT':
391-
self.start_comp_transformed = self.world_matrix @ self.start_comp.co
392-
elif self.sel_mode == 'EDGE':
393-
self.start_comp_transformed = self.world_matrix @ find_center(self.start_comp)
394-
if mode == 'END':
395-
self.end_comp = selected_comp # Set the end component
396-
if self.sel_mode == 'VERT':
397-
self.end_comp_transformed = self.world_matrix @ self.end_comp.co
398-
elif self.sel_mode == 'EDGE':
399-
self.end_comp_transformed = self.world_matrix @ find_center(self.end_comp)
400-
401-
402159
def main(self, context, event):
403160
"""Run this function on left mouse, execute the ray cast"""
404161
self.m_coord = event.mouse_region_x, event.mouse_region_y
@@ -535,6 +292,8 @@ def modal(self, context, event):
535292
elif self.sel_mode == 'EDGE':
536293
# Case of two fully separate edges
537294
if not any([v for v in self.start_comp.verts if v in self.end_comp.verts]):
295+
# Bridge is a hack to let Blender deal with deciding
296+
# which vertices connect to each other so we don't have to
538297
bridge = bmesh.ops.bridge_loops(self.bm, edges=(self.start_comp, self.end_comp))
539298
new_e0 = bridge['edges'][0]
540299
new_e1 = bridge['edges'][1]
@@ -628,6 +387,7 @@ def invoke(self, context, event):
628387
self.world_matrix = bpy.context.object.matrix_world
629388
self.bm = bmesh.from_edit_mesh(self.me)
630389

390+
# Get starting selection, if any.
631391
if self.sel_mode == 'VERT' and context.object.data.total_vert_sel > 1:
632392
self.start_sel = [v for v in self.bm.verts if v.select]
633393
elif self.sel_mode == 'EDGE' and context.object.data.total_edge_sel > 1:
@@ -682,6 +442,8 @@ class WorkSpaceMergeTool(bpy.types.WorkSpaceTool):
682442
bl_cursor = t_cursor
683443
bl_widget = None
684444
bl_keymap = (
445+
("mesh.merge_tool", {"ctrl": 1, "type": 'LEFTMOUSE', "value": 'PRESS'},
446+
{"properties": [("merge_location", 'CENTER')]}),
685447
("mesh.merge_tool", {"type": 'LEFTMOUSE', "value": 'PRESS'},
686448
{"properties": [("wait_for_input", False)]}),
687449
)

0 commit comments

Comments
 (0)