20 Commits

Author SHA1 Message Date
raccoon
36a4cd76b1 Added Apply Modifiers 2025-01-29 01:35:07 +05:00
raccoon
01f42bcce5 Cleanup 2025-01-29 00:32:34 +05:00
raccoon
47a346551c Yoink 2025-01-29 00:30:00 +05:00
raccoon
0fcc132eac Deform only armature 2025-01-28 14:47:12 +05:00
raccoon
acc87b4b09 Cleanup 2025-01-28 08:50:42 +05:00
raccoon
89d3c504e6 Order 2025-01-28 08:49:48 +05:00
raccoon
fb5a718243 Even more future-proof implementation 2025-01-28 07:31:52 +05:00
raccoon
8f6a83b86f More stuff 2025-01-28 07:22:11 +05:00
raccoon
0f37a5a02c Added binary exporter 2025-01-28 06:37:44 +05:00
raccoon
27c93c3e29 Future-proof implementation 2.0 2025-01-23 20:04:04 +05:00
raccoon
6c7f9e5493 Revert "Future-proof implementation"
This reverts commit 5a270289f0.
2025-01-20 04:06:20 +05:00
raccoon
5a270289f0 Future-proof implementation 2025-01-20 03:16:06 +05:00
raccoon
a01ccf03ce Export animation, but not default one 2025-01-20 01:45:02 +05:00
raccoon
c2e20e8aa7 Added all scripts 2025-01-11 22:25:32 +05:00
raccoon
9bc6821b6d Added Triplanar setup script 2024-12-25 18:24:47 +05:00
raccoon
821a60251c AMEND ME!!! 2024-12-16 01:29:38 +05:00
raccoon
b1690c7a57 Much less cluttered version of the script 2024-12-16 01:29:38 +05:00
raccoon
3fd6371608 Brand new export script with filtering 2024-12-16 01:29:37 +05:00
raccoon
957768b38a Updated export script 2024-12-16 01:29:33 +05:00
raccoon
255858a478 Added VRC project structure example 2024-12-16 01:29:28 +05:00
54 changed files with 640 additions and 0 deletions

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,72 @@
from math import pi
import bpy
from bpy.types import Context, Operator
def create_triplanar_setup(context: Context) -> None:
"""
Creates triplanar setup - a mesh cube with UVProject modifier and 6 UV projectors
:param context: Current context
:type context: bpy.types.Context
"""
bpy.ops.object.empty_add(type='CUBE', radius=1.6, location=(1.6, 1.6, 1.6), rotation=(0, 0, 0))
anchor_empty = context.active_object
anchor_empty.name = "UV_anchor"
bpy.ops.mesh.primitive_cube_add(radius=1, calc_uvs=True)
cube_obj = context.active_object
bpy.ops.object.modifier_add(type='UV_PROJECT')
uv_mod = cube_obj.modifiers["UVProject"]
rotations_list = [
("top", (0, 0, 0)),
("front", (pi/2, 0, 0)),
("back", (pi/2, 0, pi)),
("left", (pi/2, 0, pi/2)),
("right", (pi/2, 0, -pi/2)),
("bottom", (pi, 0, 0)),
]
uv_mod.projector_count = len(rotations_list)
for i, (name, rotation) in enumerate(rotations_list):
bpy.ops.object.empty_add(type='SINGLE_ARROW', radius=1.6, location=(0, 0, 0), rotation=rotation)
uv_projector = context.active_object
uv_projector.name = "UV_%s" % name
uv_projector.parent = anchor_empty
uv_mod.projectors[i].object = uv_projector
class MESH_OT_AddTriplanarSetup(Operator):
bl_idname = "mesh.setup_triplanar_add"
bl_label = "Triplanar UV Setup"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context: Context):
try:
create_triplanar_setup(context)
except Exception as e:
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
return {'FINISHED'}
def draw_triplanar_setup(self, context):
self.layout.operator(MESH_OT_AddTriplanarSetup.bl_idname, icon='MOD_UVPROJECT')
def register():
bpy.utils.register_class(MESH_OT_AddTriplanarSetup)
if draw_triplanar_setup.__name__ not in [f.__name__ for f in bpy.types.INFO_MT_mesh_add._dyn_ui_initialize()]:
bpy.types.INFO_MT_mesh_add.append(draw_triplanar_setup)
def unregister():
bpy.types.INFO_MT_mesh_add.remove(draw_triplanar_setup)
bpy.utils.unregister_class(MESH_OT_AddTriplanarSetup)
register()

View File

