/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** \file * \ingroup spinfo */ #include #include #include "MEM_guardedalloc.h" #include "DNA_armature_types.h" #include "DNA_collection_types.h" #include "DNA_curve_types.h" #include "DNA_gpencil_types.h" #include "DNA_lattice_types.h" #include "DNA_mesh_types.h" #include "DNA_meta_types.h" #include "DNA_scene_types.h" #include "DNA_windowmanager_types.h" #include "BLF_api.h" #include "BLI_listbase.h" #include "BLI_math.h" #include "BLI_string.h" #include "BLI_utildefines.h" #include "BLT_translation.h" #include "BKE_armature.h" #include "BKE_blender_version.h" #include "BKE_context.h" #include "BKE_curve.h" #include "BKE_displist.h" #include "BKE_editmesh.h" #include "BKE_gpencil.h" #include "BKE_key.h" #include "BKE_layer.h" #include "BKE_main.h" #include "BKE_object.h" #include "BKE_paint.h" #include "BKE_particle.h" #include "BKE_pbvh.h" #include "BKE_scene.h" #include "BKE_subdiv_ccg.h" #include "DEG_depsgraph_query.h" #include "ED_info.h" #include "UI_resources.h" #include "GPU_capabilities.h" #define MAX_INFO_NUM_LEN 16 typedef struct SceneStats { uint64_t totvert, totvertsel, totvertsculpt; uint64_t totedge, totedgesel; uint64_t totface, totfacesel, totfacesculpt; uint64_t totbone, totbonesel; uint64_t totobj, totobjsel; uint64_t totlamp, totlampsel; uint64_t tottri; uint64_t totgplayer, totgpframe, totgpstroke, totgppoint; } SceneStats; typedef struct SceneStatsFmt { /* Totals */ char totvert[MAX_INFO_NUM_LEN], totvertsel[MAX_INFO_NUM_LEN], totvertsculpt[MAX_INFO_NUM_LEN]; char totface[MAX_INFO_NUM_LEN], totfacesel[MAX_INFO_NUM_LEN]; char totedge[MAX_INFO_NUM_LEN], totedgesel[MAX_INFO_NUM_LEN], totfacesculpt[MAX_INFO_NUM_LEN]; char totbone[MAX_INFO_NUM_LEN], totbonesel[MAX_INFO_NUM_LEN]; char totobj[MAX_INFO_NUM_LEN], totobjsel[MAX_INFO_NUM_LEN]; char totlamp[MAX_INFO_NUM_LEN], totlampsel[MAX_INFO_NUM_LEN]; char tottri[MAX_INFO_NUM_LEN]; char totgplayer[MAX_INFO_NUM_LEN], totgpframe[MAX_INFO_NUM_LEN]; char totgpstroke[MAX_INFO_NUM_LEN], totgppoint[MAX_INFO_NUM_LEN]; } SceneStatsFmt; static bool stats_mesheval(Mesh *me_eval, bool is_selected, SceneStats *stats) { if (me_eval == NULL) { return false; } int totvert, totedge, totface, totloop; if (me_eval->runtime.subdiv_ccg != NULL) { const SubdivCCG *subdiv_ccg = me_eval->runtime.subdiv_ccg; BKE_subdiv_ccg_topology_counters(subdiv_ccg, &totvert, &totedge, &totface, &totloop); } else { totvert = me_eval->totvert; totedge = me_eval->totedge; totface = me_eval->totpoly; totloop = me_eval->totloop; } stats->totvert += totvert; stats->totedge += totedge; stats->totface += totface; stats->tottri += poly_to_tri_count(totface, totloop); if (is_selected) { stats->totvertsel += totvert; stats->totfacesel += totface; } return true; } static void stats_object(Object *ob, SceneStats *stats, GSet *objects_gset) { if ((ob->base_flag & BASE_VISIBLE_VIEWLAYER) == 0) { return; } const bool is_selected = (ob->base_flag & BASE_SELECTED) != 0; stats->totobj++; if (is_selected) { stats->totobjsel++; } switch (ob->type) { case OB_MESH: { /* we assume evaluated mesh is already built, this strictly does stats now. */ Mesh *me_eval = BKE_object_get_evaluated_mesh(ob); if (!BLI_gset_add(objects_gset, me_eval)) { break; } stats_mesheval(me_eval, is_selected, stats); break; } case OB_LAMP: stats->totlamp++; if (is_selected) { stats->totlampsel++; } break; case OB_SURF: case OB_CURVE: case OB_FONT: { Mesh *me_eval = BKE_object_get_evaluated_mesh(ob); if ((me_eval != NULL) && !BLI_gset_add(objects_gset, me_eval)) { break; } if (stats_mesheval(me_eval, is_selected, stats)) { break; } ATTR_FALLTHROUGH; /* Fallthrough to displist. */ } case OB_MBALL: { int totv = 0, totf = 0, tottri = 0; if (ob->runtime.curve_cache && ob->runtime.curve_cache->disp.first) { /* Note: We only get the same curve_cache for instances of the same curve/font/... * For simple linked duplicated objects, each has its own dispList. */ if (!BLI_gset_add(objects_gset, ob->runtime.curve_cache)) { break; } BKE_displist_count(&ob->runtime.curve_cache->disp, &totv, &totf, &tottri); } stats->totvert += totv; stats->totface += totf; stats->tottri += tottri; if (is_selected) { stats->totvertsel += totv; stats->totfacesel += totf; } break; } case OB_GPENCIL: { if (is_selected) { bGPdata *gpd = (bGPdata *)ob->data; if (!BLI_gset_add(objects_gset, gpd)) { break; } /* GPXX Review if we can move to other place when object change * maybe to depsgraph evaluation */ BKE_gpencil_stats_update(gpd); stats->totgplayer += gpd->totlayer; stats->totgpframe += gpd->totframe; stats->totgpstroke += gpd->totstroke; stats->totgppoint += gpd->totpoint; } break; } case OB_HAIR: case OB_POINTCLOUD: case OB_VOLUME: { break; } } } static void stats_object_edit(Object *obedit, SceneStats *stats) { if (obedit->type == OB_MESH) { BMEditMesh *em = BKE_editmesh_from_object(obedit); stats->totvert += em->bm->totvert; stats->totvertsel += em->bm->totvertsel; stats->totedge += em->bm->totedge; stats->totedgesel += em->bm->totedgesel; stats->totface += em->bm->totface; stats->totfacesel += em->bm->totfacesel; stats->tottri += em->tottri; } else if (obedit->type == OB_ARMATURE) { /* Armature Edit */ bArmature *arm = obedit->data; EditBone *ebo; for (ebo = arm->edbo->first; ebo; ebo = ebo->next) { stats->totbone++; if ((ebo->flag & BONE_CONNECTED) && ebo->parent) { stats->totvert--; } if (ebo->flag & BONE_TIPSEL) { stats->totvertsel++; } if (ebo->flag & BONE_ROOTSEL) { stats->totvertsel++; } if (ebo->flag & BONE_SELECTED) { stats->totbonesel++; } /* if this is a connected child and its parent is being moved, remove our root */ if ((ebo->flag & BONE_CONNECTED) && (ebo->flag & BONE_ROOTSEL) && ebo->parent && (ebo->parent->flag & BONE_TIPSEL)) { stats->totvertsel--; } stats->totvert += 2; } } else if (ELEM(obedit->type, OB_CURVE, OB_SURF)) { /* OB_FONT has no cu->editnurb */ /* Curve Edit */ Curve *cu = obedit->data; Nurb *nu; BezTriple *bezt; BPoint *bp; int a; ListBase *nurbs = BKE_curve_editNurbs_get(cu); for (nu = nurbs->first; nu; nu = nu->next) { if (nu->type == CU_BEZIER) { bezt = nu->bezt; a = nu->pntsu; while (a--) { stats->totvert += 3; if (bezt->f1 & SELECT) { stats->totvertsel++; } if (bezt->f2 & SELECT) { stats->totvertsel++; } if (bezt->f3 & SELECT) { stats->totvertsel++; } bezt++; } } else { bp = nu->bp; a = nu->pntsu * nu->pntsv; while (a--) { stats->totvert++; if (bp->f1 & SELECT) { stats->totvertsel++; } bp++; } } } } else if (obedit->type == OB_MBALL) { /* MetaBall Edit */ MetaBall *mball = obedit->data; MetaElem *ml; for (ml = mball->editelems->first; ml; ml = ml->next) { stats->totvert++; if (ml->flag & SELECT) { stats->totvertsel++; } } } else if (obedit->type == OB_LATTICE) { /* Lattice Edit */ Lattice *lt = obedit->data; Lattice *editlatt = lt->editlatt->latt; BPoint *bp; int a; bp = editlatt->def; a = editlatt->pntsu * editlatt->pntsv * editlatt->pntsw; while (a--) { stats->totvert++; if (bp->f1 & SELECT) { stats->totvertsel++; } bp++; } } } static void stats_object_pose(Object *ob, SceneStats *stats) { if (ob->pose) { bArmature *arm = ob->data; bPoseChannel *pchan; for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) { stats->totbone++; if (pchan->bone && (pchan->bone->flag & BONE_SELECTED)) { if (pchan->bone->layer & arm->layer) { stats->totbonesel++; } } } } } static bool stats_is_object_dynamic_topology_sculpt(Object *ob) { if (ob == NULL) { return false; } const eObjectMode object_mode = ob->mode; return ((object_mode & OB_MODE_SCULPT) && ob->sculpt && ob->sculpt->bm); } static void stats_object_sculpt(Object *ob, SceneStats *stats) { SculptSession *ss = ob->sculpt; if (!ss) { return; } switch (BKE_pbvh_type(ss->pbvh)) { case PBVH_FACES: stats->totvertsculpt = ss->totvert; stats->totfacesculpt = ss->totfaces; break; case PBVH_BMESH: stats->totvertsculpt = ob->sculpt->bm->totvert; stats->tottri = ob->sculpt->bm->totface; break; case PBVH_GRIDS: stats->totvertsculpt = BKE_pbvh_get_grid_num_vertices(ss->pbvh); stats->totfacesculpt = BKE_pbvh_get_grid_num_faces(ss->pbvh); break; } } /* Statistics displayed in info header. Called regularly on scene changes. */ static void stats_update(Depsgraph *depsgraph, ViewLayer *view_layer) { SceneStats stats = {0}; Object *ob = OBACT(view_layer); Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer); if (obedit) { /* Edit Mode */ FOREACH_OBJECT_BEGIN (view_layer, ob_iter) { if (ob_iter->base_flag & BASE_VISIBLE_VIEWLAYER) { if (ob_iter->mode == OB_MODE_EDIT) { stats_object_edit(ob_iter, &stats); stats.totobjsel++; } stats.totobj++; } } FOREACH_OBJECT_END; } else if (ob && (ob->mode & OB_MODE_POSE)) { /* Pose Mode */ stats_object_pose(ob, &stats); } else if (stats_is_object_dynamic_topology_sculpt(ob)) { /* Dynamic topology. Do not count all vertices, dynamic topology stats are initialized later as * part of sculpt stats. */ } else { /* Objects */ GSet *objects_gset = BLI_gset_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__); DEG_OBJECT_ITER_FOR_RENDER_ENGINE_BEGIN (depsgraph, ob_iter) { stats_object(ob_iter, &stats, objects_gset); } DEG_OBJECT_ITER_FOR_RENDER_ENGINE_END; BLI_gset_free(objects_gset, NULL); } if (ob && (ob->mode & OB_MODE_SCULPT)) { /* Sculpt Mode. When dynamic topology is not enabled both sculpt stats and scene stats are * collected. */ stats_object_sculpt(ob, &stats); } if (!view_layer->stats) { view_layer->stats = MEM_callocN(sizeof(SceneStats), "SceneStats"); } *(view_layer->stats) = stats; } void ED_info_stats_clear(ViewLayer *view_layer) { if (view_layer->stats) { MEM_freeN(view_layer->stats); view_layer->stats = NULL; } } static bool format_stats(Main *bmain, Scene *scene, ViewLayer *view_layer, SceneStatsFmt *stats_fmt) { /* Create stats if they don't already exist. */ if (!view_layer->stats) { /* Do not not access dependency graph if interface is marked as locked. */ wmWindowManager *wm = bmain->wm.first; if (wm->is_interface_locked) { return false; } Depsgraph *depsgraph = BKE_scene_ensure_depsgraph(bmain, scene, view_layer); stats_update(depsgraph, view_layer); } SceneStats *stats = view_layer->stats; /* Generate formatted numbers. */ #define SCENE_STATS_FMT_INT(_id) BLI_str_format_uint64_grouped(stats_fmt->_id, stats->_id) SCENE_STATS_FMT_INT(totvert); SCENE_STATS_FMT_INT(totvertsel); SCENE_STATS_FMT_INT(totvertsculpt); SCENE_STATS_FMT_INT(totedge); SCENE_STATS_FMT_INT(totedgesel); SCENE_STATS_FMT_INT(totface); SCENE_STATS_FMT_INT(totfacesel); SCENE_STATS_FMT_INT(totfacesculpt); SCENE_STATS_FMT_INT(totbone); SCENE_STATS_FMT_INT(totbonesel); SCENE_STATS_FMT_INT(totobj); SCENE_STATS_FMT_INT(totobjsel); SCENE_STATS_FMT_INT(totlamp); SCENE_STATS_FMT_INT(totlampsel); SCENE_STATS_FMT_INT(tottri); SCENE_STATS_FMT_INT(totgplayer); SCENE_STATS_FMT_INT(totgpframe); SCENE_STATS_FMT_INT(totgpstroke); SCENE_STATS_FMT_INT(totgppoint); #undef SCENE_STATS_FMT_INT return true; } static void get_stats_string( char *info, int len, size_t *ofs, ViewLayer *view_layer, SceneStatsFmt *stats_fmt) { Object *ob = OBACT(view_layer); Object *obedit = OBEDIT_FROM_OBACT(ob); eObjectMode object_mode = ob ? ob->mode : OB_MODE_OBJECT; LayerCollection *layer_collection = view_layer->active_collection; if (object_mode == OB_MODE_OBJECT) { *ofs += BLI_snprintf(info + *ofs, len - *ofs, "%s | ", BKE_collection_ui_name_get(layer_collection->collection)); } if (ob) { *ofs += BLI_snprintf(info + *ofs, len - *ofs, "%s | ", ob->id.name + 2); } if (obedit) { if (BKE_keyblock_from_object(obedit)) { *ofs += BLI_strncpy_rlen(info + *ofs, TIP_("(Key) "), len - *ofs); } if (obedit->type == OB_MESH) { *ofs += BLI_snprintf(info + *ofs, len - *ofs, TIP_("Verts:%s/%s | Edges:%s/%s | Faces:%s/%s | Tris:%s"), stats_fmt->totvertsel, stats_fmt->totvert, stats_fmt->totedgesel, stats_fmt->totedge, stats_fmt->totfacesel, stats_fmt->totface, stats_fmt->tottri); } else if (obedit->type == OB_ARMATURE) { *ofs += BLI_snprintf(info + *ofs, len - *ofs, TIP_("Joints:%s/%s | Bones:%s/%s"), stats_fmt->totvertsel, stats_fmt->totvert, stats_fmt->totbonesel, stats_fmt->totbone); } else { *ofs += BLI_snprintf( info + *ofs, len - *ofs, TIP_("Verts:%s/%s"), stats_fmt->totvertsel, stats_fmt->totvert); } } else if (ob && (object_mode & OB_MODE_POSE)) { *ofs += BLI_snprintf( info + *ofs, len - *ofs, TIP_("Bones:%s/%s"), stats_fmt->totbonesel, stats_fmt->totbone); } else if ((ob) && (ob->type == OB_GPENCIL)) { *ofs += BLI_snprintf(info + *ofs, len - *ofs, TIP_("Layers:%s | Frames:%s | Strokes:%s | Points:%s"), stats_fmt->totgplayer, stats_fmt->totgpframe, stats_fmt->totgpstroke, stats_fmt->totgppoint); } else if (ob && (object_mode & OB_MODE_SCULPT)) { if (stats_is_object_dynamic_topology_sculpt(ob)) { *ofs += BLI_snprintf(info + *ofs, len - *ofs, TIP_("Verts:%s | Tris:%s"), stats_fmt->totvert, stats_fmt->tottri); } else { *ofs += BLI_snprintf(info + *ofs, len - *ofs, TIP_("Verts:%s/%s | Faces:%s/%s"), stats_fmt->totvertsculpt, stats_fmt->totvert, stats_fmt->totfacesculpt, stats_fmt->totface); } } else { *ofs += BLI_snprintf(info + *ofs, len - *ofs, TIP_("Verts:%s | Faces:%s | Tris:%s"), stats_fmt->totvert, stats_fmt->totface, stats_fmt->tottri); } *ofs += BLI_snprintf( info + *ofs, len - *ofs, TIP_(" | Objects:%s/%s"), stats_fmt->totobjsel, stats_fmt->totobj); } static const char *info_statusbar_string(Main *bmain, Scene *scene, ViewLayer *view_layer, char statusbar_flag) { char formatted_mem[15]; size_t ofs = 0; static char info[256]; int len = sizeof(info); info[0] = '\0'; /* Scene statistics. */ if (statusbar_flag & STATUSBAR_SHOW_STATS) { SceneStatsFmt stats_fmt; if (format_stats(bmain, scene, view_layer, &stats_fmt)) { get_stats_string(info + ofs, len, &ofs, view_layer, &stats_fmt); } } /* Memory status. */ if (statusbar_flag & STATUSBAR_SHOW_MEMORY) { if (info[0]) { ofs += BLI_snprintf(info + ofs, len - ofs, " | "); } uintptr_t mem_in_use = MEM_get_memory_in_use(); BLI_str_format_byte_unit(formatted_mem, mem_in_use, false); ofs += BLI_snprintf(info + ofs, len, TIP_("Memory: %s"), formatted_mem); } /* GPU VRAM status. */ if ((statusbar_flag & STATUSBAR_SHOW_VRAM) && (GPU_mem_stats_supported())) { int gpu_free_mem_kb, gpu_tot_mem_kb; GPU_mem_stats_get(&gpu_tot_mem_kb, &gpu_free_mem_kb); float gpu_total_gb = gpu_tot_mem_kb / 1048576.0f; float gpu_free_gb = gpu_free_mem_kb / 1048576.0f; if (info[0]) { ofs += BLI_snprintf(info + ofs, len - ofs, " | "); } if (gpu_free_mem_kb && gpu_tot_mem_kb) { ofs += BLI_snprintf(info + ofs, len - ofs, TIP_("VRAM: %.1f/%.1f GiB"), gpu_total_gb - gpu_free_gb, gpu_total_gb); } else { /* Can only show amount of GPU VRAM available. */ ofs += BLI_snprintf(info + ofs, len - ofs, TIP_("VRAM: %.1f GiB Free"), gpu_free_gb); } } /* Blender version. */ if (statusbar_flag & STATUSBAR_SHOW_VERSION) { if (info[0]) { ofs += BLI_snprintf(info + ofs, len - ofs, " | "); } ofs += BLI_snprintf(info + ofs, len - ofs, TIP_("%s"), BKE_blender_version_string()); } return info; } const char *ED_info_statusbar_string(Main *bmain, Scene *scene, ViewLayer *view_layer) { return info_statusbar_string(bmain, scene, view_layer, U.statusbar_flag); } const char *ED_info_statistics_string(Main *bmain, Scene *scene, ViewLayer *view_layer) { const eUserpref_StatusBar_Flag statistics_status_bar_flag = STATUSBAR_SHOW_STATS | STATUSBAR_SHOW_MEMORY | STATUSBAR_SHOW_VERSION; return info_statusbar_string(bmain, scene, view_layer, statistics_status_bar_flag); } static void stats_row(int col1, const char *key, int col2, const char *value1, const char *value2, int *y, int height) { *y -= height; BLF_draw_default(col1, *y, 0.0f, key, 128); char values[128]; BLI_snprintf(values, sizeof(values), (value2) ? "%s / %s" : "%s", value1, value2); BLF_draw_default(col2, *y, 0.0f, values, sizeof(values)); } void ED_info_draw_stats( Main *bmain, Scene *scene, ViewLayer *view_layer, int x, int *y, int height) { SceneStatsFmt stats_fmt; if (!format_stats(bmain, scene, view_layer, &stats_fmt)) { return; } Object *ob = OBACT(view_layer); Object *obedit = OBEDIT_FROM_OBACT(ob); eObjectMode object_mode = ob ? ob->mode : OB_MODE_OBJECT; const int font_id = BLF_default(); UI_FontThemeColor(font_id, TH_TEXT_HI); BLF_enable(font_id, BLF_SHADOW); const float shadow_color[4] = {0.0f, 0.0f, 0.0f, 1.0f}; BLF_shadow(font_id, 5, shadow_color); BLF_shadow_offset(font_id, 1, -1); /* Translated labels for each stat row. */ enum { OBJ, VERTS, EDGES, FACES, TRIS, JOINTS, BONES, LAYERS, FRAMES, STROKES, POINTS, MAX_LABELS_COUNT }; char labels[MAX_LABELS_COUNT][64]; STRNCPY(labels[OBJ], IFACE_("Objects")); STRNCPY(labels[VERTS], IFACE_("Vertices")); STRNCPY(labels[EDGES], IFACE_("Edges")); STRNCPY(labels[FACES], IFACE_("Faces")); STRNCPY(labels[TRIS], IFACE_("Triangles")); STRNCPY(labels[JOINTS], IFACE_("Joints")); STRNCPY(labels[BONES], IFACE_("Bones")); STRNCPY(labels[LAYERS], IFACE_("Layers")); STRNCPY(labels[FRAMES], IFACE_("Frames")); STRNCPY(labels[STROKES], IFACE_("Strokes")); STRNCPY(labels[POINTS], IFACE_("Points")); int longest_label = 0; int i; for (i = 0; i < MAX_LABELS_COUNT; ++i) { longest_label = max_ii(longest_label, BLF_width(font_id, labels[i], sizeof(labels[i]))); } int col1 = x; int col2 = x + longest_label + (0.5f * U.widget_unit); /* Add some extra margin above this section. */ *y -= (0.6f * height); if (object_mode == OB_MODE_OBJECT) { stats_row(col1, labels[OBJ], col2, stats_fmt.totobjsel, stats_fmt.totobj, y, height); } if (obedit) { if (obedit->type == OB_MESH) { stats_row(col1, labels[OBJ], col2, stats_fmt.totobjsel, stats_fmt.totobj, y, height); stats_row(col1, labels[VERTS], col2, stats_fmt.totvertsel, stats_fmt.totvert, y, height); stats_row(col1, labels[EDGES], col2, stats_fmt.totedgesel, stats_fmt.totedge, y, height); stats_row(col1, labels[FACES], col2, stats_fmt.totfacesel, stats_fmt.totface, y, height); stats_row(col1, labels[TRIS], col2, stats_fmt.tottri, NULL, y, height); } else if (obedit->type == OB_ARMATURE) { stats_row(col1, labels[JOINTS], col2, stats_fmt.totvertsel, stats_fmt.totvert, y, height); stats_row(col1, labels[BONES], col2, stats_fmt.totbonesel, stats_fmt.totbone, y, height); } else { stats_row(col1, labels[VERTS], col2, stats_fmt.totvertsel, stats_fmt.totvert, y, height); } } else if (ob && (object_mode & OB_MODE_POSE)) { stats_row(col1, labels[BONES], col2, stats_fmt.totbonesel, stats_fmt.totbone, y, height); } else if ((ob) && (ob->type == OB_GPENCIL)) { stats_row(col1, labels[LAYERS], col2, stats_fmt.totgplayer, NULL, y, height); stats_row(col1, labels[FRAMES], col2, stats_fmt.totgpframe, NULL, y, height); stats_row(col1, labels[STROKES], col2, stats_fmt.totgpstroke, NULL, y, height); stats_row(col1, labels[POINTS], col2, stats_fmt.totgppoint, NULL, y, height); } else if (ob && (object_mode & OB_MODE_SCULPT)) { if (stats_is_object_dynamic_topology_sculpt(ob)) { stats_row(col1, labels[VERTS], col2, stats_fmt.totvertsculpt, NULL, y, height); stats_row(col1, labels[TRIS], col2, stats_fmt.tottri, NULL, y, height); } else { stats_row(col1, labels[VERTS], col2, stats_fmt.totvertsculpt, stats_fmt.totvert, y, height); stats_row(col1, labels[FACES], col2, stats_fmt.totfacesculpt, stats_fmt.totface, y, height); } } else { stats_row(col1, labels[VERTS], col2, stats_fmt.totvert, NULL, y, height); stats_row(col1, labels[EDGES], col2, stats_fmt.totedge, NULL, y, height); stats_row(col1, labels[FACES], col2, stats_fmt.totface, NULL, y, height); stats_row(col1, labels[TRIS], col2, stats_fmt.tottri, NULL, y, height); } BLF_disable(font_id, BLF_SHADOW); }