Skip to content

Commit d1a0546

Browse files
TitusLVRclaude
andcommitted
feat(stats): modular overlay lines — dims, pos/dist, material, mods, instances, parent, units
Eight new individually-toggleable stat lines in the viewport overlay, each a pure property read per redraw (no geometry traversal, no caches): - Dims: active object dimensions in scene units - Pos/Dist: world location + distance to viewpoint - Mat: active material, filled/total slots, empty-slot warning, optional shared-users count - Mods: modifier count + viewport/render visibility mismatch warning - Instances: warning when object data is shared (fake user excluded) - Parent: parent name + constraint count - Units: warning-only line when scene unit scale != 1.0 All toggles live in prefs (Statistics Overlay section) and round-trip through the UI_TEXT_STAT save/load section. Defaults: dims ON, rest OFF. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent fca727d commit d1a0546

5 files changed

Lines changed: 188 additions & 1 deletion

File tree

docs/preferences.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ on the preferences class. The list below mirrors `draw()` top-to-bottom.
4242
| -------------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------- |
4343
| `iops_stat` | `Bool` | `True` | Master toggle for the persistent viewport statistics overlay (active object name, UV maps, scale...). |
4444
| `show_filename_stat` | `Bool` | `True` | Show / hide the current `.blend` filename inside the statistics overlay. |
45+
| `show_dimensions_stat` | `Bool` | `True` | Active object dimensions in scene units (hidden when all zero, e.g. empties). |
46+
| `show_view_position_stat` | `Bool` | `False` | Active object world position + distance from the viewpoint. |
47+
| `show_material_stat` | `Bool` | `False` | Active material name and filled/total slot count; warns on `No material` / `Empty slots`. |
48+
| `show_material_users_stat` | `Bool` | `False` | Appends `(N users)` to the material line when the active material is shared. |
49+
| `show_modifiers_stat` | `Bool` | `False` | Modifier count of the active object; warns when any modifier's viewport/render visibility differs. |
50+
| `show_instances_stat` | `Bool` | `False` | Warns with the user count when the active object's data is shared (instances / linked duplicates). |
51+
| `show_parent_stat` | `Bool` | `False` | Parent name and constraint count of the active object (hidden when it has neither). |
52+
| `show_units_stat` | `Bool` | `False` | Warning-only line shown when the scene unit scale is not `1.0`. |
4553

4654
All colors, sizes and text positioning for stats now live in the **Theme**
4755
tab — see [HUD & Theme](ui/ui_hud.md).
@@ -217,7 +225,7 @@ file takes precedence on every subsequent launch.
217225
| `IOPS_DEBUG` | `IOPS_DEBUG` |
218226
| `EXECUTOR` | `executor_column_count`, `executor_scripts_folder`, `executor_name_length`, `executor_use_script_path_user`, `executor_scripts_subfolder` |
219227
| `SPLIT_AREA_PIES` | One block per slot 1..9 (no 5) with `_factor`, `_pos`, `_ui`, `_alt_ui` |
220-
| `UI_TEXT_STAT` | `iops_stat`, `show_filename_stat` |
228+
| `UI_TEXT_STAT` | `iops_stat`, `show_filename_stat`, `show_dimensions_stat`, `show_view_position_stat`, `show_material_stat`, `show_material_users_stat`, `show_modifiers_stat`, `show_instances_stat`, `show_parent_stat`, `show_units_stat` |
221229
| `TEXTURE_TO_MATERIAL`| `texture_to_material_prefixes`, `texture_to_material_suffixes` |
222230
| `SNAP_COMBOS` | `snap_combo_1..8` — each with `SNAP_ELEMENTS`, `TOOL_SETTINGS`, `TRANSFORMATION` |
223231
| `MODIFIER_WINDOW` | `modifier_window_method` |

operators/preferences/io_addon_preferences.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,18 @@ def safe_get(data, key, default=None):
224224
defaults = default_prefs.get("UI_TEXT_STAT", {})
225225
prefs.iops_stat = safe_get(value, "iops_stat", defaults.get("iops_stat", True))
226226
prefs.show_filename_stat = safe_get(value, "show_filename_stat", defaults.get("show_filename_stat", True))
227+
for key_, default_ in (
228+
("show_dimensions_stat", True),
229+
("show_instances_stat", False),
230+
("show_modifiers_stat", False),
231+
("show_material_stat", False),
232+
("show_material_users_stat", False),
233+
("show_parent_stat", False),
234+
("show_units_stat", False),
235+
("show_view_position_stat", False),
236+
):
237+
setattr(prefs, key_,
238+
safe_get(value, key_, defaults.get(key_, default_)))
227239

