Skip to content

Commit e16ae98

Browse files
committed
v1.1.0
1 parent 110acd2 commit e16ae98

11 files changed

Lines changed: 3653 additions & 90 deletions

File tree

__init__.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,17 @@
4343
UVGAMI_OT_add_grid,
4444
UVGAMI_OT_remove_grid,
4545
)
46-
from .src.ops.viewer import UVGAMI_OT_view_unwrap
46+
from .src.ops.viewer import (
47+
UVGAMI_OT_view_unwrap,
48+
UVGAMI_OT_view_uvs,
49+
)
4750
from .src.ops.info import (
4851
UVGAMI_OT_clear_logs,
4952
UVGAMI_OT_copy_logs,
5053
)
5154
from .src.ui.panels import (
5255
UVGAMI_PT_main,
56+
UVGAMI_PT_speed,
5357
UVGAMI_PT_guides,
5458
UVGAMI_PT_symmetry,
5559
UVGAMI_PT_grid,
@@ -62,17 +66,18 @@
6266
UVGAMI_PG_properties,
6367
UVGAMI_AP_preferences,
6468
)
69+
from .src.updater import addon_updater_ops
6570

6671

6772
bl_info = {
6873
"name": "UVgami",
6974
"author": "Daniel Boxer",
7075
"description": "Automatic UV unwrapping",
7176
"blender": (2, 90, 0),
72-
"version": (1, 0, 0),
77+
"version": (1, 1, 0),
7378
"location": "View3D > Sidebar > UVgami",
7479
"category": "UV",
75-
"doc_url": "https://github.com/DanielBoxer/UVgami/blob/main/documentation.md",
80+
"doc_url": "",
7681
"tracker_url": "https://github.com/DanielBoxer/UVgami/issues",
7782
}
7883

@@ -99,9 +104,11 @@
99104
UVGAMI_OT_clear_logs,
100105
UVGAMI_OT_copy_logs,
101106
UVGAMI_OT_setup_wsl,
107+
UVGAMI_OT_view_uvs,
102108
UVGAMI_PT_main,
103109
UVGAMI_PT_guides,
104110
UVGAMI_PT_symmetry,
111+
UVGAMI_PT_speed,
105112
UVGAMI_PT_grid,
106113
UVGAMI_PT_pack,
107114
UVGAMI_PT_uv,
@@ -113,6 +120,10 @@
113120

114121

115122
def register():
123+
try:
124+
addon_updater_ops.register(bl_info)
125+
except ValueError:
126+
pass
116127
for cls in classes:
117128
bpy.utils.register_class(cls)
118129
bpy.types.Scene.uvgami = bpy.props.PointerProperty(type=UVGAMI_PG_properties)

src/manager.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# See __init__.py and LICENSE for more information
33

44
import bpy
5+
import bmesh
56
import multiprocessing
67
from .utils import (
78
get_preferences,
@@ -11,6 +12,8 @@
1112
set_active_any,
1213
switch_shading,
1314
set_origin,
15+
new_bmesh,
16+
set_bmesh,
1417
)
1518
from . import progress_bar
1619
from .logger import logger
@@ -126,6 +129,11 @@ def finish_unwrap(self, unwrap, invalid_pass=False):
126129
if unwrap.symmetrize_job is not None:
127130
unwrap.symmetrize_job.finish(output)
128131

132+
if unwrap.merge_cuts:
133+
bm = new_bmesh(output)
134+
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-7)
135+
set_bmesh(bm, output)
136+
129137
# automatically add grid material to final object
130138
if props.auto_grid:
131139
grid_img = make_grid_img()

src/ops/misc.py

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@
77
import subprocess
88
import shutil
99
from ..ui.panels import expand
10-
from ..utils import calc_center, validate_obj, get_linux_path, get_preferences
10+
from ..utils import (
11+
calc_center,
12+
validate_obj,
13+
get_linux_path,
14+
get_preferences,
15+
check_exists,
16+
deselect_all,
17+
)
18+
19+
sym_planes = {}
1120

