366 lines
14 KiB
Python
366 lines
14 KiB
Python
import bpy
|
|
from bpy.props import *
|
|
from bpy.types import Operator, Panel
|
|
|
|
|
|
import os
|
|
import mathutils
|
|
import bpy.utils.previews
|
|
import sys
|
|
from bpy_extras.io_utils import ExportHelper
|
|
from mathutils import Vector
|
|
|
|
# Load custom icon
|
|
# main_dir = os.path.dirname(__file__)
|
|
# icon_logo_path = os.path.join(main_dir, "sinewave.png")
|
|
# icon_collection = bpy.utils.previews.new()
|
|
# icon_collection.load("icon_logo", icon_logo_path, 'IMAGE')
|
|
|
|
############################################################################################################
|
|
|
|
class VIEW3D_PT_MeshFixLocalPanel(bpy.types.Panel):
|
|
bl_idname = "VIEW3D_PT_MeshFixLocalPanel"
|
|
bl_label = "Local Fix"
|
|
bl_space_type = "VIEW_3D"
|
|
bl_region_type = "UI"
|
|
bl_category = "Mesh Repair"
|
|
#bl_options = {'DEFAULT_CLOSED'}
|
|
# # Note: All drawers are already closed by default
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object is not None and context.object.mode == 'EDIT'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
scene = context.scene
|
|
props = scene.meshfixtool_properties
|
|
space = bpy.context.space_data
|
|
wiz_bool = props.wiz_boolean
|
|
|
|
# Deprecated from 4.2
|
|
# row = layout.row()
|
|
# row.operator("object.flatten_local", text="Flatten Surface", icon ='HIDE_ON')
|
|
# row.prop(props, "bumper_reduction")
|
|
|
|
#------ V2
|
|
#layout.box()
|
|
row = layout.row()
|
|
row.operator("mesh.select_more", text="Select More", icon ='EVENT_PLUS')
|
|
row.operator("mesh.select_less", text="Select Less", icon ='EVENT_MINUS')
|
|
|
|
# layout.separator()
|
|
|
|
# row = layout.row()
|
|
# col = row.column()
|
|
# col.prop(space.overlay, "show_face_orientation", text="Face Orientation")
|
|
|
|
# col = row.column()
|
|
# col.enabled = hasattr(space, 'overlay') and space.overlay.show_face_orientation
|
|
# col.operator("object.local_face_normal", text="Unify/Flip Face", icon ='ORIENTATION_NORMAL')
|
|
|
|
# col = row.column()
|
|
|
|
# if hasattr(bpy.context.scene, "fix_wizard_properties") and wiz_bool:
|
|
# col.operator("object.refind_local", text="Refine", icon ='KEYTYPE_KEYFRAME_VEC')
|
|
# else:
|
|
# col.operator("object.refind_local", text="Refine", icon ='MESH_ICOSPHERE')
|
|
|
|
# row = layout.row()
|
|
# if hasattr(bpy.context.scene, "fix_wizard_properties") and wiz_bool:
|
|
# row.operator("object.remesh_local_v2", text="Remesh", icon ='KEYTYPE_KEYFRAME_VEC')
|
|
# else:
|
|
# row.operator("object.remesh_local_v2", text="Remesh", icon ='MOD_REMESH')
|
|
# row.operator("object.smooth_local_v2", text="Smooth", icon = 'MOD_SMOOTH')
|
|
# row.operator("object.reduce_local", text="Decimate", icon = 'MOD_DECIM')
|
|
|
|
|
|
|
|
has_wiz = hasattr(scene, "fix_wizard_properties") and wiz_bool
|
|
row = layout.row()
|
|
col = row.column()
|
|
col.prop(space.overlay, "show_face_orientation", text="Face Normal")
|
|
|
|
col = row.column()
|
|
col.enabled = hasattr(space, 'overlay') and space.overlay.show_face_orientation
|
|
col.operator("object.local_face_normal", text="Unify/Flip", icon ='ORIENTATION_NORMAL')
|
|
|
|
col = row.column()
|
|
|
|
if has_wiz:
|
|
col.operator("object.refind_local", text="Refine", icon ='KEYTYPE_KEYFRAME_VEC')
|
|
else:
|
|
col.operator("object.refind_local", text="Refine", icon ='MESH_ICOSPHERE')
|
|
|
|
row = layout.row()
|
|
if has_wiz:
|
|
row.operator("object.remesh_local_v2", text="Remesh", icon ='KEYTYPE_KEYFRAME_VEC')
|
|
else:
|
|
row.operator("object.remesh_local_v2", text="Remesh", icon ='MOD_REMESH')
|
|
row.operator("object.smooth_local_v2", text="Smooth", icon = 'MOD_SMOOTH')
|
|
row.operator("object.reduce_local", text="Reduce", icon = 'MOD_DECIM')
|
|
|
|
|
|
############################################################################################################
|
|
|
|
class VIEW3D_PT_MeshFixGlobalPanel(bpy.types.Panel):
|
|
bl_idname = "VIEW3D_PT_MeshFixGlobalPanel"
|
|
bl_label = "Global Fix"
|
|
bl_space_type = "VIEW_3D"
|
|
bl_region_type = "UI"
|
|
bl_category = "Mesh Repair"
|
|
#bl_options = {'DEFAULT_CLOSED'}
|
|
# # Note: All drawers are already closed by default
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object is not None
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
scene = context.scene
|
|
props = scene.meshfixtool_properties
|
|
wiz_bool = props.wiz_boolean
|
|
fill_settings = props.wiz_fill_settings_boolean
|
|
|
|
full_func = props.tri_boolean or props.quad_boolean # or props.poly_boolean
|
|
|
|
active_obj = context.active_object
|
|
nvs = 0
|
|
ls_bool = False
|
|
size_lmt = None
|
|
|
|
if active_obj is not None and active_obj.type == 'MESH':
|
|
nvs = len(active_obj.data.vertices)
|
|
|
|
#=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
|
|
if active_obj and active_obj.type == 'MESH':
|
|
bbox = [active_obj.matrix_world @ Vector(corner) for corner in active_obj.bound_box]
|
|
size_x = (bbox[4] - bbox[0]).length
|
|
size_y = (bbox[2] - bbox[0]).length
|
|
size_z = (bbox[1] - bbox[0]).length
|
|
smallest_size = min(size_x, size_y, size_z)
|
|
size_lmt = smallest_size / 200
|
|
#=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
|
|
|
|
if 'Lattice_Structs' in active_obj:
|
|
ls_bool = True
|
|
|
|
# layout.label(text="Enable the LoopTools")
|
|
# layout.label(text="Enable the Edit Mesh Tools")
|
|
|
|
|
|
# v4.0.2
|
|
row = layout.row()
|
|
row.prop(props, "tri_boolean", text="Tri Mesh", icon ='MOD_TRIANGULATE')
|
|
row.prop(props, "quad_boolean", text="Quad Mesh", icon ='SPLIT_VERTICAL')
|
|
# row.prop(props, "poly_boolean", text="Poly Mesh", icon ='MESH_PLANE')
|
|
|
|
layout.prop(props, "face_normal_boolean", icon ='ORIENTATION_NORMAL')
|
|
|
|
row = layout.row()
|
|
row.prop(props, "minor_parts_boolean", icon ='UNLINKED')
|
|
row.prop(props, "minor_parts_threshold",text="Min %", slider=True)
|
|
|
|
row = layout.row()
|
|
row.enabled = full_func
|
|
row.prop(props, "spikes_boolean", icon ='SHARPCURVE')
|
|
row.prop(props, "spikes_angle_limit",text="Min Angle", slider=True)
|
|
|
|
row = layout.row()
|
|
row.enabled = full_func
|
|
row.prop(props, "intersection_boolean", icon ='MOD_SOLIDIFY')
|
|
row.prop(props, "intersection_angle_limit",text="Min Angle", slider=True)
|
|
|
|
row = layout.row()
|
|
row.enabled = full_func
|
|
row.prop(props, "volume_intersection_boolean", icon ='SELECT_EXTEND')
|
|
|
|
if hasattr(bpy.context.scene, "fix_wizard_properties") and props.wiz_boolean:
|
|
props_wiz = scene.fix_wizard_properties
|
|
row = layout.row()
|
|
row.enabled = full_func
|
|
row.prop(props_wiz, "record_filled_holes", text="", icon = 'EVENT_NDOF_BUTTON_ROLL_CCW')
|
|
row.operator("object.wiz_clear_all_holes_record", text="", icon = 'FILE_REFRESH')
|
|
|
|
row.prop(props, "holes_boolean", text = 'Smart Fill', toggle=True)
|
|
|
|
row.prop(props, "wiz_fill_settings_boolean", text = '', icon = 'PREFERENCES')
|
|
|
|
if full_func and props.wiz_fill_settings_boolean:
|
|
#*************************************************************************************#
|
|
hole_size_bool = props_wiz.specific_hole_mesh_size_bool
|
|
hole_ratio_bool = props_wiz.specific_hole_mesh_ratio_bool
|
|
|
|
box = layout.box()
|
|
row = box.row()
|
|
col = row.column()
|
|
col.scale_x = 2.3
|
|
col.prop(props_wiz, "specific_hole_mesh_size_bool", toggle=True)
|
|
|
|
col = row.column()
|
|
col.enabled = hole_size_bool
|
|
col.scale_x = 3
|
|
col.prop(props_wiz, "hole_mesh_size", text="")
|
|
|
|
row = box.row()
|
|
col = row.column()
|
|
col.scale_x = 2.3
|
|
col.prop(props_wiz, "specific_hole_mesh_ratio_bool", toggle=True)
|
|
|
|
col = row.column()
|
|
col.enabled = hole_ratio_bool
|
|
col.scale_x = 3
|
|
col.prop(props_wiz, "hole_mesh_ratio", text="")
|
|
|
|
if size_lmt and hole_size_bool and props_wiz.hole_mesh_size < size_lmt:
|
|
layout.alert = True
|
|
layout.label(text="Caution: The hole mesh size may be too small.", icon="ERROR")
|
|
layout.alert = False
|
|
|
|
if hole_ratio_bool and props_wiz.hole_mesh_ratio < 0.2:
|
|
layout.alert = True
|
|
layout.label(text="Caution: The hole mesh ratio may be too small.", icon="ERROR")
|
|
layout.alert = False
|
|
|
|
row = box.row()
|
|
row.prop(props_wiz, "hole_number_limit", text = "Number Limit", slider = True)
|
|
row.prop(props_wiz, "hole_max_size_limit", text = "Size Limit")
|
|
#*************************************************************************************#
|
|
else:
|
|
row = layout.row()
|
|
row.enabled = full_func
|
|
row.prop(props, "holes_boolean", text = 'Fill Holes', icon ='HOLDOUT_ON')
|
|
|
|
|
|
if not full_func:
|
|
layout.label(text="Enable Tri/Quad Mesh to access full functions", icon="MODIFIER")
|
|
else:
|
|
if ls_bool:
|
|
layout.alert = True
|
|
layout.label(text="Repairing the lattice structure is not recommended.", icon="ERROR")
|
|
layout.alert = False
|
|
elif nvs > 300000:
|
|
layout.alert = True
|
|
layout.label(text="Caution: Large mesh detected.", icon="ERROR")
|
|
layout.alert = False
|
|
else:
|
|
layout.separator()
|
|
|
|
# if props.minor_parts_boolean or props.spikes_boolean or props.intersection_boolean or props.holes_boolean or props.face_normal_boolean or props.volume_intersection_boolean:
|
|
# if not props.meshfixing:
|
|
# row = layout.row()
|
|
# row.operator("object.fix_mesh_global", text="AutoFix", icon ='HAND')
|
|
# row.prop(props, "statistics_boolean",text="", icon ='TEXT')
|
|
# else:
|
|
# layout.operator("object.fix_mesh_global", text="Calculating ...", icon ='SEQ_CHROMA_SCOPE')
|
|
|
|
|
|
# v4.0.1
|
|
if not props.meshfixing:
|
|
row = layout.row()
|
|
col = row.column()
|
|
|
|
# v4.0.2
|
|
col.enabled = full_func or (props.face_normal_boolean or props.minor_parts_boolean)
|
|
|
|
# col.enabled = (
|
|
# full_func or
|
|
# props.minor_parts_boolean or
|
|
# props.spikes_boolean or
|
|
# props.intersection_boolean or
|
|
# props.holes_boolean or
|
|
# props.face_normal_boolean or
|
|
# props.volume_intersection_boolean
|
|
# )
|
|
col.scale_y = 1.2
|
|
col.operator("object.fix_mesh_global", text="AutoFix", icon ='HAND')
|
|
|
|
col = row.column()
|
|
col.scale_y = 1.2
|
|
col.prop(props, "statistics_boolean",text="", icon ='TEXT')
|
|
else:
|
|
row = layout.row()
|
|
col = row.column()
|
|
col.scale_y = 1.2
|
|
col.alert = True
|
|
col.operator("object.fix_mesh_global", text="Calculating ...", icon ='SORTTIME')
|
|
col.alert = False
|
|
|
|
col = row.column()
|
|
col.scale_y = 1.2
|
|
col.prop(props, "statistics_boolean",text="", icon ='TEXT')
|
|
|
|
|
|
|
|
layout.separator()
|
|
|
|
if props.statistics_boolean:
|
|
box = layout.box()
|
|
row = box.row()
|
|
row.label(text="Mesh fixed:")
|
|
|
|
# v4.0.1
|
|
if hasattr(bpy.context.scene, "fix_wizard_properties"):
|
|
row.prop(props, "wiz_boolean", text="", icon = 'SOLO_ON' if wiz_bool else 'SOLO_OFF')
|
|
|
|
# row.operator("wm.mrts_open_website", icon = 'INTERNET')
|
|
|
|
row.operator("object.mrts_sinewave", icon = 'FORCE_HARMONIC')
|
|
box.label(text=f"Verts: {props.sum_vertices}")
|
|
box.label(text=f"Edges: {props.sum_edges}")
|
|
box.label(text=f"Faces: {props.sum_faces}")
|
|
if full_func and props.volume_intersection_boolean:
|
|
box.label(text=f"Intersect Volumes: {props.sum_volumes}")
|
|
|
|
# v4.0.2
|
|
if full_func and props.holes_boolean:
|
|
if hasattr(bpy.context.scene, "fix_wizard_properties") and props.wiz_boolean:
|
|
props_wiz = scene.fix_wizard_properties
|
|
total_hole_number = props_wiz.total_hole_number
|
|
# failed_number = props_wiz.failed_number
|
|
fixed_number = props_wiz.fixed_number
|
|
|
|
row = box.row()
|
|
if total_hole_number > 0 and fixed_number > 0:
|
|
row.label(text=f"Holes: {fixed_number} 🌟")
|
|
else:
|
|
row.label(text="Holes: 0 🧙")
|
|
|
|
if props_wiz.record_filled_holes:
|
|
row.operator("object.select_next_filled_hole", text="Next Hole", icon = 'LAYER_ACTIVE')
|
|
row.operator("object.select_all_filled_hole", text="All Holes", icon = 'OUTLINER_OB_POINTCLOUD')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# if total_hole_number > 0:
|
|
# box.label(text=f"Holes:")
|
|
# row = box.row()
|
|
# row.label(text=f"Total : {total_hole_number}")
|
|
# if fixed_number > 0:
|
|
# row.label(text=f"Filled : {fixed_number}")
|
|
# if failed_number > 0:
|
|
# row.alert = True
|
|
# row.label(text=f"Failed: {failed_number}")
|
|
# row.alert = False
|
|
|
|
# else:
|
|
# box.label(text="Holes: 0")
|
|
|
|
else:
|
|
box.label(text=f"Holes: {props.sum_holes}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
###############################################################################
|