chore: 支持 HintBox 类型的 R.py 资源文件生成
This commit is contained in:
parent
feb1dedb69
commit
6c1f7f50aa
|
@ -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') {
|
||||
|
||||
|
|
|
@ -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) -%}
|
||||
|
|
|
@ -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:
|
||||
"""调整文本的缩进"""
|
||||
|
|
Loading…
Reference in New Issue