1221

1322
class UVGAMI_OT_expand(bpy.types.Operator):
@@ -51,30 +60,48 @@ def execute(self, context):
5160
class UVGAMI_OT_preview_symmetry(bpy.types.Operator):
5261
bl_idname = "uvgami.preview_symmetry"
5362
bl_label = "Preview"
54-
bl_description = "Add a plane mesh to verify symmetry of selected meshes"
63+
bl_description = (
64+
"Add a plane meshes to verify symmetry of selected meshes."
65+
" Press again to delete the planes"
66+
)
5567
bl_options = {"UNDO"}
5668

5769
def execute(self, context):
5870
sym = context.scene.uvgami.sym_axes
71+
old_select = context.selected_objects
72+
old_active = context.view_layer.objects.active
5973
for obj in context.selected_objects:
60-
if validate_obj(self, obj):
61-
center = calc_center(obj)
62-
if "X" in sym:
63-
bpy.ops.mesh.primitive_plane_add(
64-
size=obj.dimensions.y * 2,
65-
location=center,
66-
rotation=(0, math.radians(90), 0),
67-
)
68-
if "Y" in sym:
69-
bpy.ops.mesh.primitive_plane_add(
70-
size=obj.dimensions.x * 2,
71-
location=center,
72-
rotation=(math.radians(90), 0, 0),
73-
)
74-
if "Z" in sym:
75-
bpy.ops.mesh.primitive_plane_add(
76-
size=obj.dimensions.z * 2, location=center
77-
)
74+
if obj not in sym_planes:
75+
if validate_obj(self, obj):
76+
center = calc_center(obj)
77+
before = set(context.scene.objects)
78+
if "X" in sym:
79+
bpy.ops.mesh.primitive_plane_add(
80+
size=obj.dimensions.y * 2,
81+
location=center,
82+
rotation=(0, math.radians(90), 0),
83+
)
84+
if "Y" in sym:
85+
bpy.ops.mesh.primitive_plane_add(
86+
size=obj.dimensions.x * 2,
87+
location=center,
88+
rotation=(math.radians(90), 0, 0),
89+
)
90+
if "Z" in sym:
91+
bpy.ops.mesh.primitive_plane_add(
92+
size=obj.dimensions.z * 2, location=center
93+
)
94+
sym_planes[obj] = set(context.scene.objects).difference(before)
95+
else:
96+
for plane in sym_planes[obj]:
97+
if check_exists(plane):
98+
bpy.data.objects.remove(plane, do_unlink=True)
99+
del sym_planes[obj]
100+
101+
deselect_all()
102+
for obj in old_select:
103+
obj.select_set(True)
104+
context.view_layer.objects.active = old_active
78105
return {"FINISHED"}
79106

80107

src/ops/start.py

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
import bpy
55
import bmesh
6-
import mathutils
76
import pathlib
87
import platform
98
import shutil
109
import subprocess
10+
import numpy
1111
from ..manager import manager
1212
from ..unwrap import Unwrap
1313
from ..utils import (
@@ -20,6 +20,8 @@
2020
set_bmesh,
2121
get_linux_path,
2222
calc_center,
23+
apply_transforms,
24+
cut,
2325
)
2426
from ..ui.panels import expand
2527
from ..job import Preserve, Join, Cleanup, Symmetrise
@@ -37,6 +39,7 @@ def execute(self, context):
3739
try:
3840
logger.new_info()
3941
prefs = get_preferences()
42+
props = context.scene.uvgami
4043
engine_path = pathlib.Path(prefs.engine_path)
4144
if prefs.autosave:
4245
if bpy.data.is_saved:
@@ -149,6 +152,43 @@ def execute(self, context):
149152
# if the modifier is disabled, don't apply
150153
pass
151154

