1 Commits

Author SHA1 Message Date
raccoon
7ff21d3c98 Added Avatars settings and package 2024-12-16 00:54:51 +05:00
56 changed files with 43 additions and 643 deletions

View File

@@ -1 +0,0 @@

View File

@@ -1 +0,0 @@

View File

@@ -1 +0,0 @@

View File

@@ -1,72 +0,0 @@
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

@@ -1,288 +0,0 @@
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

@@ -1,91 +0,0 @@
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

@@ -1,113 +0,0 @@
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.

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,8 @@
{
"dependencies": {
"com.vrchat.avatars": {
"version": "3.x"
},
"com.vrchat.core.vpm-resolver": {
"version": "0.1.x"
}

View File

@@ -4,13 +4,13 @@
QualitySettings:
m_ObjectHideFlags: 0
serializedVersion: 5
m_CurrentQuality: 2
m_CurrentQuality: 3
m_QualitySettings:
- serializedVersion: 2
name: VRC Low
pixelLightCount: 4
shadows: 2
shadowResolution: 1
shadowResolution: 2
shadowProjection: 1
shadowCascades: 2
shadowDistance: 75
@@ -58,7 +58,7 @@ QualitySettings:
skinWeights: 4
textureQuality: 0
anisotropicTextures: 2
antiAliasing: 2
antiAliasing: 4
softParticles: 1
softVegetation: 1
realtimeReflectionProbes: 1
@@ -86,6 +86,43 @@ QualitySettings:
shadows: 2
shadowResolution: 3
shadowProjection: 1
shadowCascades: 2
shadowDistance: 75
shadowNearPlaneOffset: 2
shadowCascade2Split: 0.33333334
shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
shadowmaskMode: 0
skinWeights: 4
textureQuality: 0
anisotropicTextures: 2
antiAliasing: 4
softParticles: 1
softVegetation: 1
realtimeReflectionProbes: 1
billboardsFaceCameraPosition: 1
vSyncCount: 0
lodBias: 2
maximumLODLevel: 0
streamingMipmapsActive: 0
streamingMipmapsAddAllCameras: 1
streamingMipmapsMemoryBudget: 512
streamingMipmapsRenderersPerFrame: 512
streamingMipmapsMaxLevelReduction: 2
streamingMipmapsMaxFileIORequests: 1024
particleRaycastBudget: 4096
asyncUploadTimeSlice: 2
asyncUploadBufferSize: 128
asyncUploadPersistentBuffer: 1
resolutionScalingFixedDPIFactor: 1
customRenderPipeline: {fileID: 0}
excludedTargetPlatforms:
- Android
- serializedVersion: 2
name: VRC Ultra
pixelLightCount: 8
shadows: 2
shadowResolution: 3
shadowProjection: 1
shadowCascades: 4
shadowDistance: 150
shadowNearPlaneOffset: 2