228240
case "TEXTURE_TO_MATERIAL":
229241
if isinstance(value, dict):

prefs/addon_preferences.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,54 @@ class IOPS_AddonPreferences(bpy.types.AddonPreferences):
127127
default=True,
128128
)
129129

130+
show_dimensions_stat: BoolProperty(
131+
name="Dimensions",
132+
description="Show active object dimensions in scene units",
133+
default=True,
134+
)
135+
136+
show_instances_stat: BoolProperty(
137+
name="Instances",
138+
description="Warn when the active object's data is shared by other objects",
139+
default=False,
140+
)
141+
142+
show_modifiers_stat: BoolProperty(
143+
name="Modifiers",
144+
description="Show modifier count and warn on viewport/render visibility mismatch",
145+
default=False,
146+
)
147+
148+
show_material_stat: BoolProperty(
149+
name="Material",
150+
description="Show active material name, slot usage and empty-slot warnings",
151+
default=False,
152+
)
153+
154+
show_material_users_stat: BoolProperty(
155+
name="Material Users",
156+
description="Append the active material's user count when it is shared",
157+
default=False,
158+
)
159+
160+
show_parent_stat: BoolProperty(
161+
name="Parent / Constraints",
162+
description="Show parent name and constraint count of the active object",
163+
default=False,
164+
)
165+
166+
show_units_stat: BoolProperty(
167+
name="Units Warning",
168+
description="Warn when the scene unit scale is not 1.0",
169+
default=False,
170+
)
171+
172+
show_view_position_stat: BoolProperty(
173+
name="Position / Distance",
174+
description="Show active object world position and distance to the viewpoint",
175+
default=False,
176+
)
177+
130178
# Persistent GPU widget panels (ui/widgets) — JSON blob with each
131179
# widget's visibility/position, read/written by ui/widgets/state.py.
132180
# Internal storage only, intentionally not drawn in the prefs UI.
@@ -812,6 +860,15 @@ def draw(self, context):
812860
if body is not None:
813861
body.prop(self, "iops_stat", toggle=True)
814862
body.prop(self, "show_filename_stat", toggle=True)
863+
grid = body.grid_flow(columns=2, align=True)
864+
grid.prop(self, "show_dimensions_stat", toggle=True)
865+
grid.prop(self, "show_view_position_stat", toggle=True)
866+
grid.prop(self, "show_material_stat", toggle=True)
867+
grid.prop(self, "show_material_users_stat", toggle=True)
868+
grid.prop(self, "show_modifiers_stat", toggle=True)
869+
grid.prop(self, "show_instances_stat", toggle=True)
870+
grid.prop(self, "show_parent_stat", toggle=True)
871+
grid.prop(self, "show_units_stat", toggle=True)
815872
body.separator()
816873
body.label(text="Colors, sizes and text positioning live in the Theme tab.")
817874

