Skip to content
12 changes: 10 additions & 2 deletions DUV_HotSpot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -455,4 +463,4 @@ def execute(self, context):

return {'FINISHED'}

bpy.ops.object.editmode_toggle()
bpy.ops.object.editmode_toggle()
121 changes: 121 additions & 0 deletions DUV_UVTexelDensity.py
Original file line number Diff line number Diff line change
@@ -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'}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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

33 changes: 30 additions & 3 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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():
Expand All @@ -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__
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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"
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand All @@ -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")
Expand Down