@@ -0,0 +1,288 @@
import os
from datetime import datetime
import bpy
_FUTURE = bpy.app.version >= (2, 80, 0)
from bpy.props import BoolProperty, EnumProperty, IntProperty, StringProperty
from bpy.types import Context, Operator, Panel, Scene, UIList
if _FUTURE:
from bpy.types import Collection as Group
else:
from bpy.types import Group as Group
def message_box(message: str, title: str = "Message", icon: str = 'INFO') -> None:
"""Shows modal window with a message
:param message: Message text
:param title: Window title
:param icon: Window icon
"""
def draw(self, context):
col = self.layout.column(align=True)
for line in message.strip().split("\n"):
col.label(text=line)
bpy.context.window_manager.popup_menu(draw, title=title, icon=icon)
def name_as_path(name: str) -> str:
"""Converts and sanitizes an arbitrary string to a canonical path with OS-specific separator
:param name: Some name
:returns: Canonical path
"""
return os.path.join(*[bpy.path.clean_name(n) for n in name.replace("\\", "/").split("/") if n]) + ".fbx"
def fbx_export(context: Context) -> None:
"""
Export all objects Groups to ASCII FBX
"""
print("[%s] Batch FBX export begin" % datetime.now())
settings_ascii = dict(
# Common
version='ASCII6100',
axis_forward='-Z',
axis_up='Y',
global_scale=1.0,
object_types=context.scene.fbx_object_types,
use_selection=True,
# Mesh
mesh_smooth_type='FACE',
use_mesh_edges=False,
use_mesh_modifiers=context.scene.fbx_use_modifiers,
use_tspace=False,
# Armature
add_leaf_bones=False,
armature_nodetype='NULL',
primary_bone_axis='Y',
secondary_bone_axis='X',
use_armature_deform_only=True,
# Animation
use_anim=context.scene.fbx_use_anim,
use_default_take=False,
# File
batch_mode='OFF',
path_mode='RELATIVE',
)
settings_binary = dict(
# Common
version='BIN7400',
axis_forward='-Z',
axis_up='Y',
apply_scale_options='FBX_SCALE_ALL',
apply_unit_scale=True,
bake_space_transform=True,
global_scale=1.0,
object_types=context.scene.fbx_object_types,
use_custom_props=True,
use_selection=True,
# Mesh
mesh_smooth_type='FACE',
use_mesh_edges=False,
use_mesh_modifiers=context.scene.fbx_use_modifiers,
use_mesh_modifiers_render=True,
use_tspace=False,
# Armature
add_leaf_bones=False,
armature_nodetype='NULL',
primary_bone_axis='Y',
secondary_bone_axis='X',
use_armature_deform_only=True,
# Animation
bake_anim=context.scene.fbx_use_anim,
bake_anim_force_startend_keying=True,
bake_anim_simplify_factor=1.0,
bake_anim_step=1.0,
bake_anim_use_all_actions=True,
bake_anim_use_all_bones=True,
bake_anim_use_nla_strips=True,
# File
batch_mode='OFF',
embed_textures=False,
path_mode='RELATIVE',
)
settings = settings_binary if _FUTURE or context.scene.fbx_force_binary else settings_ascii
if _FUTURE:
settings.pop('version')
rootpath = os.path.normpath(bpy.path.abspath(context.scene.fbx_export_dir))
groups = bpy.data.collections if _FUTURE else bpy.data.groups
override = context.copy()
for group in [g for g in groups if g.fbx_exportable]:
filepath = os.path.join(rootpath, name_as_path(group.name))
basepath = os.path.dirname(filepath)
if not os.path.exists(basepath):
os.makedirs(basepath)
override.update(
{
"selected_objects": group.objects
}
)
settings.update(
{
"filepath": filepath
}
)
if bpy.app.version >= (4, 0, 0):
with context.temp_override(**override):
bpy.ops.export_scene.fbx(**settings)
else:
bpy.ops.export_scene.fbx(override, **settings)
print("\n[%s] Batch FBX export end." % datetime.now())
class FBX_OT_ManualExport(Operator):
bl_idname = "fbx.manual_export"
bl_label = "Manual Export"
bl_description = "Export FBX manualy"
def execute(self, context):
try:
fbx_export(context)
except Exception as e:
self.report({'ERROR'}, str(e))
return {'CANCELLED'}
return {'FINISHED'}
class FBX_UL_ExportItems(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
row = layout.row(align=True)
row.prop(item, "fbx_exportable", icon='CHECKBOX_HLT' if item.fbx_exportable else 'CHECKBOX_DEHLT', text="", emboss=False)
row.prop(item, "name", text="", emboss=False, icon='GROUP')
class SCENE_PT_fbx_autoexport(Panel):
"""Creates a Panel in the scene context of the properties editor"""
bl_label = "FBX Auto-Export"
bl_idname = "SCENE_PT_fbx_autoexport"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"
def draw(self, context):
layout = self.layout
scene = context.scene
col = layout.column()
split = col.split()
sub = split.column()
sub.label(text="Filter groups:")
sub.template_list("FBX_UL_ExportItems", "", bpy.data, "collections" if _FUTURE else "groups", scene, "fbx_group_index", rows=5)
sub = split.column()
sub.label(text="Filter object types:")
sub.prop(scene, "fbx_object_types")
col.separator()
if _FUTURE:
col.use_property_split = True
col.use_property_decorate = False
col.prop(scene, "fbx_on_save")
col.prop(scene, "fbx_use_anim")
col.prop(scene, "fbx_use_modifiers")
if not _FUTURE:
col.prop(scene, "fbx_force_binary")
col.separator()
col.prop(scene, "fbx_export_dir")
col.operator(FBX_OT_ManualExport.bl_idname)
def on_save(*args, **kwargs) -> None:
"""
Runs when Blender file is being saved
"""
if not bpy.context.scene.fbx_on_save:
return
try:
fbx_export(bpy.context)
except Exception as e:
message_box(str(e), "Export error", 'ERROR')
classes = [
FBX_OT_ManualExport,
FBX_UL_ExportItems,
SCENE_PT_fbx_autoexport,
]
def register() -> None:
for cls in classes:
bpy.utils.register_class(cls)
Scene.fbx_export_dir = StringProperty(
name="FBX Export Dir",
description="FBX Export Directory",
subtype="DIR_PATH",
default="//../fbx/",
)
Scene.fbx_on_save = BoolProperty(
name="Export on save",
description="Export FBX on Blender file save",
default=False
)
Scene.fbx_object_types = EnumProperty(
name="Object Types",
options={'ENUM_FLAG'},
items=(
('EMPTY', "Empty", ""),
('CAMERA', "Camera", ""),
('LAMP', "Lamp", ""),
('ARMATURE', "Armature", "WARNING: not supported in dupli/group instances"),
('MESH', "Mesh", ""),
('OTHER', "Other", "Other geometry types, like curve, metaball, etc. (converted to meshes)"),
),
description="Which kind of object to export",
default={'ARMATURE', 'EMPTY', 'MESH', 'OTHER'},
)
Scene.fbx_group_index = IntProperty(default=0)
Scene.fbx_force_binary = BoolProperty(
name="Binary Format",
description="Force export FBX file as binary",
default=False
)
Scene.fbx_use_anim = BoolProperty(
name="Include Animation",
description="Export baked keyframe animation",
default=False
)
Scene.fbx_use_modifiers = BoolProperty(
name="Apply Modifiers",
description="Apply modifiers to mesh objects.\n** WARNING: Breaks exporting shape keys",
default=False,
)
Group.fbx_exportable = BoolProperty(
name="Export Group",
description="Mark group as FBX exportable",
default=False
)
bpy.app.handlers.save_pre.append(on_save)
def unregister() -> None:
bpy.app.handlers.save_pre.remove(on_save)
del Group.fbx_exportable
del Scene.fbx_use_modifiers
del Scene.fbx_use_anim
del Scene.fbx_force_binary
del Scene.fbx_group_index
del Scene.fbx_object_types
del Scene.fbx_on_save
del Scene.fbx_export_dir
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
register()

View File

@@ -0,0 +1,91 @@
import bpy
from bpy.props import FloatProperty
from bpy.types import Context, Operator, Panel
class ArmEditOperator:
@classmethod
def poll(cls, context: Context):
return (
context.active_object
and context.active_object.type == 'ARMATURE'
and context.active_bone
and context.mode == 'EDIT_ARMATURE'
)
class ARMATURE_OT_ReadBoneLength(ArmEditOperator, Operator):
"""Make length property equal to actual bone length"""
bl_idname = "armature.read_bone_length"
bl_label = "Read Bone Length"
bl_options = {'UNDO'}
def execute(self, context):
context.active_bone.bbone_y = context.active_bone.length
return {'FINISHED'}
class ARMATURE_OT_MakeBoneSquare(ArmEditOperator, Operator):
"""Make bone's width and depth equal to it's length"""
bl_idname = "armature.make_bone_square"
bl_label = "Make Bone Square"
bl_options = {'UNDO'}
def execute(self, context):
context.active_bone.bbone_x = context.active_bone.bbone_z = context.active_bone.length * 0.5
return {'FINISHED'}
class VIEW3D_PT_bone_size(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_label = "Bone Size"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context: Context):
scene = context.space_data
ob = context.active_object
return scene and ob and ob.type == 'ARMATURE' and ob.mode == 'EDIT'
def draw(self, context: Context):
layout = self.layout
col = layout.column()
col.prop(context.active_bone, "bbone_x", text="Bone Width")
col.prop(context.active_bone, "bbone_z", text="Bone Depth")
sub = col.row(align=True)
sub.operator(ARMATURE_OT_ReadBoneLength.bl_idname, icon="COPYDOWN", text="")
sub.prop(context.active_bone, "bbone_y", text="Bone Length")
col.operator(ARMATURE_OT_MakeBoneSquare.bl_idname, icon="MESH_PLANE")
def update_length(self, context) -> None:
context.active_bone.length = self.bbone_y
def register():
bpy.types.EditBone.bbone_y = FloatProperty(
name="B-Bone Display Y Length",
description="B-Bone Y size",
default=1.0,
min=0.0,
soft_min=0.001,
step=10,
precision=3,
options={'HIDDEN'},
update=update_length,
)
bpy.utils.register_class(ARMATURE_OT_ReadBoneLength)
bpy.utils.register_class(ARMATURE_OT_MakeBoneSquare)
bpy.utils.register_class(VIEW3D_PT_bone_size)
def unregister():
bpy.utils.unregister_class(VIEW3D_PT_bone_size)
bpy.utils.unregister_class(ARMATURE_OT_MakeBoneSquare)
bpy.utils.unregister_class(ARMATURE_OT_ReadBoneLength)
del bpy.types.EditBone.bbone_y
register()

View File

@@ -0,0 +1,113 @@
import math
import bpy
from bpy.props import EnumProperty, IntProperty
HU_TO_METERS_RATIO = 0.025 # 1hu = 2.5cm
GRID_SUBDIVISIONS = 8
class VIEW3D_OT_ResetGrid(bpy.types.Operator):
"""Reset grid settings to defaults"""
bl_idname = "screen.reset_grid"
bl_label = "Reset Grid"
bl_options = {'UNDO'}
def execute(self, context):
context.space_data.grid_lines = 16
context.space_data.grid_scale = 1.0
context.space_data.grid_subdivisions = 10
return {'FINISHED'}
class VIEW3D_PT_grid_level(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_label = "Grid Level"
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(context.scene, "grid_type", expand=True)
col = layout.column()
if context.scene.grid_type == 'HAMMER':
col.prop(context.scene, "grid_hu")
grid_size = pow(2, context.scene.grid_hu)
split = col.split(align=True)
col_l = split.column()
col_l.alignment = 'RIGHT'
col_l.label("Hammer Units:")
col_l.label("Meters:")
col_r = split.column()
col_r.label("%i hu" % grid_size)
col_r.label("%.3f m" % (grid_size * HU_TO_METERS_RATIO))
elif context.scene.grid_type == 'POWEROF2':
col.prop(context.scene, "grid_po2")
layout.operator(VIEW3D_OT_ResetGrid.bl_idname)
def update_grid_hu(self, context) -> None:
grid_size = pow(2, self.grid_hu)
context.space_data.grid_lines = max(3, math.ceil(1024 / grid_size))
context.space_data.grid_scale = grid_size * HU_TO_METERS_RATIO
context.space_data.grid_subdivisions = GRID_SUBDIVISIONS
def update_grid_po2(self, context) -> None:
context.space_data.grid_lines = max(3, pow(2, self.grid_po2 + 5))
context.space_data.grid_scale = 1 / pow(2, self.grid_po2)
context.space_data.grid_subdivisions = GRID_SUBDIVISIONS
def update_type(self, context) -> None:
if self.grid_type == 'HAMMER':
update_grid_hu(self, context)
elif self.grid_type == 'POWEROF2':
update_grid_po2(self, context)
def register():
bpy.types.Scene.grid_type = EnumProperty(
items=(
('HAMMER', "Hammer Units", "Main basic settings"),
('POWEROF2', "Power Of 2", "Armature-related settings"),
),
name="Grid type",
description="",
update=update_type,
)
bpy.types.Scene.grid_hu = IntProperty(
name="Grid level",
description="Hammer Units grid level",
min=0,
max=9,
default=6,
update=update_grid_hu,
)
bpy.types.Scene.grid_po2 = IntProperty(
name="Grid divisions",
description="Grid divisions in power of 2",
min=-4,
max=4,
default=0,
update=update_grid_po2,
)
bpy.utils.register_class(VIEW3D_OT_ResetGrid)
bpy.utils.register_class(VIEW3D_PT_grid_level)
def unregister():
bpy.utils.unregister_class(VIEW3D_PT_grid_level)
bpy.utils.register_class(VIEW3D_OT_ResetGrid)
del bpy.types.Scene.grid_po2
del bpy.types.Scene.grid_hu
del bpy.types.Scene.grid_type
register()

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.