many text work and scripts

This commit is contained in:
sShemet
2025-12-08 20:28:56 +05:00
parent 37dca7ee15
commit c09c3db56c
697 changed files with 12308 additions and 9392 deletions

392
event_scene_import.py Normal file
View File

@@ -0,0 +1,392 @@
bl_info = {
"name": "Persona2 PSX Scene Importer",
"author": "Sergey Shemet",
"version": (1, 2),
"blender": (4, 5, 0),
"location": "File > Import",
"description": "Import Persona 2 Eternal Punishment PSX 3D scenes (.p2e)",
"category": "Import-Export",
}
import bpy
import struct
from bpy_extras.io_utils import ImportHelper
from bpy.props import FloatProperty, EnumProperty, BoolProperty
class ImportP2E(bpy.types.Operator, ImportHelper):
bl_idname = "import_scene.p2e"
bl_label = "Import Persona2 PSX Scene"
bl_description = "Import Persona 2 Eternal Punishment PSX 3D scenes"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
filename_ext = ".p2e"
filter_glob: bpy.props.StringProperty(
default="*.p2e",
options={'HIDDEN'},
maxlen=255,
)
coord_scale: FloatProperty(
name="Coordinate Scale",
description="PSX fixed-point scale multiplier",
default=0.01,
min=0.00001,
max=1.0,
step=0.01,
precision=5
)
scale_preset: EnumProperty(
name="PSX Scale Preset",
description="Standard PSX fixed-point scales",
items=[
('CUSTOM', "Custom", "Custom scale"),
('100', "1/100", "Life-like scale"),
('4096', "1/4096 (12.4)", "High precision"),
('256', "1/256 (8.8)", "Alternative scale"),
('16', "1/16 (12.4)", "Typical PSX vertex scale"),
('1', "1/1 (Raw)", "No scaling"),
],
default='100'
)
show_vertices: BoolProperty(
name="Show Vertices",
description="Show vertex points for debugging",
default=False
)
vertex_size: FloatProperty(
name="Vertex Size",
description="Size of debug vertex markers",
default=0.1,
min=0.001,
max=1.0
)
def draw(self, context):
layout = self.layout
box = layout.box()
box.label(text="PSX Import Settings", icon='SETTINGS')
box.prop(self, "scale_preset")
if self.scale_preset != 'CUSTOM':
preset_scales = {
'100': 1/100.0,
'4096': 1/4096.0,
'256': 1/256.0,
'16': 1/16.0,
'1': 1.0
}
self.coord_scale = preset_scales[self.scale_preset]
if self.scale_preset == 'CUSTOM':
box.prop(self, "coord_scale")
box.separator()
box.label(text="Debug Options", icon='MODIFIER')
box.prop(self, "show_vertices")
if self.show_vertices:
box.prop(self, "vertex_size")
box.separator()
box.label(text=f"Current scale: {self.coord_scale:.6f}", icon='INFO')
def execute(self, context):
# Читаем файл
vertices, objects_data = self.read_p2e_file(self.filepath)
if not vertices:
self.report({'ERROR'}, "No vertices found")
return {'CANCELLED'}
# Создаем меши для каждого объекта
self.create_objects_from_data(vertices, objects_data)
# Опционально показываем вершины для отладки
if self.show_vertices:
self.create_debug_vertices(vertices)
self.report({'INFO'}, f"Imported {len(vertices)} vertices, {len(objects_data)} objects")
return {'FINISHED'}
def read_p2e_file(self, filepath):
"""Чтение .p2e файла: вершины и полигоны объектов"""
try:
with open(filepath, 'rb') as f:
# Пропускаем заголовок (16 байт sourceName)
f.seek(0x10)
# Читаем заголовок сцены
data = f.read(12)
if len(data) < 12:
self.report({'ERROR'}, "File too small")
return [], []
objects_cnt, some_obj_ctr, vertex_ptr, shift_ptr = struct.unpack('<HHII', data)
# Вычисляем количество вершин
vertex_count = (shift_ptr - vertex_ptr) // 8
print(f"=== P2E File Info ===")
print(f"Objects: {objects_cnt}")
print(f"Vertices: {vertex_count}")
print(f"Vertex pointer: 0x{vertex_ptr:08X}")
print(f"Shift pointer: 0x{shift_ptr:08X}")
# 1. Читаем вершины
vertices = []
f.seek(vertex_ptr)
for i in range(vertex_count):
vertex_data = f.read(8)
if len(vertex_data) < 8:
break
x, y, z, d = struct.unpack('<4h', vertex_data)
# PSX координаты: X=X, Y=Z, Z=-Y
vertex = (
x * self.coord_scale,
z * self.coord_scale,
-y * self.coord_scale
)
vertices.append(vertex)
print(f"Read {len(vertices)} vertices")
# 2. Читаем объекты
objects_data = []
f.seek(0x10 + 12) # После заголовка сцены начинаются объекты
for obj_idx in range(objects_cnt):
# Читаем указатель на объект
obj_ptr_data = f.read(4)
if len(obj_ptr_data) < 4:
break
obj_ptr = struct.unpack('<I', obj_ptr_data)[0]
# Сохраняем текущую позицию
current_pos = f.tell()
# Переходим к данным объекта
f.seek(obj_ptr)
# Читаем gpuPacket заголовок
packet_data = f.read(8) # u16 globalChunkCnt, u16 localChunkCnt, u32 objectId
if len(packet_data) < 8:
f.seek(current_pos)
continue
global_chunk_cnt, local_chunk_cnt, object_id = struct.unpack('<HHI', packet_data)
print(f"\nObject {obj_idx}:")
print(f" Pointer: 0x{obj_ptr:08X}")
print(f" ID: {object_id}")
print(f" Chunks: {global_chunk_cnt}")
# Собираем полигоны этого объекта
obj_polygons = []
for chunk_idx in range(global_chunk_cnt):
# Читаем заголовок chunk
chunk_header = f.read(4)
if len(chunk_header) < 4:
break
cmd_cnt, chunk_size_words, header_fuck = struct.unpack('<BBH', chunk_header)
# print(f" Chunk {chunk_idx}: cmd={cmd_cnt}, size={chunk_size_words} words")
# Определяем тип полигона по cmd_cnt
poly_type = self.get_polygon_type(cmd_cnt)
if poly_type is None:
# Пропускаем неизвестный chunk
bytes_to_skip = chunk_size_words * 4 # chunk_size в словах (4 байта)
f.read(bytes_to_skip)
continue
# Читаем ВЕСЬ chunk (включая индексы вершин)
# chunk_size_words - количество 4-байтных слов данных ПОСЛЕ заголовка
chunk_data_size = chunk_size_words * 4
poly_data = f.read(chunk_data_size)
if len(poly_data) < chunk_data_size:
break
# Извлекаем индексы вершин из конца chunk
vertex_indices = self.extract_vertex_indices(poly_type, poly_data)
if vertex_indices:
obj_polygons.append({
'type': poly_type,
'cmd': cmd_cnt,
'vertices': vertex_indices,
'chunk_size': chunk_size_words
})
# Сохраняем данные объекта
if obj_polygons:
objects_data.append({
'id': object_id,
'ptr': obj_ptr,
'polygons': obj_polygons,
'vertex_count': len(obj_polygons) * 4 # Примерно
})
# Возвращаемся к списку объектов
f.seek(current_pos)
print(f"\n=== Summary ===")
print(f"Total objects with polygons: {len(objects_data)}")
return vertices, objects_data
except Exception as e:
self.report({'ERROR'}, f"Error reading file: {str(e)}")
import traceback
traceback.print_exc()
return [], []
def get_polygon_type(self, cmd_cnt):
"""Определяем тип полигона по команде"""
poly_types = {
7: 'shaded_textured_quad', # shadedTexturedPoly
6: 'textured_quad', # texturedPoly
5: 'shaded_quad', # shadedPoly
3: 'shaded_textured_tri', # shadedTextured3Poly
2: 'textured_tri', # textured3Poly
}
return poly_types.get(cmd_cnt, None)
def extract_vertex_indices(self, poly_type, poly_data):
"""Извлекаем индексы вершин из конца chunk данных"""
try:
if poly_type in ['shaded_textured_quad', 'textured_quad', 'shaded_quad']:
# Квады: 4 индекса uint32 в конце (16 байт)
if len(poly_data) >= 16:
# Последние 16 байт: vertexId1-4
v1, v2, v3, v4 = struct.unpack_from('<4I', poly_data, len(poly_data) - 16)
return [v1, v2, v4, v3]
elif poly_type in ['shaded_textured_tri', 'textured_tri']:
# Треугольники: 3 индекса uint32 в конце (12 байт)
if len(poly_data) >= 12:
# Последние 12 байт: vertexId1-3
v1, v2, v3 = struct.unpack_from('<3I', poly_data, len(poly_data) - 12)
return [v1, v2, v3]
except Exception as e:
print(f"Error extracting indices from {poly_type}: {e}")
return []
def create_objects_from_data(self, vertices, objects_data):
"""Создание отдельных мешей для каждого объекта"""
if not objects_data:
return
# Создаем коллекцию для импортированных объектов
collection_name = "PSX_Objects"
if collection_name not in bpy.data.collections:
collection = bpy.data.collections.new(collection_name)
bpy.context.scene.collection.children.link(collection)
else:
collection = bpy.data.collections[collection_name]
total_polygons = 0
successful_objects = 0
for obj_idx, obj_data in enumerate(objects_data):
# Собираем все полигоны этого объекта
all_faces = []
for poly in obj_data['polygons']:
vertex_ids = poly['vertices']
# Проверяем, что индексы в пределах массива вершин
valid_ids = []
for vid in vertex_ids:
if vid < len(vertices):
valid_ids.append(vid)
else:
print(f"Warning: Vertex index {vid} out of range (max {len(vertices)-1})")
if len(valid_ids) >= 3: # Нужно минимум 3 вершины для полигона
all_faces.append(valid_ids)
total_polygons += 1
if not all_faces:
print(f"Object {obj_idx} has no valid polygons")
continue
# Создаем меш
mesh_name = f"PSX_Obj_{obj_idx:03d}_ID{obj_data['id']}"
mesh = bpy.data.meshes.new(mesh_name)
# Создаем вершины и полигоны
mesh.from_pydata(vertices, [], all_faces)
# Обновляем меш
mesh.update()
# Создаем объект
obj = bpy.data.objects.new(mesh_name, mesh)
# Добавляем кастомные свойства
obj["psx_object_id"] = obj_data['id']
obj["psx_object_ptr"] = obj_data['ptr']
obj["psx_poly_count"] = len(all_faces)
obj["psx_poly_types"] = ','.join(str(p['cmd']) for p in obj_data['polygons'])
# Добавляем в коллекцию
collection.objects.link(obj)
successful_objects += 1
print(f" Created object {obj_idx} with {len(all_faces)} polygons")
print(f"\nCreated {successful_objects} objects with {total_polygons} total polygons")
def create_debug_vertices(self, vertices):
"""Создание мета-шаров для отладки вершин"""
if not vertices:
return
meta = bpy.data.metaballs.new("Debug_Vertices")
meta_obj = bpy.data.objects.new("PSX_Debug_Vertices", meta)
meta.resolution = 0.1
meta.threshold = 0.05
for i, vertex in enumerate(vertices):
elem = meta.elements.new()
elem.co = vertex
elem.radius = self.vertex_size
bpy.context.collection.objects.link(meta_obj)
print(f"Created {len(vertices)} debug vertices")
def menu_func_import(self, context):
self.layout.operator(ImportP2E.bl_idname, text="Persona2 PSX Scene (.p2e)")
classes = (ImportP2E,)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
if __name__ == "__main__":
register()