-
Notifications
You must be signed in to change notification settings - Fork 0
/
pdo_import.py
169 lines (136 loc) · 7.86 KB
/
pdo_import.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
from posixpath import split
from bmesh.types import BMVert, BMesh
import bpy
import os
import struct
import bmesh
from bpy.types import MeshVertex
bl_info = {
"name": "Pepakura PDO import",
"blender": (2,90,0),
"category": "Import/Export",
"description": "Imports Pepakura 4 PDO files",
"author": "Coreforge"
}
class ImportPDO(bpy.types.Operator):
bl_idname = "import.pdo"
bl_label = "Import .PDO"
bl_description = "loads a pepakura 4 .pdo"
filepath: bpy.props.StringProperty(subtype="FILE_PATH")
def execute(self, context):
print("Import PDO called")
def decode(encoding, data):
if encoding == 0:
# *0x0e == 0 likely means utf-8 encoding
return data.decode(encoding="utf-8")
if encoding == 1:
# *0x0e == 1 likely means utf-16 encoding
return data.decode(encoding="utf-16")
def skipDataBlockV6(f):
#skips over the data Block in the header to the object count for format version 6
# the number of Strings following? (though there is one more and one with 0 length, but after 2 bytes, there is another length and string, so idk)
# ^ Wrong, seems to always be 3 Strings before weirdness
# ^ They probably aren't strings but just some binary data (doesn't really matter anyways, it just needs to be skipped, which works)
unknown_int_3 = int.from_bytes(f.read(4),byteorder='little')
for x in range(3):
data_len = int.from_bytes(f.read(4),byteorder='little')
f.read(data_len) # advance to after the data block and just discard it (I don't know what it's for)
print("Data Block %d length %d" % (x,data_len))
# skip another block
f.read(2) # advance by 2 bytes that contain some unknown data
data_len = int.from_bytes(f.read(4),byteorder='little')
f.read(data_len)
f.read(0x24) # skip another data block
def skipDataBlockV5(f):
f.read(4) # skip over an unknown 32bit integer
f.read(int.from_bytes(f.read(4),byteorder='little')) # read size of first block and read first block
f.read(int.from_bytes(f.read(4),byteorder='little')) # read size of second block and read second block
f.read(4) # skip another unknown 32bit integer
f.read(int.from_bytes(f.read(4),byteorder='little')) # read size of third block and read third block
f.read(0x22) # skip another data block
def skipEdgeBlock(f):
num_edges = int.from_bytes(f.read(4),byteorder='little') # might not be the amount of edges, but it seems to match
f.read(num_edges * 0x16) # I don't know how to interpret the data in the block, but the first 4 bytes are the amount of edges, and the following block
# is 0x16 bytes for every edge
with open(self.filepath,"rb") as f:
f.seek(0x0a) # skip "version 3" string at the beginning
# parse header
# get some values from the header
version = int.from_bytes(f.read(0x4),byteorder='little')
encoding = int.from_bytes(f.read(4),byteorder='little') # 0x0e, text encoding
second_unknown_int = int.from_bytes(f.read(4),byteorder='little') # 0x12
text_length = int.from_bytes(f.read(4),byteorder='little') # 0x16
if encoding == 2:
if version == 5:
self.report({"WARNING"},"File is weird. Header parsing is mostly based on fixed numbers, so high probability of it not working.")
f.read(0x12) # skip a bunch of uint32s and 2 additional bytes I don't know what they're for
f.read(int.from_bytes(f.read(4),byteorder='little')) # read size of data block and read the block
f.read(0x22) # skip another data block (I don't know what it's for, but it has the same size as the block in the "normal" v5 files)
else:
self.report({"ERROR"},"File is too weird. Please contact the author of the plugin.")
return {"CANCELLED"}
else:
print("PDO File version: %d" % version)
#print("second unknown int: %d" % second_unknown_int)
# vendor String? (either "Pepakura Designer 4" or "Pepakura Designer 3")
string_1 = decode(encoding,f.read(text_length))
print("first String: %s" % string_1)
if version == 6:
skipDataBlockV6(f)
else :
skipDataBlockV5(f)
print("nObjects offset: %s" % hex(f.tell()))
num_objects = int.from_bytes(f.read(4),byteorder='little')
print(num_objects)
for obj in range(num_objects):
# this block is there for every object in the file, so it has to be skipped for every object
print("block starts at offset: %s" % hex(f.tell() + 4))
f.read(int.from_bytes(f.read(4),byteorder='little')) # read size of another block and read that block
f.read(0x1) # there is another byte after that block I don't know the purpose of, but I don't think it matters
fname = os.path.basename(f.name)
# create the mesh, create an object, link the mesh to the object and link the object to the active collection of the current view layer
mesh = bpy.data.meshes.new(fname[:len(fname) - 4])
object = bpy.data.objects.new(fname[:len(fname) - 4],mesh)
bpy.context.view_layer.active_layer_collection.collection.objects.link(object)
# (currently hardcoded offset from the start of the file, needs to be changed once I figure out the block of data after the unfold data)
print("nVerts offset: %s" % hex(f.tell()))
num_verts = int.from_bytes(f.read(4),byteorder='little')
print(num_verts)
mesh.vertices.add(num_verts)
for x in range(num_verts):
mesh.vertices[x].co = (struct.unpack("d",f.read(8))[0],struct.unpack("d",f.read(8))[0],struct.unpack("d",f.read(8))[0])
print("nFaces offset: %s" % hex(f.tell()))
num_faces = int.from_bytes(f.read(4),byteorder='little')
print(num_faces)
bm = bmesh.new()
bm.from_mesh(mesh)
for x in range(num_faces):
f.read(0x28) # skip some per face data to the per vertex data
vertices = int.from_bytes(f.read(4),byteorder="little")
indicies = [] # declare list that holds the BMVerts for the current face
bm.verts.ensure_lookup_table()
for p in range(vertices):
indicies.append(bm.verts[int.from_bytes(f.read(4),byteorder="little")])
f.read(0x51)
bm.faces.ensure_lookup_table()
bm.faces.new(indicies)
bm.to_mesh(mesh)
bm.free()
print("End of face Data: %s" % hex(f.tell()))
skipEdgeBlock(f)
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
def menu_func(self,context):
self.layout.operator_context = 'INVOKE_DEFAULT'
self.layout.operator(ImportPDO.bl_idname,text="Pepakura 3/4 (.pdo)")
def register():
print("loaded")
bpy.utils.register_class(ImportPDO)
bpy.types.TOPBAR_MT_file_import.append(menu_func)
def unregister():
print("unloaded")
bpy.utils.unregister_class(ImportPDO)
if __name__ == "__main__":
register()