Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 79 additions & 29 deletions code/idmapper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
bl_info = {
"name": "IDMapper",
"author": "Michel Anders (varkenvarken)",
"version": (0, 0, 20250816102007),
"version": (0, 0, 20250902164500),
"blender": (4, 4, 0),
"location": "View3D > Vertex Paint > Paint",
"description": "Create an idmap as a vertex color layer, grouping related faces by color",
Expand Down Expand Up @@ -187,6 +187,18 @@ class IDMapper(bpy.types.Operator):
size=4,
default=Vector((0, 0, 0, 1)),
)
number_of_ids: BoolProperty(
name="Number of ids",
description="Limit the number of distinct vertex colors assigned",
default=False,
)
colors: IntProperty(
name="Colors",
description="Maximum number of distinct vertex colors to use",
default=8,
min=1,
max=256,
)

method: EnumProperty(
items=[
Expand All @@ -203,8 +215,8 @@ class IDMapper(bpy.types.Operator):
default="HEURISTIC",
)

# materialid : BoolProperty (name="By Material id", description="Calculate id map based on assigned materials (disabled when no materials present on mesh)", default=False)
# facemapid : BoolProperty (name="By Face map", description="Calculate id map based on face map membership", default=False)
# materialid : BoolProperty (name="By Material id", description="Calculate id map based on assigned materials (disabled when no materials present on mesh)", default=False)
# facemapid : BoolProperty (name="By Face map", description="Calculate id map based on face map membership", default=False)
selectmaterial: BoolProperty(
name="Only material id",
description="Calculate id map only for faces with a material id (disabled when no materials present on mesh)",
Expand Down Expand Up @@ -290,6 +302,22 @@ def mapcolors(self, ob):
indexed_colors[-1] = list(
self.basecolor[:]
) # but the default color for non selected faces is defined by the user
# Optional: reduce number of distinct output colors
if self.number_of_ids:
ncolors = max(1, int(self.colors))
# limit by available non-default weights
ncolors = min(ncolors, len([w for w in set(weightmap.values()) if w != -1]))
try:
palette = color_set(ncolors)
except Exception:
palette = np.ones((ncolors, 4), dtype=np.float32)
for i in range(ncolors):
h = i / max(1, ncolors)
palette[i, :3] = [h, 1.0 - h, (0.5 + h) % 1.0]
remap_keys = sorted([w for w in indexed_colors.keys() if w != -1])
for i, w in enumerate(remap_keys):
c = palette[i % ncolors]
indexed_colors[w][:3] = [float(c[0]), float(c[1]), float(c[2])]
# facemaps are currently unsupported in 4.0, see https://projects.blender.org/blender/blender/issues/105317
# elif self.method == "FACEMAPID":
# fm = bm.faces.layers.face_map.verify()
Expand Down Expand Up @@ -510,6 +538,22 @@ def mapcolors(self, ob):
indexed_colors[-1] = list(
self.basecolor[:]
) # but the default color for non selected faces is defined by the user
# Optional: reduce number of distinct output colors
if self.number_of_ids:
ncolors = max(1, int(self.colors))
# limit by available non-default weights
ncolors = min(ncolors, len([w for w in set(weightmap.values()) if w != -1]))
try:
palette = color_set(ncolors)
except Exception:
palette = np.ones((ncolors, 4), dtype=np.float32)
for i in range(ncolors):
h = i / max(1, ncolors)
palette[i, :3] = [h, 1.0 - h, (0.5 + h) % 1.0]
remap_keys = sorted([w for w in indexed_colors.keys() if w != -1], key=lambda x: str(x))
for i, w in enumerate(remap_keys):
c = palette[i % ncolors]
indexed_colors[w][:3] = [float(c[0]), float(c[1]), float(c[2])]

for f, weight in weightmap.items():
color = indexed_colors[weight]
Expand Down Expand Up @@ -585,6 +629,10 @@ def draw(self, context):
row.prop(self, "selected")
if self.selected or self.method == "MATERIALID":
row.prop(self, "basecolor")
row = layout.row()
row.prop(self, "number_of_ids")
if self.number_of_ids:
row.prop(self, "colors", slider=True)

if self.method in {"HEURISTIC", "FACEMAPID"} or self.usedisplaycolor:
layout.prop(self, "seed")
Expand Down Expand Up @@ -1344,31 +1392,33 @@ def color_set(n):

# each line MUST have two tabs
paint_helptext = [
"Left mouse paint",
" S pick color",
" K fill region",
"Alt K fill same colored regions",
" W smooth (ctrl respects seams)",
"Alt W restricted smooth (ctrl respects seams)",
" P paint selected faces",
" F resize cursor (+mousewheel)",
"Num + expand region (ctrl respects seams)",
"Num - shrink region (ctrl respects seams)",
"Num / flatten face colors",
"Ctl Z undo",
"Ctl 1-9 select from color list",
"Ctl 0 roll color list up",
"Left mouse paint",
" S pick color",
" K fill region",
"Alt K fill same colored regions",
" W smooth (ctrl respects seams)",
"Alt W restricted smooth (ctrl respects seams)",
" P paint selected faces",
" F resize cursor (+mousewheel)",
"Num + expand region (ctrl respects seams)",
"Num - shrink region (ctrl respects seams)",
"Num / flatten face colors",
"Ctl Z undo",
"Ctl 1-9 select from color list",
"Ctl 0 roll color list up",
]

blf.size(0, 10)

top = 10 + 12 * len(paint_helptext)
ph1 = [ht.split("\t")[0] for ht in paint_helptext]
w1, _ = blf.dimensions(0, max(ph1, key=len))
ph2 = [ht.split("\t")[1] for ht in paint_helptext]
w2, _ = blf.dimensions(0, max(ph2, key=len))
ph3 = [ht.split("\t")[2] for ht in paint_helptext]
w3, _ = blf.dimensions(0, max(ph3, key=len))
_parts = [ht.split("\t") for ht in paint_helptext]
_parts = [(p + ["", "", ""])[:3] for p in _parts]
ph1 = [p[0] for p in _parts]
w1, _ = blf.dimensions(0, max(ph1, key=len) if ph1 else "")
ph2 = [p[1] for p in _parts]
w2, _ = blf.dimensions(0, max(ph2, key=len) if ph2 else "")
ph3 = [p[2] for p in _parts]
w3, _ = blf.dimensions(0, max(ph3, key=len) if ph3 else "")
left3 = w3 + 10
left2 = w2 + 4 + left3
left1 = w1 + 8 + left2
Expand Down Expand Up @@ -2147,11 +2197,11 @@ def orient2d(a, b, x, y):

def draw_tris_np(pixels, size, color, tris, step):
"""
pixels a size x size x channels int32 ndarray
size size of the image
color N x length int32 ndarray (either length 3 or 4)
tris a N x 3 x 2 float python list of lists of lists
step a function thats is called with argument (1) every 100 triangles
pixels a size x size x channels int32 ndarray
size size of the image
color N x length int32 ndarray (either length 3 or 4)
tris a N x 3 x 2 float python list of lists of lists
step a function thats is called with argument (1) every 100 triangles
"""

XY = np.array([size - 1, size - 1], dtype=np.int32)
Expand Down Expand Up @@ -3235,4 +3285,4 @@ def unregister():


if __name__ == "__main__":
register()
register()
2 changes: 2 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,8 @@ <h3 class="c30 c18" id="h.6nrh1j367p0g"><span class="c21">Base color</span></h3>
different restrictions and settings and later add (if black is chosen as a base color) or multiply (if
white) the two layers (See also </span><span class="c2"><a class="c10" href="#h.25gkddjp31v7">Vertex Color
Merge</a></span><span class="c5">)</span></p>
<h3 class="c30 c18" id="h.6nrh1j367p0Q"><span class="c21">Number of ids</span></h3>
<p class="c8"><span>When selected this will limit the number of vertex colors assigned, between 1 and 256. It will attempt to distribute the limited range evenly across the object.</span></p>
<h2 class="c1" id="h.m8aylenqndox-1"><span class="c28 c17">Options</span></h2>
<h3 class="c30 c18" id="h.ol8n4fkyqajs"><span class="c21">Similarity</span></h3>
<p class="c8"><span class="c5">This slider lets you specify how similar faces should be to be considered part of the
Expand Down