prefs/iops_prefs.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,14 @@ def safelist(attr, default=None):
160160
"UI_TEXT_STAT": {
161161
"iops_stat": safe("iops_stat", True),
162162
"show_filename_stat": safe("show_filename_stat", True),
163+
"show_dimensions_stat": safe("show_dimensions_stat", True),
164+
"show_instances_stat": safe("show_instances_stat", False),
165+
"show_modifiers_stat": safe("show_modifiers_stat", False),
166+
"show_material_stat": safe("show_material_stat", False),
167+
"show_material_users_stat": safe("show_material_users_stat", False),
168+
"show_parent_stat": safe("show_parent_stat", False),
169+
"show_units_stat": safe("show_units_stat", False),
170+
"show_view_position_stat": safe("show_view_position_stat", False),
163171
},
164172
"TEXTURE_TO_MATERIAL": {
165173
"texture_to_material_prefixes": safe("texture_to_material_prefixes", "env_"),

utils/draw_stats.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
from ..ui.hud import text as hud_text
66
from ..ui.hud.text import draw as hud_text_draw, measure as hud_text_measure
77

8+
# Object types whose data block carries material slots — gates the material
9+
# stat line so empties/lights/cameras don't show a useless "No material".
10+
_MATERIAL_OBJECT_TYPES = frozenset(
11+
{"MESH", "CURVE", "SURFACE", "META", "FONT",
12+
"GREASEPENCIL", "GPENCIL", "VOLUME", "POINTCLOUD"}
13+
)
14+
815

916
def draw_iops_statistics():
1017
context = bpy.context
@@ -73,6 +80,101 @@ def _dim(text):
7380
_t(file_status, role=value_role, x=base_column_x)
7481
offset_y -= row_step
7582

83+
unit_settings = context.scene.unit_settings
84+
85+
def _fmt_len(value):
86+
if unit_settings.system != "NONE":
87+
return bpy.utils.units.to_string(
88+
unit_settings.system, "LENGTH",
89+
value * unit_settings.scale_length, precision=2)
90+
return f"{value:.2f}"
91+
92+
if active_object:
93+
if prefs.show_dimensions_stat:
94+
dims = active_object.dimensions
95+
if dims.x or dims.y or dims.z:
96+
_t("Dims:", role=Role.HUD_LABEL)
97+
_t(" x ".join(_fmt_len(d) for d in dims),
98+
role=Role.HUD_LABEL_ACTIVE, x=base_column_x)
99+
offset_y -= row_step
100+
101+
if prefs.show_view_position_stat:
102+
loc = active_object.matrix_world.translation
103+
value = f"{loc.x:.2f}, {loc.y:.2f}, {loc.z:.2f}"
104+
rv3d = space_3d.region_3d if space_3d else None
105+
if rv3d:
106+
dist = (rv3d.view_matrix @ loc).length
107+
value += f" Dist: {_fmt_len(dist)}"
108+
_t("Pos:", role=Role.HUD_LABEL)
109+
_t(value, role=Role.HUD_LABEL_ACTIVE, x=base_column_x)
110+
offset_y -= row_step
111+
112+
if (prefs.show_material_stat
113+
and active_object.type in _MATERIAL_OBJECT_TYPES):
114+
slots = active_object.material_slots
115+
filled = sum(1 for slot in slots if slot.material)
116+
mat = active_object.active_material
117+
segments = []
118+
if mat:
119+
name = mat.name
120+
if prefs.show_material_users_stat and mat.users > 1:
121+
name += f" ({mat.users} users)"
122+
segments.append((f"{name} [{filled}/{len(slots)}]",
123+
Role.HUD_LABEL_ACTIVE))
124+
else:
125+
segments.append(("No material", Role.HUD_STATS_ERROR))
126+
if slots and filled < len(slots):
127+
segments.append(("Empty slots", Role.HUD_STATS_ERROR))
128+
_t("Mat:", role=Role.HUD_LABEL)
129+
col_x = base_column_x
130+
for seg_text, seg_role in segments:
131+
_t(seg_text, role=seg_role, x=col_x)
132+
w, _h = _dim(seg_text)
133+
col_x += w + 6
134+
offset_y -= row_step
135+
136+
if prefs.show_modifiers_stat and active_object.modifiers:
137+
mods = active_object.modifiers
138+
count = str(len(mods))
139+
_t("Mods:", role=Role.HUD_LABEL)
140+
_t(count, role=Role.HUD_LABEL_ACTIVE, x=base_column_x)
141+
if any(m.show_viewport != m.show_render for m in mods):
142+
w, _h = _dim(count)
143+
_t("viewport ≠ render", role=Role.HUD_STATS_ERROR,
144+
x=base_column_x + w + 6)
145+
offset_y -= row_step
146+
147+
if prefs.show_instances_stat:
148+
obj_data = getattr(active_object, "data", None)
149+
if obj_data is not None:
150+
users = obj_data.users - (1 if obj_data.use_fake_user else 0)
151+
if users > 1:
152+
_t("Instances:", role=Role.HUD_LABEL)
153+
_t(str(users), role=Role.HUD_STATS_ERROR,
154+
x=base_column_x)
155+
offset_y -= row_step
156+
157+
if prefs.show_parent_stat and (active_object.parent
158+
or active_object.constraints):
159+
_t("Parent:", role=Role.HUD_LABEL)
160+
col_x = base_column_x
161+
if active_object.parent:
162+
parent_name = active_object.parent.name
163+
_t(parent_name, role=Role.HUD_LABEL_ACTIVE, x=col_x)
164+
w, _h = _dim(parent_name)
165+
col_x += w + 6
166+
constraint_count = len(active_object.constraints)
167+
if constraint_count:
168+
_t(f"+{constraint_count} constraints",
169+
role=Role.HUD_LABEL_ACTIVE, x=col_x)
170+
offset_y -= row_step
171+
172+
if prefs.show_units_stat and unit_settings.scale_length != 1.0:
173+
_t("Units:", role=Role.HUD_LABEL)
174+
_t(f"scale {unit_settings.scale_length:g}",
175+
role=Role.HUD_STATS_ERROR, x=base_column_x)
176+
offset_y -= row_step
177+
76178
if active_object and active_object.type == "MESH":
77179
scale_stat = []
78180
scale = active_object.scale

0 commit comments

Comments
 (0)