2025-12-01
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
import bpy # type: ignore
|
||||
|
||||
|
||||
class BakeParticlesOperator(bpy.types.Operator):
|
||||
"""Bake all Particles to Keyframes. The particles need to have an instance object set. The baked instances are moved to a new collection."""
|
||||
|
||||
bl_idname = "object.bake_particles"
|
||||
bl_label = "Bake Particles"
|
||||
|
||||
KEYFRAME_LOCATION: bpy.props.BoolProperty()
|
||||
KEYFRAME_ROTATION: bpy.props.BoolProperty()
|
||||
KEYFRAME_SCALE: bpy.props.BoolProperty()
|
||||
# Viewport and render visibility.
|
||||
KEYFRAME_VISIBILITY: bpy.props.BoolProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.object
|
||||
if not obj:
|
||||
return False
|
||||
|
||||
if not hasattr(obj, "particle_systems"):
|
||||
return False
|
||||
|
||||
if not len(obj.particle_systems) > 0:
|
||||
return False
|
||||
|
||||
for particlesys in obj.particle_systems:
|
||||
if bpy.data.particles[particlesys.settings.name].instance_object is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def create_particle_collection(self, collection_name):
|
||||
# Create or clear the particle collection.
|
||||
# Create a new collection and link it to the scene.
|
||||
collection_name = bpy.context.scene.particle_settings.collection_name
|
||||
particle_collection = bpy.data.collections.get(collection_name)
|
||||
|
||||
if particle_collection is None:
|
||||
particle_collection = bpy.data.collections.new(collection_name)
|
||||
|
||||
if bpy.context.scene.collection.children.get(collection_name) is None:
|
||||
bpy.context.scene.collection.children.link(particle_collection)
|
||||
|
||||
# remove all objects from the particle collection
|
||||
if len(particle_collection.objects) > 0:
|
||||
rem_obj_names = []
|
||||
for obj in particle_collection.objects:
|
||||
rem_obj_names.append(obj.name)
|
||||
|
||||
for rem_name in rem_obj_names:
|
||||
bpy.data.objects.remove(bpy.data.objects[rem_name])
|
||||
|
||||
def create_objects_for_particles(self, ps, obj, collection_name):
|
||||
# Duplicate the given object for every particle and return the duplicates.
|
||||
# Use instances instead of full copies.
|
||||
obj_list = []
|
||||
mesh = obj.data
|
||||
particle_collection = bpy.data.collections.get(collection_name)
|
||||
|
||||
for particle in ps.particles:
|
||||
dupli = bpy.data.objects.new(name=obj.name, object_data=mesh)
|
||||
particle_collection.objects.link(dupli)
|
||||
obj_list.append(dupli)
|
||||
|
||||
# copy modifiers to duplicates
|
||||
# adapted from: https://blender.stackexchange.com/a/4883
|
||||
for modifierOrig in obj.modifiers:
|
||||
modifierNew = dupli.modifiers.new(modifierOrig.name, modifierOrig.type)
|
||||
# collect names of writable properties
|
||||
properties = [
|
||||
p.identifier
|
||||
for p in modifierOrig.bl_rna.properties
|
||||
if not p.is_readonly
|
||||
]
|
||||
|
||||
# copy those properties
|
||||
for prop in properties:
|
||||
setattr(modifierNew, prop, getattr(modifierOrig, prop))
|
||||
|
||||
return obj_list
|
||||
|
||||
def match_and_keyframe_objects(self, ps, obj_list, start_frame, end_frame):
|
||||
# Match and keyframe the objects to the particles for every frame in the
|
||||
# given range.
|
||||
frame_offset = bpy.context.scene.particle_settings.frame_offset
|
||||
for frame in range(start_frame, end_frame + 1, frame_offset):
|
||||
bpy.context.scene.frame_set(frame)
|
||||
for p, obj in zip(ps.particles, obj_list):
|
||||
self.match_object_to_particle(p, obj)
|
||||
self.keyframe_obj(obj)
|
||||
|
||||
def match_object_to_particle(self, p, obj):
|
||||
# Match the location, rotation, scale and visibility of the object to
|
||||
# the particle.
|
||||
loc = p.location
|
||||
rot = p.rotation
|
||||
size = p.size
|
||||
# Set rotation mode to quaternion to match particle rotation.
|
||||
obj.rotation_mode = "QUATERNION"
|
||||
obj.rotation_quaternion = rot
|
||||
|
||||
if self.KEYFRAME_VISIBILITY:
|
||||
if p.alive_state != "ALIVE":
|
||||
size *= 0.01
|
||||
|
||||
obj.location = loc
|
||||
obj.scale = (size, size, size)
|
||||
|
||||
# obj.hide_viewport = not(vis)
|
||||
# obj.hide_render = not(vis)
|
||||
|
||||
def keyframe_obj(self, obj):
|
||||
# Keyframe location, rotation, scale and visibility if specified.
|
||||
if self.KEYFRAME_LOCATION:
|
||||
obj.keyframe_insert("location")
|
||||
if self.KEYFRAME_ROTATION:
|
||||
obj.keyframe_insert("rotation_quaternion")
|
||||
if self.KEYFRAME_SCALE:
|
||||
obj.keyframe_insert("scale")
|
||||
# if self.KEYFRAME_VISIBILITY:
|
||||
# obj.keyframe_insert("hide_viewport")
|
||||
# obj.keyframe_insert("hide_render")
|
||||
|
||||
def execute(self, context):
|
||||
# go to start frame
|
||||
bpy.context.scene.frame_set(0)
|
||||
|
||||
collection_name = bpy.context.scene.particle_settings.collection_name
|
||||
self.create_particle_collection(collection_name)
|
||||
|
||||
# get emitter and instance
|
||||
emitter = bpy.context.object
|
||||
ps_list = emitter.particle_systems
|
||||
for i, ps in enumerate(ps_list):
|
||||
instance = ps.settings.instance_object
|
||||
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
|
||||
# Extract locations
|
||||
|
||||
ps = depsgraph.objects[emitter.name].particle_systems[i]
|
||||
|
||||
# update ps hack
|
||||
# bpy.data.particles[ps.name].count += 1
|
||||
# bpy.data.particles[ps.name].count -= 1
|
||||
|
||||
start_frame = bpy.context.scene.frame_start
|
||||
end_frame = bpy.context.scene.frame_end
|
||||
obj_list = self.create_objects_for_particles(ps, instance, collection_name)
|
||||
self.match_and_keyframe_objects(ps, obj_list, start_frame, end_frame)
|
||||
|
||||
# Simplify
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
|
||||
for obj in bpy.data.collections[collection_name].all_objects:
|
||||
obj.select_set(True)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(BakeParticlesOperator)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(BakeParticlesOperator)
|
||||
@@ -0,0 +1,122 @@
|
||||
import bpy # type: ignore
|
||||
|
||||
|
||||
class JoinAnimationOperator(bpy.types.Operator):
|
||||
bl_idname = "scene.join_anim"
|
||||
bl_label = "Join Animation"
|
||||
bl_description = "Join animations for all selected objects by making an NLA strip for each object and naming the track consistently"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
anim_name: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
sel_objects = context.selected_objects
|
||||
for obj in sel_objects:
|
||||
if obj.animation_data is None:
|
||||
continue
|
||||
if obj.animation_data.action is None:
|
||||
continue
|
||||
# if hasattr(obj.animation_data,"action"):
|
||||
action = obj.animation_data.action
|
||||
track = obj.animation_data.nla_tracks.new()
|
||||
track.strips.new(self.anim_name, int(action.frame_start), action)
|
||||
track.name = self.anim_name
|
||||
obj.animation_data.action = None
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class SeperateAnimationOperator(bpy.types.Operator):
|
||||
bl_idname = "scene.seperate_anim"
|
||||
bl_label = "Separate Animation"
|
||||
bl_description = "Transform NLA strips back to Keyframes to make them editable again. Make sure to select all objects you want to transform back."
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
sel_objects = context.selected_objects
|
||||
for obj in sel_objects:
|
||||
if obj.animation_data is None:
|
||||
continue
|
||||
|
||||
if len(obj.animation_data.nla_tracks) == 0:
|
||||
continue
|
||||
|
||||
# set actions
|
||||
track = obj.animation_data.nla_tracks[0]
|
||||
action_name = track.strips[0].name
|
||||
action = bpy.data.actions.get(action_name)
|
||||
obj.animation_data.action = action
|
||||
|
||||
# remove track
|
||||
obj.animation_data.nla_tracks.remove(track)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RenameNLAAnimationOperator(bpy.types.Operator):
|
||||
bl_idname = "scene.rename_anim"
|
||||
bl_label = "Rename Animation"
|
||||
bl_description = "Rename NLA Tracks on selected objects"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
anim_name: bpy.props.StringProperty()
|
||||
index = 0
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
sel_objects = context.selected_objects
|
||||
for obj in sel_objects:
|
||||
track = obj.animation_data.nla_tracks[self.index]
|
||||
track.name = self.anim_name
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RenameActionOperator(bpy.types.Operator):
|
||||
bl_idname = "scene.rename_action"
|
||||
bl_label = "Rename Action"
|
||||
bl_description = "Rename Action on active object"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
action_name: bpy.props.StringProperty()
|
||||
index = 0
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
active_object = context.active_object
|
||||
active_object.animation_data.action.name = self.action_name
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def register():
|
||||
# Use actions instead of NLA Strips for Animation merging
|
||||
if bpy.app.version < (4, 4, 0):
|
||||
bpy.utils.register_class(JoinAnimationOperator)
|
||||
bpy.utils.register_class(SeperateAnimationOperator)
|
||||
bpy.utils.register_class(RenameNLAAnimationOperator)
|
||||
else:
|
||||
bpy.utils.register_class(RenameActionOperator)
|
||||
|
||||
|
||||
def unregister():
|
||||
# Use actions instead of NLA Strips for Animation merging
|
||||
if bpy.app.version < (4, 4, 0):
|
||||
bpy.utils.unregister_class(JoinAnimationOperator)
|
||||
bpy.utils.unregister_class(SeperateAnimationOperator)
|
||||
bpy.utils.unregister_class(RenameNLAAnimationOperator)
|
||||
else:
|
||||
bpy.utils.unregister_class(RenameActionOperator)
|
||||
@@ -0,0 +1,361 @@
|
||||
import bpy # type: ignore
|
||||
from pathlib import Path
|
||||
|
||||
from ..Functions import functions
|
||||
|
||||
|
||||
class GOVIE_open_export_folder_Operator(bpy.types.Operator):
|
||||
bl_idname = "scene.open_export_folder"
|
||||
bl_label = "Open Folder"
|
||||
bl_description = "Open current GLB folder. You may need to export first for the folder to be created."
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
# get folder of blend file
|
||||
blend_path = Path(bpy.data.filepath).parent
|
||||
|
||||
# get export settings
|
||||
glb_filename = context.scene.export_settings.glb_filename
|
||||
|
||||
if blend_path.joinpath(glb_filename).parent.exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
# get folder of blend file
|
||||
blend_path = Path(bpy.data.filepath).parent
|
||||
|
||||
# get export settings
|
||||
glb_filename = context.scene.export_settings.glb_filename
|
||||
|
||||
bpy.ops.wm.url_open(
|
||||
url="file://{}".format(
|
||||
str(blend_path.joinpath(glb_filename).parent.absolute())
|
||||
)
|
||||
)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GOVIE_Open_Link_Operator(bpy.types.Operator):
|
||||
bl_idname = "scene.open_link"
|
||||
bl_label = "Open Website"
|
||||
bl_description = "Go to GOVIE Website"
|
||||
|
||||
url: bpy.props.StringProperty(name="url")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.url_open(url=self.url)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GOVIE_Add_Property_Operator(bpy.types.Operator):
|
||||
"""Add the custom property on the current selected object"""
|
||||
|
||||
bl_idname = "object.add_property"
|
||||
bl_label = "Add custom Property"
|
||||
|
||||
property_type: bpy.props.StringProperty(name="custom_property_name")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.object is None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
selected_objects = context.selected_objects
|
||||
for obj in selected_objects:
|
||||
if self.property_type == "visibility":
|
||||
obj["visibility"] = 1
|
||||
# obj.visibility_bool = 1
|
||||
if self.property_type == "clickable":
|
||||
obj["clickablePart"] = "clickablePart"
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GOVIE_Remove_Property_Operator(bpy.types.Operator):
|
||||
"""Remove the custom property on the current selected object"""
|
||||
|
||||
bl_idname = "object.remove_property"
|
||||
bl_label = "Remove visibility Property"
|
||||
|
||||
property_type: bpy.props.StringProperty(name="custom_property_name")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.object is None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
selected_objects = context.selected_objects
|
||||
for obj in selected_objects:
|
||||
if self.property_type == "visibility":
|
||||
if "visibility" in obj.keys():
|
||||
del obj["visibility"]
|
||||
if self.property_type == "clickable":
|
||||
if "clickablePart" in obj.keys():
|
||||
del obj["clickablePart"]
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GOVIE_Quick_Export_GLB_Operator(bpy.types.Operator):
|
||||
bl_idname = "scene.gltf_quick_export"
|
||||
bl_label = "EXPORT_GLTF"
|
||||
bl_description = "Save Blend file first ! The GLB file will be saved to 'pathOfBlendFile/glb/filename.glb'"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if bpy.data.is_saved:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
# check spelling
|
||||
filename = context.scene.export_settings.glb_filename
|
||||
context.scene.export_settings.glb_filename = functions.convert_umlaut(filename)
|
||||
|
||||
# check annotation names
|
||||
functions.rename_annotation()
|
||||
|
||||
# blender file saved
|
||||
file_is_saved = bpy.data.is_saved
|
||||
|
||||
if not file_is_saved:
|
||||
self.report({"INFO"}, "You need to save the Blend file first!")
|
||||
return {"FINISHED"}
|
||||
|
||||
# get folder of blend file
|
||||
blend_path = Path(bpy.data.filepath).parent
|
||||
|
||||
# get export settings
|
||||
glb_filename = context.scene.export_settings.glb_filename
|
||||
glb_filepath = blend_path.joinpath(glb_filename)
|
||||
|
||||
if not glb_filepath.parent.exists():
|
||||
glb_filepath.parent.mkdir(parents=True)
|
||||
|
||||
preset_path = "operator/export_scene.gltf/"
|
||||
export_preset_name = context.scene.export_settings.export_preset
|
||||
preset_filepath = bpy.utils.preset_find(export_preset_name, preset_path)
|
||||
|
||||
gltf_export_param = {}
|
||||
|
||||
# read preset parameters from file
|
||||
if preset_filepath:
|
||||
|
||||
class Container(object):
|
||||
__slots__ = ("__dict__",)
|
||||
|
||||
op = Container()
|
||||
preset_file = open(preset_filepath, "r")
|
||||
|
||||
# storing the values from the preset on the class
|
||||
for line in preset_file.readlines()[3::]:
|
||||
exec(line, globals(), locals())
|
||||
|
||||
# pass class dictionary to the operator
|
||||
gltf_export_param = op.__dict__
|
||||
else:
|
||||
gltf_export_param["export_extras"] = True
|
||||
|
||||
join_objects = context.scene.export_settings.join_objects
|
||||
|
||||
gltf_export_param["filepath"] = str(glb_filepath.absolute())
|
||||
|
||||
# export glb
|
||||
if join_objects:
|
||||
functions.optimize_scene(gltf_export_param)
|
||||
else:
|
||||
bpy.ops.export_scene.gltf(**gltf_export_param)
|
||||
|
||||
# change glb dropdown entry
|
||||
# context.scene.glb_file_dropdown = context.scene.export_settings.glb_filename
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GOVIE_CleanupMesh_Operator(bpy.types.Operator):
|
||||
bl_idname = "object.cleanup_mesh"
|
||||
bl_label = "Delete Loose and Degenerate Dissolve"
|
||||
bl_description = "Mesh Cleanup -> Delete Loose and Degenerate Dissolve"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.mode == "OBJECT"
|
||||
|
||||
def execute(self, context):
|
||||
exclude_temp_list = []
|
||||
collections = bpy.context.view_layer.layer_collection.children
|
||||
|
||||
# switch on all layers but remember vis settings
|
||||
for collection in collections:
|
||||
exclude_temp_list.append(collection.exclude)
|
||||
collection.exclude = False
|
||||
|
||||
for obj in context.scene.objects:
|
||||
if obj.type == "MESH":
|
||||
functions.select_object(self, obj)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.mesh.delete_loose()
|
||||
bpy.ops.mesh.dissolve_degenerate()
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
# set back layer settings
|
||||
for collection, exclude_temp_value in zip(collections, exclude_temp_list):
|
||||
collection.exclude = exclude_temp_value
|
||||
|
||||
self.report({"INFO"}, "Meshes Cleaned !")
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GOVIE_CheckTexNodes_Operator(bpy.types.Operator):
|
||||
"""Check if there are any empty Texture Nodes in any Material and print that material"""
|
||||
|
||||
bl_idname = "object.check_tex_nodes"
|
||||
bl_label = "Check Empty Tex Nodes"
|
||||
|
||||
bpy.types.Scene.mat_name_list = []
|
||||
|
||||
def execute(self, context):
|
||||
mat_name_list = context.scene.mat_name_list
|
||||
mat_name_list.clear()
|
||||
|
||||
# get materials with texture nodes that have no image assigned
|
||||
for mat in bpy.data.materials:
|
||||
if mat.node_tree is None:
|
||||
continue
|
||||
for node in mat.node_tree.nodes:
|
||||
if node.type == "TEX_IMAGE" and node.image is None:
|
||||
mat_name_list.append(mat.name)
|
||||
self.report(
|
||||
{"INFO"},
|
||||
"Found empty image node in material {}".format(mat.name),
|
||||
)
|
||||
functions.select_object_by_mat(self, mat)
|
||||
|
||||
if len(mat_name_list) == 0:
|
||||
self.report({"INFO"}, "No Empty Image Nodes")
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GOVIE_Add_UV_Animation_Operator(bpy.types.Operator):
|
||||
"""Create UV Animation for selected object"""
|
||||
|
||||
bl_idname = "object.add_uv_anim"
|
||||
bl_label = "Add UV Animation"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.object is None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
active_object = context.active_object
|
||||
new_name = active_object.name + "_uv_anim_controller"
|
||||
|
||||
if bpy.data.objects.get(new_name):
|
||||
empty = bpy.data.objects[new_name]
|
||||
else:
|
||||
bpy.ops.object.empty_add(
|
||||
type="PLAIN_AXES", align="WORLD", location=(0, 0, 0), scale=(1, 1, 1)
|
||||
)
|
||||
empty = context.active_object
|
||||
empty.name = new_name
|
||||
|
||||
# add custom property
|
||||
empty["uvAnim"] = active_object.name
|
||||
|
||||
# save emtpy name in scene for later use
|
||||
context.scene["uv_anim_obj"] = empty.name
|
||||
|
||||
# add driver to material mapping node
|
||||
if active_object and active_object.active_material:
|
||||
material = active_object.active_material
|
||||
|
||||
# Find the Mapping node in the material's node tree
|
||||
mapping_node = None
|
||||
for node in material.node_tree.nodes:
|
||||
print(node.type)
|
||||
if node.type == "MAPPING":
|
||||
mapping_node = node
|
||||
break
|
||||
|
||||
if mapping_node:
|
||||
# remove driver fist if there is one
|
||||
mapping_node.inputs["Location"].driver_remove("default_value", 0)
|
||||
driverX = (
|
||||
mapping_node.inputs["Location"]
|
||||
.driver_add("default_value", 0)
|
||||
.driver
|
||||
)
|
||||
driverX.type = "SCRIPTED"
|
||||
driverX.expression = empty.name
|
||||
|
||||
# Add the Empty object as a variable target
|
||||
var = driverX.variables.new()
|
||||
var.name = empty.name
|
||||
var.type = "TRANSFORMS"
|
||||
var.targets[0].id = bpy.data.objects[empty.name]
|
||||
var.targets[0].transform_type = "LOC_X"
|
||||
|
||||
mapping_node.inputs["Location"].driver_remove("default_value", 1)
|
||||
driverY = (
|
||||
mapping_node.inputs["Location"]
|
||||
.driver_add("default_value", 1)
|
||||
.driver
|
||||
)
|
||||
driverY.type = "SCRIPTED"
|
||||
driverY.expression = "-" + empty.name
|
||||
|
||||
var = driverY.variables.new()
|
||||
var.name = empty.name
|
||||
var.type = "TRANSFORMS"
|
||||
var.targets[0].id = bpy.data.objects[empty.name]
|
||||
var.targets[0].transform_type = "LOC_Z"
|
||||
|
||||
else:
|
||||
print("Mapping node not found in the material's node tree.")
|
||||
else:
|
||||
print("Active object or active material not found.")
|
||||
|
||||
active_object.select_set(True)
|
||||
context.view_layer.objects.active = active_object
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(GOVIE_open_export_folder_Operator)
|
||||
bpy.utils.register_class(GOVIE_Open_Link_Operator)
|
||||
bpy.utils.register_class(GOVIE_Add_Property_Operator)
|
||||
bpy.utils.register_class(GOVIE_Remove_Property_Operator)
|
||||
bpy.utils.register_class(GOVIE_Quick_Export_GLB_Operator)
|
||||
bpy.utils.register_class(GOVIE_CleanupMesh_Operator)
|
||||
bpy.utils.register_class(GOVIE_CheckTexNodes_Operator)
|
||||
bpy.utils.register_class(GOVIE_Add_UV_Animation_Operator)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(GOVIE_open_export_folder_Operator)
|
||||
bpy.utils.unregister_class(GOVIE_Open_Link_Operator)
|
||||
bpy.utils.unregister_class(GOVIE_Add_Property_Operator)
|
||||
bpy.utils.unregister_class(GOVIE_Remove_Property_Operator)
|
||||
bpy.utils.unregister_class(GOVIE_Quick_Export_GLB_Operator)
|
||||
bpy.utils.unregister_class(GOVIE_CleanupMesh_Operator)
|
||||
bpy.utils.unregister_class(GOVIE_CheckTexNodes_Operator)
|
||||
bpy.utils.unregister_class(GOVIE_Add_UV_Animation_Operator)
|
||||
@@ -0,0 +1,53 @@
|
||||
from pathlib import Path
|
||||
|
||||
import bpy # type: ignore
|
||||
|
||||
from ..Functions import functions
|
||||
|
||||
|
||||
class GOVIE_Preview_Operator(bpy.types.Operator):
|
||||
bl_idname = "scene.open_web_preview"
|
||||
bl_label = "Open in Browser"
|
||||
bl_description = "Press export to display a preview of the exported file"
|
||||
|
||||
port = 8000
|
||||
url = (
|
||||
"https://3dit-tools.s3.eu-central-1.amazonaws.com/StaticGLBViewerV2/index.html"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
# get folder of blend file
|
||||
blend_path = Path(bpy.data.filepath).parent
|
||||
|
||||
# get export settings
|
||||
glb_filename = context.scene.export_settings.glb_filename
|
||||
if not glb_filename.endswith(".glb"):
|
||||
glb_filename = "{}.glb".format(glb_filename)
|
||||
|
||||
if blend_path.joinpath(glb_filename).exists():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
# get folder of blend file
|
||||
blend_path = Path(bpy.data.filepath).parent
|
||||
|
||||
# get export settings
|
||||
glb_filename = context.scene.export_settings.glb_filename
|
||||
if not glb_filename.endswith(".glb"):
|
||||
glb_filename = "{}.glb".format(glb_filename)
|
||||
|
||||
functions.start_server(blend_path.joinpath(glb_filename).absolute(), self.port)
|
||||
# run browser
|
||||
bpy.ops.wm.url_open(url=self.url)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(GOVIE_Preview_Operator)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(GOVIE_Preview_Operator)
|
||||
@@ -0,0 +1,43 @@
|
||||
import bpy # type: ignore
|
||||
|
||||
|
||||
class SimplifyKeyframes(bpy.types.Operator):
|
||||
bl_idname = "scene.simplify_keyframes"
|
||||
bl_label = "Simplify Keyframes"
|
||||
bl_description = "Simplify the Keyframes by the selected decimate ratio, higher values will reduce more keyframes"
|
||||
bl_options = {"REGISTER"}
|
||||
|
||||
decimate_ratio: bpy.props.FloatProperty()
|
||||
mode: bpy.props.StringProperty()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
display_button = False
|
||||
|
||||
# at least one object with animation on it
|
||||
sel_objects = context.selected_objects
|
||||
for obj in sel_objects:
|
||||
if obj.animation_data is not None:
|
||||
display_button = True
|
||||
|
||||
return display_button
|
||||
|
||||
def execute(self, context):
|
||||
context.area.type = "GRAPH_EDITOR"
|
||||
bpy.ops.graph.select_all(action="SELECT")
|
||||
bpy.ops.graph.decimate(
|
||||
mode=self.mode,
|
||||
factor=self.decimate_ratio,
|
||||
remove_error_margin=self.decimate_ratio,
|
||||
)
|
||||
context.area.type = "VIEW_3D"
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(SimplifyKeyframes)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(SimplifyKeyframes)
|
||||
Reference in New Issue
Block a user