229 lines
7.2 KiB
Python
229 lines
7.2 KiB
Python
import bpy
|
|
from bpy.types import Operator
|
|
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import subprocess
|
|
|
|
from . jrt_pref import get_preferences
|
|
|
|
from . utils.select_utils import *
|
|
|
|
class JRT_OT_Remesh(Operator):
|
|
bl_idname = "object.jrt_remesh_op"
|
|
bl_label = "Quad remesh"
|
|
bl_description = "Execute quad remesher"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return len(context.selected_objects) > 0
|
|
|
|
def is_blender_quadriflow(self, context):
|
|
return context.scene.remesher == "Blender Quadriflow"
|
|
|
|
def is_instant_meshes(self, context):
|
|
return context.scene.remesher == "Instant Meshes"
|
|
|
|
def get_app_path(self, context):
|
|
pref = get_preferences()
|
|
|
|
if self.is_instant_meshes(context):
|
|
return pref.im_filepath
|
|
|
|
return None
|
|
|
|
|
|
def get_app_name(self, context):
|
|
|
|
if self.is_instant_meshes(context):
|
|
return "Instant Meshes"
|
|
|
|
return None
|
|
|
|
def execute(self, context):
|
|
|
|
# Instant meshes remesher is used
|
|
self.report({'INFO'}, "JRemesh started")
|
|
|
|
scn = context.scene
|
|
|
|
mode = get_mode()
|
|
|
|
to_object()
|
|
|
|
self.try_triangulate(context)
|
|
|
|
active_obj_name = context.active_object.name
|
|
|
|
if self.is_instant_meshes(context):
|
|
try:
|
|
|
|
app_name = self.get_app_name(context)
|
|
app_path = self.get_app_path(context)
|
|
|
|
if not os.path.isfile(app_path):
|
|
raise IOError(f"Path to {app_name} is missing.")
|
|
|
|
tmp_dir = tempfile.gettempdir()
|
|
orig = os.path.join(tmp_dir, 'orig_object.obj')
|
|
output = os.path.join(tmp_dir, 'remeshed_object.obj')
|
|
|
|
# Export original object
|
|
if bpy.app.version >= (3, 3, 0):
|
|
bpy.ops.wm.obj_export(filepath=orig,
|
|
check_existing=False,
|
|
export_selected_objects=True,
|
|
apply_modifiers=True,
|
|
export_smooth_groups=False,
|
|
smooth_group_bitflags=False,
|
|
export_normals=True,
|
|
export_uv=True)
|
|
else:
|
|
bpy.ops.export_scene.obj(filepath=orig,
|
|
check_existing=False,
|
|
use_selection=True,
|
|
use_mesh_modifiers=True,
|
|
use_edges=True,
|
|
use_smooth_groups=False,
|
|
use_smooth_groups_bitflags=False,
|
|
use_normals=True,
|
|
use_uvs=True)
|
|
|
|
orig_object = bpy.data.objects[active_obj_name]
|
|
|
|
self.do_remesh(app_path, orig, output, context)
|
|
|
|
# Import remeshed object
|
|
if bpy.app.version >= (3,3,0):
|
|
bpy.ops.wm.obj_import(filepath=output)
|
|
else:
|
|
bpy.ops.import_scene.obj(filepath=output,
|
|
use_split_objects=False,
|
|
use_smooth_groups=False,
|
|
use_image_search=False)
|
|
|
|
# Post import remeshed object
|
|
remeshed_object = bpy.context.selected_objects[0]
|
|
|
|
remeshed_object.name = active_obj_name + '_rm'
|
|
|
|
remeshed_object.data.materials.clear()
|
|
for mat in orig_object.data.materials:
|
|
remeshed_object.data.materials.append(mat)
|
|
|
|
for edge in remeshed_object.data.edges:
|
|
edge.use_edge_sharp = False
|
|
|
|
deselect_all()
|
|
|
|
select(remeshed_object)
|
|
|
|
bpy.ops.object.shade_flat()
|
|
|
|
bpy.ops.mesh.customdata_custom_splitnormals_clear()
|
|
|
|
orig_object.hide_set(True)
|
|
|
|
make_active(remeshed_object)
|
|
|
|
os.remove(output)
|
|
|
|
except IOError as ioerr:
|
|
self.report({'ERROR'}, "JRemesh: {0}".format(ioerr))
|
|
else:
|
|
self.report({'INFO'}, "JRemesh completed")
|
|
|
|
# Quadriflow remesher is used
|
|
elif self.is_blender_quadriflow(context):
|
|
|
|
try:
|
|
|
|
# Duplicate active mesh
|
|
bpy.ops.object.duplicate()
|
|
|
|
orig_object = bpy.data.objects[active_obj_name]
|
|
|
|
bpy.ops.object.quadriflow_remesh(
|
|
use_mesh_symmetry=scn.qf_use_mesh_sym,
|
|
use_preserve_sharp=scn.qf_preserve_sharp,
|
|
use_preserve_boundary=scn.qf_preserve_mesh_boundary,
|
|
preserve_paint_mask=scn.qf_preserve_paint_mask,
|
|
smooth_normals=scn.qf_smooth_normals,
|
|
target_faces=scn.qf_face_count
|
|
)
|
|
|
|
orig_object.hide_set(True)
|
|
|
|
new_name = active_obj_name + "_rm"
|
|
get_active().name = new_name
|
|
|
|
# Remove modifiers if triangulate was set
|
|
if scn.rm_triangulate:
|
|
get_active().modifiers.clear()
|
|
|
|
except RuntimeError as re:
|
|
self.report({'ERROR'}, "JRemesh: {0}".format(re))
|
|
else:
|
|
self.report({'INFO'}, "JRemesh completed")
|
|
|
|
self.try_make_manifold(scn)
|
|
|
|
to_mode(mode)
|
|
|
|
return {'FINISHED'}
|
|
|
|
def try_triangulate(self, context):
|
|
|
|
if context.scene.rm_triangulate:
|
|
for m in get_active().modifiers:
|
|
if m.type == "TRIANGULATE":
|
|
return
|
|
|
|
bpy.ops.object.modifier_add(type='TRIANGULATE')
|
|
|
|
def try_make_manifold(self, scn):
|
|
if scn.rm_fill_holes:
|
|
to_edit()
|
|
select_mesh()
|
|
bpy.ops.mesh.fill_holes()
|
|
deselect_mesh()
|
|
|
|
def do_remesh(self, app_path, orig, output, context):
|
|
|
|
cmd = self.build_im_command(context, app_path, orig, output)
|
|
subprocess.run(cmd)
|
|
|
|
|
|
|
|
# Quadriflow modifier can be called?
|
|
#
|
|
# def build_qf_command(self, context, app_path, orig, output):
|
|
# options= []
|
|
|
|
# if context.scene.qf_sharp:
|
|
# options.append('-sharp')
|
|
|
|
# options.extend(['-i', orig,
|
|
# '-o', output,
|
|
# '-f', str(context.scene.qf_face_count)])
|
|
|
|
# return [app_path] + options
|
|
|
|
def build_im_command(self, context, app_path, orig, output):
|
|
options = ['-c', str(context.scene.crease),
|
|
'-v', str(context.scene.vertex_count),
|
|
'-S', str(context.scene.smooth),
|
|
'-o', output]
|
|
|
|
if context.scene.deterministic:
|
|
options.append('-d')
|
|
if context.scene.dominant:
|
|
options.append('-D')
|
|
if context.scene.intrinsic:
|
|
options.append('-i')
|
|
if context.scene.boundaries:
|
|
options.append('-b')
|
|
|
|
return [app_path] + options + [orig] |