Files
lightspeed64/fast64_internal/sm64/custom_cmd/operators.py
T
Lila 17a6155f5c [SM64] Custom level script and special commands and improvements to custom geo commands (#457)
* implement prop

* first iter of custom cmd exporting

* let rovert fly

* Update sm64_objects.py

* Update sm64_objects.py

* Update sm64_objects.py

* upgrade existing custom geo into new custom type

* Hack for geo because

* [SM64] Add depth arg and remove comma from existing cmds

* [SM64] add depth arg to level and collision objects

* change unused depth to _depth and add my stashed changes.. oops

* some code clean up and ui improvement

* Allow custom geo to have children

* command preset system (WIP)

allows you to create presets for your repo.
missing auto updates to presets

* detect changes via a hash

* fix last remaining bugs

* fix preset edit preview

* add draw layers

* basic example macro

* boolean support and fixes

* world's most over enginered way to handle numbers

* bugs and visual fixes

* make color serializable

* dont use PRESET_EDIT if presumably there is no edit data

* use degrees

* inital support for bones

* dont require transform to be inherented, fixes bone using scale arg

* remove sys, last few bits of code tomorrow

* move custom args into a folder

* lock

* typing fixes

* use dict for exports

* early binary support

* Missing dl ext and animated

* dl ext impl

* make is animatable work

* update updateBone to suit my new needs

* finish upgrade code

* dl ext now dl cmd, example macros for dl cmds

* SM64_CustomArgProperties

* enum property support to really hammer in the scope creep

* Nice move by SpongeBob! This match is just about over.

checked for mesh, removed one indent, changed Special to Collision

* some fixes to the ops

* make binary more flexiable by introducing eval expressions

Now behavior that would require a macro can be written in an eval statement, this is peak over engineering. It's also present in c under a toggle because SCALE GEO CMD COMPATABILITY!!!

* make hard defined int types for binary

* one last fix

* allow color quant for background node compat, fix enum ops

* impl last translate/rotate name in custom

* undo export color

* fix area root UI

* fix commas

* add order, fix up a couple of nits

* lila did a stupid

* fix transforms

* conventional rotation in macros is not scaled to s16

* let eval run on tuples

* implement generators

* add cast_integer

* Update properties.py

* round to conventional for scale

* document collection operator base more, fix bug with scale rounding

also add copy_on_add

* undo repo settings ver change since there has been no breaking changes

* remove unused

* implement top level level script commands

* make clean color into tuple, run eval on lists

* update base displaylist node

* little thing i noticed missing

* unnecessary names removed

* Fix transforms in level

* fix parameter

* show animatable toggle in presets

* More control of the level script section

* copy enum example

* over engineered description code

* update preset instead of changing to NONE

* fix number updates

* typo

* fix crash

* VERY IMPORTANT FIX

* link to docs

* add comment about updating existing animation pr ops

* fix animation command checks
2025-08-21 22:00:20 +01:00

183 lines
6.8 KiB
Python

from typing import TYPE_CHECKING, Iterable
from bpy.utils import register_class, unregister_class
from bpy.props import StringProperty, IntProperty, EnumProperty
from bpy.types import Context, Scene
from ...operators import OperatorBase, CollectionOperatorBase, SearchEnumOperatorBase
from ...utility import PluginError
from .utility import custom_cmd_preset_update, duplicate_name, get_custom_cmd_preset_enum, get_custom_prop
if TYPE_CHECKING:
from .properties import SM64_CustomCmdProperties, SM64_CustomArgProperties, SM64_CustomEnumProperties
def get_conf_type(context: Context):
custom = get_custom_prop(context).custom
return "PRESET_EDIT" if custom is None or custom.preset != "NONE" else "NO_PRESET"
class SM64_CustomCmdOps(CollectionOperatorBase):
bl_idname = "scene.sm64_custom_cmd_ops"
bl_label = ""
bl_options = {"UNDO"}
object_name = "custom command preset"
index: IntProperty(default=-1)
op_name: StringProperty()
example_name: StringProperty(default="")
@classmethod
def description(cls, context: Context, properties: dict) -> str:
op_name: str = properties.get("op_name", "")
if op_name == "COPY_EXAMPLE":
return "Copy example defines"
return super().description(context, properties)
@classmethod
def collection(cls, context: Context, op_values: dict) -> Iterable["SM64_CustomCmdProperties"]:
return context.scene.fast64.sm64.custom_cmds
def execute_operator(self, context):
presets = context.scene.fast64.sm64.custom_cmds
custom, owner = get_custom_prop(context)
conf_type = get_conf_type(context)
match self.op_name:
case "ADD":
presets.add()
new_preset: "SM64_CustomCmdProperties" = presets[-1]
old_preset: "SM64_CustomCmdProperties" | None = None
if self.index == -1:
if custom is not None:
old_preset = custom
else:
old_preset = presets[self.index]
if old_preset is not None:
new_preset.from_dict(old_preset.to_dict(conf_type, owner, include_defaults=True), set_defaults=True)
old_name = old_preset.name
else:
old_name = None
existing_names = {preset.name for preset in presets if preset != new_preset}
new_preset.name = duplicate_name(new_preset.name, existing_names, old_name)
new_preset.tab = True
if self.index != -1:
presets.move(len(presets) - 1, self.index + 1)
if custom is not None:
custom.preset = new_preset.name
for area in context.screen.areas: # HACK: redraw everything
area.tag_redraw()
case "REMOVE":
presets.remove(self.index)
case "COPY_EXAMPLE":
preset = presets[self.index] if custom is None else custom
context.window_manager.clipboard = preset.get_examples(owner, conf_type)[self.example_name][1]
case _:
raise NotImplementedError(f'Unimplemented internal custom command preset op "{self.op_name}"')
custom_cmd_preset_update(self, context)
def get_args(context: Context, command_index: int) -> Iterable["SM64_CustomArgProperties"]:
owner = get_custom_prop(context).owner
if isinstance(owner, Scene):
return context.scene.fast64.sm64.custom_cmds[command_index].args
elif owner is not None:
return owner.fast64.sm64.custom.args
else:
raise PluginError("Invalid context")
class SM64_CustomArgsOps(CollectionOperatorBase):
bl_idname = "scene.sm64_custom_args_ops"
bl_label = ""
bl_options = {"UNDO"}
object_name = "arg"
command_index: IntProperty(default=0) # for scene command presets
@classmethod
def collection(cls, context: Context, op_values: dict):
return get_args(context, op_values.get("command_index", 0))
@classmethod
def description(cls, context: Context, properties: dict) -> str:
op_name: str = properties.get("op_name", "")
if op_name == "COPY_EXAMPLE":
return "Copy example enum list"
return super().description(context, properties)
def add(self, context: Context, collection: Iterable["SM64_CustomArgProperties"]):
old, new = super().add(context, collection)
old_name = None
if old is not None:
old_name = old.name
new.from_dict(
old.to_dict(get_conf_type(context), owner=get_custom_prop(context).owner, include_defaults=True),
set_defaults=True,
)
existing_names = {arg.name for arg in collection if arg != new}
new.name = duplicate_name(new.name, existing_names, old_name)
def copy_example(self, context: Context, collection: Iterable["SM64_CustomArgProperties"]):
"""Copy example of enum list to clipboard"""
arg: "SM64_CustomArgProperties" = collection[self.index]
context.window_manager.clipboard = arg.get_enum_list_example()
def execute_operator(self, context: Context):
super().execute_operator(context)
custom_cmd_preset_update(self, context)
class SM64_CustomEnumOps(CollectionOperatorBase):
bl_idname = "scene.sm64_custom_enum_ops"
bl_label = ""
bl_options = {"UNDO"}
object_name = "enum option"
command_index: IntProperty(default=0) # for scene command presets
arg_index: IntProperty(default=0)
@classmethod
def collection(cls, context: Context, op_values: dict) -> Iterable["SM64_CustomEnumProperties"]:
args = get_args(context, op_values.get("command_index", 0))
return args[op_values.get("arg_index", 0)].enum_options
def add(self, context: Context, collection: Iterable["SM64_CustomArgProperties"]):
old, new = CollectionOperatorBase.add(self, context, collection)
old_name = None
if old is not None:
old_name = old.name
new.from_dict(old.to_dict())
existing_names = {enum.name for enum in collection if enum != new}
new.name = duplicate_name(new.name, existing_names, old_name)
class SM64_SearchCustomCmds(SearchEnumOperatorBase):
bl_idname = "scene.sm64_search_custom_cmds"
bl_label = "Search Custom Commands"
bl_options = {"REGISTER", "UNDO"}
bl_property = "preset"
preset: EnumProperty(items=get_custom_cmd_preset_enum)
def update_enum(self, context):
context.object.fast64.sm64.custom.preset = self.preset
classes = (
SM64_CustomEnumOps,
SM64_CustomArgsOps,
SM64_CustomCmdOps,
SM64_SearchCustomCmds,
)
def operators_register():
for cls in classes:
register_class(cls)
def operators_unregister():
for cls in reversed(classes):
unregister_class(cls)