diff --git a/DUV_HotSpot.py b/DUV_HotSpot.py index 16b7424..36c8763 100644 --- a/DUV_HotSpot.py +++ b/DUV_HotSpot.py @@ -77,7 +77,15 @@ def execute(self, context): bmesh.update_edit_mesh(obj.data) bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') - angle = bpy.context.object.data.auto_smooth_angle + + # Fixed: Handle auto_smooth_angle for Blender 4.0+ + # Check if the old auto_smooth_angle attribute exists (Blender < 4.0) + if hasattr(bpy.context.object.data, 'auto_smooth_angle'): + angle = bpy.context.object.data.auto_smooth_angle + else: + # Default angle for Blender 4.0+ (30 degrees in radians) + angle = math.radians(bpy.context.scene.duv_hotspot_angle) + bpy.ops.mesh.edges_select_sharp(sharpness=angle) bpy.ops.mesh.mark_seam(clear=False) bpy.ops.mesh.select_all(action='DESELECT') @@ -455,4 +463,4 @@ def execute(self, context): return {'FINISHED'} - bpy.ops.object.editmode_toggle() + bpy.ops.object.editmode_toggle() \ No newline at end of file diff --git a/DUV_UVTexelDensity.py b/DUV_UVTexelDensity.py new file mode 100644 index 0000000..fc716f6 --- /dev/null +++ b/DUV_UVTexelDensity.py @@ -0,0 +1,121 @@ +from cmath import sqrt +import bpy +import bmesh +from mathutils import Vector + +def tri_area(co1, co2, co3): + return (co2-co1).cross(co3-co1).length/2.0 + +class DREAMUV_OT_texel_density(bpy.types.Operator): + """Set the texel density of the selected face(s)""" + bl_idname = "view3d.dreamuv_texeldensity" + bl_label = "3D View Texel Density" + bl_options = {"UNDO"} + + def execute(self, context): + + ob = bpy.context.object + mesh = ob.data + bm = bmesh.new() + bm = bmesh.from_edit_mesh(mesh) + # Copy of the original mesh so that we can restore it to a pre-triangulation state. + original_mesh = bm.copy() + bm.faces.ensure_lookup_table() + uv_layer = bm.loops.layers.uv.active + # Default values. + self.set_texel_density = 512.0 + self.set_image_resolution = 512.0 + module_name = __name__.split('.')[0] + addon_prefs = bpy.context.preferences.addons[module_name].preferences + self.set_texel_density = addon_prefs.set_texel_density + self.set_image_resolution = addon_prefs.set_image_resolution + target_texel_density = self.set_texel_density + target_image_resolution = self.set_image_resolution + # If custom image resolution is set to 0, fetch the texture's resolution automatically. + if self.set_image_resolution <= 0: + for face in bm.faces: + if face.select: + if face.material_index >= 0: + material = ob.material_slots[face.material_index].material + if material.use_nodes: + for x in material.node_tree.nodes: + if x.type == "TEX_IMAGE": + target_image_resolution = x.image.size[0] + + texel_uv_multiplier = (target_image_resolution / target_texel_density) ** 2 + + scale_faces = {} + scale_mult_list = [] + + for f in bm.faces: + face_area = 0.0 + uv_area = 0.0 + scale_multiplier = 0.0 + tri_count = 0 + curr_face = [] + + if f.select: + scale_faces[f] = scale_multiplier + curr_face.append(f) + + for tri in bmesh.ops.triangulate(bm, faces = curr_face)["faces"]: + # Can also use tri.calc_area(), at least for the face area. + face_area += tri_area(*(v.co for v in tri.verts)) + # Doesn't handle non-flat faces very well. + uv_area += tri_area( *(Vector( (*l[uv_layer].uv, 0) ) for l in tri.loops) ) + tri_count += 1 + + scale_multiplier = sqrt(((uv_area * texel_uv_multiplier) / face_area)).real + scale_faces[f] = scale_multiplier + scale_mult_list.append(scale_multiplier) + + face_area, uv_area = 0.0, 0.0 + tri_count = 0 + curr_face = [] + + bm.free() + # Restore the original mesh. + bpy.ops.object.mode_set(mode="OBJECT") + original_mesh.to_mesh(mesh) + bpy.ops.object.mode_set(mode="EDIT") + original_mesh.free() + + bm = bmesh.new() + bm = bmesh.from_edit_mesh(mesh) + bm.faces.ensure_lookup_table() + uv_layer = bm.loops.layers.uv.active + + faces = list() + #MAKE FACE LIST + for face in bm.faces: + if face.select: + faces.append(face) + + #get original size + xmin, xmax = faces[0].loops[0][uv_layer].uv.x, faces[0].loops[0][uv_layer].uv.x + ymin, ymax = faces[0].loops[0][uv_layer].uv.y, faces[0].loops[0][uv_layer].uv.y + + for face in faces: + for vert in face.loops: + xmin = min(xmin, vert[uv_layer].uv.x) + xmax = max(xmax, vert[uv_layer].uv.x) + ymin = min(ymin, vert[uv_layer].uv.y) + ymax = max(ymax, vert[uv_layer].uv.y) + + xcenter=(xmin+xmax)/2 + ycenter=(ymin+ymax)/2 + + for i, face in enumerate(faces): + for loop in face.loops: + loop[uv_layer].uv.x -= xcenter + loop[uv_layer].uv.y -= ycenter + + loop[uv_layer].uv.x /= scale_mult_list[i] + loop[uv_layer].uv.y /= scale_mult_list[i] + + loop[uv_layer].uv.x += xcenter + loop[uv_layer].uv.y += ycenter + + bmesh.update_edit_mesh(mesh, loop_triangles=False, destructive=False) + + return {'FINISHED'} \ No newline at end of file diff --git a/README.md b/README.md index 164f05d..855190c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # DreamUV +## This Fork +* Added an option to manually set the face angle for the hotspot tool +* Added an option to set the texel density ## About DreamUV is a collection of tools that allow you to manipulate UVs in the 3D viewport. This toolset is designed to be used with reusable textures like tiling textures, trimsheets and texture atlases. Its intent is to allow you to texture your geometry without having to exit the 3D view, saving you time and improving flexibility. @@ -69,3 +72,4 @@ To hotspot a mesh, simply select the faces you want to hotspot and click the hot ![screenshot](http://www.brameulaers.net/blender/addons/github_images/dreamuv_hotspot.jpg) If you have any feedback you can just message me via twitter: @leukbaars + diff --git a/__init__.py b/__init__.py index b8abd89..0ba7195 100644 --- a/__init__.py +++ b/__init__.py @@ -6,7 +6,7 @@ "category": "UV", "author": "Bram Eulaers", "description": "Edit selected faces'UVs directly inside the 3D Viewport. WIP. Check for updates @leukbaars", - "blender": (2, 90, 0), + "blender": (3, 1, 0), "version": (0, 9) } @@ -28,6 +28,7 @@ from . import DUV_UVInset from . import DUV_UVTrim from . import DUV_ApplyMaterial +from . import DUV_UVTexelDensity import importlib if 'bpy' in locals(): @@ -47,6 +48,7 @@ importlib.reload(DUV_UVInset) importlib.reload(DUV_UVTrim) importlib.reload(DUV_ApplyMaterial) + importlib.reload(DUV_UVTexelDensity) class DUVUVToolsPreferences(bpy.types.AddonPreferences): bl_idname = __name__ @@ -72,6 +74,16 @@ class DUVUVToolsPreferences(bpy.types.AddonPreferences): description="Rotate Angle Snap Size", default=45 ) + set_texel_density : FloatProperty( + name="Texel Density", + description="Pixels per unit length.", + default=512.0 + ) + set_image_resolution : FloatProperty( + name="Texture Resolution", + description="0 = fetch the texture resolution automatically", + default=512.0 + ) # This should get its own py file @@ -130,6 +142,16 @@ def draw(self, context): op = row.operator("view3d.dreamuv_uvscalestep", text="-Y") op.direction = "-Y" col.separator() + + row = col.row(align = True) + row.operator("view3d.dreamuv_texeldensity", text="Texel Density", icon="TEXTURE") + row = row.row(align = True) + row.prop(addon_prefs, 'set_texel_density', text="") + row = col.row(align = True) + #row.label(text="Image resolution") + row.prop(addon_prefs, 'set_image_resolution', text="Texture Resolution") + row = col.row(align = True) + col.separator() row = col.row(align = True) row.operator("view3d.dreamuv_uvrotate", text="Rotate", icon="FILE_REFRESH") @@ -151,6 +173,7 @@ def draw(self, context): op.direction = "y" col.separator() + row = col.row(align = True) op = row.operator("view3d.dreamuv_uvinsetstep", text="Inset", icon="FULLSCREEN_EXIT") op.direction = "in" @@ -179,8 +202,7 @@ def draw(self, context): unwraptool=col.operator("uv.unwrap", text="Blender Unwrap", icon='UV') unwraptool.method='CONFORMAL' unwraptool.margin=0.001 - - + col.separator() box = layout.box() if bpy.context.object.mode != 'EDIT': @@ -208,6 +230,9 @@ def draw(self, context): row.label(text="Atlas Scale:") row.prop(context.scene, "duvhotspotscale", text="") row = col.row(align = True) + row.label(text="Face Angle:") + row.prop(context.scene, "duv_hotspot_angle", text="") + row = col.row(align = True) row.label(text="Hotspot material:") row.prop_search(context.scene, "duv_hotspotmaterial", bpy.data, "materials", text="") row = col.row(align = True) @@ -307,6 +332,7 @@ def prefs(): DUV_UVTrim.DREAMUV_OT_uv_trimnext, DUV_UVTrim.DREAMUV_OT_uv_capnext, DUV_ApplyMaterial.DREAMUV_OT_apply_material, + DUV_UVTexelDensity.DREAMUV_OT_texel_density ) def poll_material(self, material): @@ -321,6 +347,7 @@ def register(): bpy.types.Scene.trim_index = bpy.props.IntProperty (name = "trim_index",default = 0,description = "trim index") bpy.types.Scene.cap_index = bpy.props.IntProperty (name = "cap_index",default = 0,description = "cap index") bpy.types.Scene.duv_trimuseinset = bpy.props.BoolProperty (name = "duv_trimuseinset",default = False,description = "Use inset when trimming") + bpy.types.Scene.duv_hotspot_angle = bpy.props.FloatProperty(name="Hotspot Angle", default=30.0, min=0.0, max=180.0, description="Angle threshold for grouping faces in hotspot tool") bpy.types.Scene.uvinsetpixels = bpy.props.FloatProperty (name = "uv inset pixel amount",default = 1.0,description = "") bpy.types.Scene.uvinsettexsize = bpy.props.FloatProperty (name = "uv inset texture size",default = 1024.0,description = "") bpy.types.Scene.uvtransferxmin = bpy.props.FloatProperty (name = "uvtransferxmin",default = 0.0,description = "uv left bottom corner X")