155+
# cuts
156+
if props.use_cuts and not props.use_symmetry:
157+
bm = new_bmesh(copy_object)
158+
159+
if props.cut_type == "EVEN":
160+
# make even cuts on axes
161+
apply_transforms(copy_object)
162+
props = context.scene.uvgami
163+
axes = props.cut_axes
164+
cuts = props.cuts
165+
166+
a_length = len(axes) if len(axes) != 0 else 3
167+
d = cuts // a_length
168+
r = cuts % a_length
169+
170+
# distribute cuts
171+
x_num = d if r == 0 else d + 1
172+
y_num = d if r != 2 else d + 1
173+
z_num = d
174+
175+
center = calc_center(obj)
176+
if not axes or "X" in axes:
177+
cut(x_num, center, copy_object.dimensions.x, 0, bm)
178+
if not axes or "Y" in axes:
179+
cut(y_num, center, copy_object.dimensions.y, 1, bm)
180+
if not axes or "Z" in axes:
181+
cut(z_num, center, copy_object.dimensions.z, 2, bm)
182+
183+
else:
184+
# cut on seams
185+
seams = numpy.zeros(len(bm.edges), dtype=numpy.bool)
186+
obj.data.edges.foreach_get("use_seam", seams)
187+
bm_seams = numpy.array(bm.edges)[seams]
188+
bmesh.ops.split_edges(bm, edges=bm_seams)
189+
190+
set_bmesh(bm, copy_object)
191+
152192
# save name, format: input name, unwrap name
153193
names[copy_object.name] = [obj.name, obj.name]
154194
objects.append(copy_object)
@@ -166,7 +206,6 @@ def execute(self, context):
166206
self.report({"ERROR"}, report_msg)
167207
return {"CANCELLED"}
168208
else:
169-
props = context.scene.uvgami
170209
input_path = get_dir_path() / "input"
171210
input_path.mkdir(exist_ok=True)
172211
output_path = input_path.parent / "output"
@@ -194,17 +233,7 @@ def execute(self, context):
194233
# bisect if symmetry on
195234
axes = props.sym_axes
196235
if props.use_symmetry:
197-
# apply all transforms
198-
location, _, scale = obj.matrix_basis.decompose()
199-
actual = (
200-
mathutils.Matrix.Translation(location)
201-
@ obj.matrix_basis.to_3x3().normalized().to_4x4()
202-
@ mathutils.Matrix.Diagonal(scale).to_4x4()
203-
)
204-
obj.data.transform(actual)
205-
for c in obj.children:
206-
c.matrix_local = actual @ c.matrix_local
207-
obj.matrix_basis = mathutils.Matrix()
236+
apply_transforms(obj)
208237

209238
bm = new_bmesh(obj)
210239
obj_center = calc_center(obj)
@@ -227,8 +256,9 @@ def execute(self, context):
227256
plane_no=direction,
228257
clear_inner=True,
229258
)
230-
# if the object already had vertices down it's center plane, there will be duplicates
231-
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
259+
# if the object already had vertices down it's center plane
260+
# there will be duplicates
261+
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-7)
232262
set_bmesh(bm, obj)
233263

234264
# separate objects
@@ -240,11 +270,11 @@ def execute(self, context):
240270
join_job = Join(len(s))
241271
cleanup_job = None
242272

243-
# the delete job can come after join because it doesn't depend on
244-
# the unwrapped objects
273+
# the delete job can come after join because it doesn't depend
274+
# on the unwrapped objects
245275
if prefs.cleanup == "HIDE" or prefs.cleanup == "DELETE":
246-
# the count is > 1 because all the separated objs need to finish
247-
# before deleting the original
276+
# the count is > 1 because all the separated objs need to
277+
# finish before deleting the original
248278
cleanup_job = Cleanup(len(s), prefs.cleanup)
249279
manager.input[cleanup_job] = input[object_idx]
250280

@@ -444,6 +474,7 @@ def execute(self, context):
444474
len(obj.data.vertices),
445475
shade_smooth,
446476
angle,
477+
props.use_cuts and not props.use_symmetry,
447478
)
448479
manager.active.append(unwrap)
449480

0 commit comments

Comments
 (0)