chore: 支持 HintBox 类型的 R.py 资源文件生成

This commit is contained in:
XcantloadX 2025-02-04 23:14:07 +08:00
parent feb1dedb69
commit 6c1f7f50aa
3 changed files with 135 additions and 62 deletions

View File

@ -249,9 +249,11 @@ const EditToolBar: React.FC<EditToolBarProps> = ({
// 保存元数据
const metaFile = await directoryHandle.getFileHandle(`${name}.png.json`, { create: true });
const metaWritable = await metaFile.createWritable();
await metaWritable.write(JSON.stringify(imageMetaDataObject?.imageMetaData, null, 2));
await metaWritable.write(imageMetaDataObject?.toString(imageMetaDataObject.imageMetaData) || '');
await metaWritable.close();
// 清理并退出
imageMetaDataObject?.clear();
exitEditMode();
@ -502,9 +504,6 @@ const ScriptRecorder: React.FC = () => {
}, []);
const handleAnnotationChange = async (e: AnnotationChangedEvent) => {
if (e.type === 'add') {

View File

@ -2,7 +2,7 @@
####### 此文件为自动生成,请勿编辑 #######
####### AUTO GENERATED. DO NOT EDIT. #######
from kotonebot.backend.util import res_path
from kotonebot.backend.core import image
from kotonebot.backend.core import image, HintBox
{% macro render_class_attributes(class) -%}

View File

@ -16,9 +16,16 @@ PATH = '.\\res\\sprites'
SpriteType = Literal['basic', 'metadata']
@dataclass
class Sprite(DataClassJsonMixin):
class Resource:
type: Literal['template', 'hint-box']
data: 'Sprite | HintBox'
@dataclass
class Sprite:
"""表示一个精灵图像资源及其元数据。"""
type: SpriteType
uuid: str
@ -40,6 +47,18 @@ class Sprite(DataClassJsonMixin):
origin_file: str
"""原始图片的绝对路径"""
@dataclass
class HintBox:
"""表示一个提示框"""
name: str
display_name: str
class_path: list[str]
x1: float
y1: float
x2: float
y2: float
origin_file: str
@dataclass
class RectPoints(DataClassJsonMixin):
"""表示一个矩形的两个对角点坐标"""
@ -126,12 +145,19 @@ def scan_png_files(path: str) -> list[str]:
png_files.append(file_path)
return png_files
def load_and_copy_meta_data_sprite(root_path: str, png_file: str) -> list[Sprite]:
"""加载 metadata 类型的 sprite"""
def query_annotation(annotations: list[Annotation], id: str) -> Annotation:
for annotation in annotations:
if annotation.id == id:
return annotation
raise ValueError(f'Annotation not found: {id}')
def load_metadata(root_path: str, png_file: str) -> list[Resource]:
"""加载 metadata 类型的标注"""
json_path = png_file + '.json'
with open(json_path, 'r', encoding='utf-8') as f:
metadata = SpriteMetadata.from_json(f.read()) # 使用 dataclass-json 的解析方法
# 裁剪并保存图片
# 遍历标注,裁剪、保存图片
clips: dict[str, str] = {} # id -> 文件路径
image = cv2.imread(png_file)
for annotation in metadata.annotations:
@ -146,28 +172,45 @@ def load_and_copy_meta_data_sprite(root_path: str, png_file: str) -> list[Sprite
cv2.imwrite(path, clip)
clips[annotation.id] = path
# 关联 Definition创建 Sprite
sprites: list[Sprite] = []
resources: list[Resource] = []
for definition in metadata.definitions.values():
if definition.type != 'template':
continue
sprites.append(Sprite(
type='metadata',
uuid=definition.annotationId,
name=definition.name.split('.')[-1],
display_name=definition.displayName,
class_path=to_camel_cases(definition.name.split('.')[:-1]),
rel_path=png_file,
abs_path=os.path.abspath(clips[definition.annotationId]),
origin_file=os.path.abspath(png_file),
))
return sprites
if definition.type == 'template':
spr = Sprite(
type='metadata',
uuid=definition.annotationId,
name=definition.name.split('.')[-1],
display_name=definition.displayName,
class_path=to_camel_cases(definition.name.split('.')[:-1]),
def load_basic_sprite(root_path: str, png_file: str) -> Sprite:
rel_path=png_file,
abs_path=os.path.abspath(clips[definition.annotationId]),
origin_file=os.path.abspath(png_file),
)
resources.append(Resource('template', spr))
elif definition.type == 'hint-box':
annotation = query_annotation(metadata.annotations, definition.annotationId)
hb = HintBox(
name=definition.name.split('.')[-1],
display_name=definition.displayName,
class_path=to_camel_cases(definition.name.split('.')[:-1]),
x1=annotation.data.x1,
y1=annotation.data.y1,
x2=annotation.data.x2,
y2=annotation.data.y2,
origin_file=os.path.abspath(png_file),
)
resources.append(Resource('hint-box', hb))
else:
raise ValueError(f'Unknown definition type: {definition.type}')
return resources
def load_basic_sprite(root_path: str, png_file: str) -> Resource:
"""加载 basic 类型的 sprite"""
file_name = os.path.basename(png_file)
class_path = os.path.relpath(os.path.dirname(png_file), root_path).split(os.sep)
class_path = [to_camel_case(c) for c in class_path]
return Sprite(
spr = Sprite(
type='basic',
uuid=str(uuid.uuid4()),
name=to_camel_case(file_name.replace('.png', '')),
@ -177,25 +220,27 @@ def load_basic_sprite(root_path: str, png_file: str) -> Sprite:
abs_path=os.path.abspath(png_file),
origin_file=os.path.abspath(png_file)
)
return Resource('template', spr)
def load_sprites(root_path: str, png_files: list[str]) -> list[Sprite]:
def load_sprites(root_path: str, png_files: list[str]) -> list[Resource]:
""""""
sprites = []
resources = []
for file in png_files:
# 判断类型
json_path = file + '.json'
if os.path.exists(json_path):
sprites.extend(load_and_copy_meta_data_sprite(root_path, file))
resources.extend(load_metadata(root_path, file))
else:
# continue
sprites.append(load_basic_sprite(root_path, file))
return sprites
resources.append(load_basic_sprite(root_path, file))
return resources
def make_classes(sprites: list[Sprite], output_path: str) -> list[OutputClass]:
def make_classes(resources: list[Resource], output_path: str) -> list[OutputClass]:
"""根据 Sprite 数据生成 R.py 中的类信息。"""
# 按照 class_path 对 sprites 进行分组
class_map: dict[str, OutputClass] = {}
# 创建或获取指定路径的类
def get_or_create_class(path: list[str]) -> Union[OutputClass, None]:
if not path:
@ -213,11 +258,11 @@ def make_classes(sprites: list[Sprite], output_path: str) -> list[OutputClass]:
class_map[class_key] = new_class
return new_class
# 处理每个 sprite
for sprite in sprites:
# 处理每个 资源
for resource in resources:
# 获取当前 sprite 的完整路径
class_path = sprite.class_path
class_path = resource.data.class_path
# 创建或获取所有父类
current_class = None
for i in range(len(class_path)):
@ -236,40 +281,69 @@ def make_classes(sprites: list[Sprite], output_path: str) -> list[OutputClass]:
# 将 sprite 添加为最后一级类的属性
if current_class:
# 创建图片属性
docstring = (
f"名称:{sprite.display_name}\\n\n"
f"路径:{escape(sprite.rel_path)}\\n\n"
f"模块:`{'.'.join(sprite.class_path)}`\\n\n"
f'<img src="vscode-file://vscode-app/{escape(sprite.abs_path)}" title="{sprite.display_name}" />\\n\n'
)
if sprite.type == 'metadata':
docstring += (
f"原始文件:\\n\n"
f"<img src='vscode-file://vscode-app/{escape(sprite.origin_file)}' title='原始文件' width='80%' />"
if resource.type == 'template':
sprite = resource.data
assert isinstance(sprite, Sprite)
docstring = (
f"名称:{sprite.display_name}\\n\n"
f"路径:{escape(sprite.rel_path)}\\n\n"
f"模块:`{'.'.join(sprite.class_path)}`\\n\n"
f'<img src="vscode-file://vscode-app/{escape(sprite.abs_path)}" title="{sprite.display_name}" />\\n\n'
)
img_attr = ImageAttribute(
type='image',
name=sprite.name,
docstring=docstring,
value=f'image(res_path(r"{output_path}\\{sprite.uuid}.png"))'
)
current_class.attributes.append(img_attr)
if sprite.type == 'metadata':
docstring += (
f"原始文件:\\n\n"
f"<img src='vscode-file://vscode-app/{escape(sprite.origin_file)}' title='原始文件' width='80%' />"
)
img_attr = ImageAttribute(
type='image',
name=sprite.name,
docstring=docstring,
value=f'image(res_path(r"{output_path}\\{sprite.uuid}.png"))'
)
current_class.attributes.append(img_attr)
elif resource.type == 'hint-box':
hint_box = resource.data
assert isinstance(hint_box, HintBox)
docstring = (
f"名称:{hint_box.display_name}\\n\n"
f"模块:`{'.'.join(hint_box.class_path)}`\\n\n"
f"x1={hint_box.x1}, y1={hint_box.y1}, x2={hint_box.x2}, y2={hint_box.y2}\\n\n"
f"<img src='vscode-file://vscode-app/{escape(hint_box.origin_file)}' title='原始文件' width='80%' />"
)
img_attr = ImageAttribute(
type='image',
name=hint_box.name,
docstring=docstring,
value=(
f'HintBox(' +
f'x1={int(hint_box.x1)}, y1={int(hint_box.y1)}, '
f'x2={int(hint_box.x2)}, y2={int(hint_box.y2)}, '
f'source_resolution=(720, 1280))' # HACK: 硬编码分辨率
)
)
current_class.attributes.append(img_attr)
# 返回顶层类列表
return [cls for (path, cls) in class_map.items() if path.find('.') == -1]
def copy_sprites(sprites: list[Sprite], output_folder: str) -> list[Sprite]:
def copy_sprites(resources: list[Resource], output_folder: str) -> list[Resource]:
"""复制 sprites 图片到目标路径,并输出 R.py"""
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for sprite in sprites:
src_img_path = sprite.abs_path
img_name = sprite.uuid + '.png'
dst_img_path = os.path.join(output_folder, img_name)
shutil.copy(src_img_path, dst_img_path)
sprite.abs_path = os.path.abspath(dst_img_path)
return sprites
for resource in resources:
if resource.type == 'template':
spr = resource.data
assert isinstance(spr, Sprite)
src_img_path = spr.abs_path
img_name = spr.uuid + '.png'
dst_img_path = os.path.join(output_folder, img_name)
shutil.copy(src_img_path, dst_img_path)
spr.abs_path = os.path.abspath(dst_img_path)
return resources
def indent(text: str, indent: int = 4) -> str:
"""调整文本的缩进"""