Skip to content

Commit 0e079ff

Browse files
author
Leo Schurrer
committed
init
0 parents  commit 0e079ff

File tree

6 files changed

+305
-0
lines changed

6 files changed

+305
-0
lines changed

.gitmodules

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[submodule "kaitai_struct_python_runtime"]
2+
path = kaitai_struct_python_runtime
3+
url = https://github.com/kaitai-io/kaitai_struct_python_runtime.git
4+
branch = master
5+
[submodule "kaitai_lithtech_dat_struct"]
6+
path = kaitai_lithtech_dat_struct
7+
url = .\\kaitai_lithtech_dat_struct\\
8+
branch = main

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Blender Lithtech DAT Importer
2+
3+
This is a blender addon to import Lithtech \*.dat (DAT) map files. The plugin is developed with Blender v(3.4.1) and works for Lithtech DAT files with version 85.
4+
The Plugin is developed and tested for Combat Arms maps, but should work with every Lithtech game, provided that the version matches.
5+
6+
## Installation
7+
8+
1. Download the latest zip archive from the release page or clone this repository and zip the entire folder. When cloning, don't forget to clone recursive to include the required submodules.
9+
```
10+
git clone --recursive
11+
```
12+
2. In Blender navigate to: `Edit -> Preferences -> Add-ons -> Install`
13+
3. Select the created \*.zip file
14+
4. Enable the Plugin with the check-box
15+
16+
## Usage
17+
18+
After installing you can import the file in Blender with `File -> Import -> Import Lithtech DAT map`.
19+
20+
Alternatively with python:
21+
22+
```py
23+
bpy.ops.import_scene.lithtech_dat(filepath)
24+
```
25+
26+
Or over the Blender quick search with F3 `Import Lithtech Dat map`
27+
28+
## Limitation
29+
30+
- Textures are currently not supported
31+
- WorldObjects are also not supported
32+
- The RenderNodes are created in a rather basic way

__init__.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import bpy
2+
import bmesh
3+
from .lithtech_dat_import import ImportLithtechDat
4+
from .kaitai_struct_python_runtime import kaitaistruct
5+
from mathutils import Vector
6+
7+
bl_info = {
8+
"name": "Lithtech DAT Map Format (.dat)",
9+
"author": "leos",
10+
"description": "This plugins allows you to import Lithtech Map files.",
11+
"blender": (3, 4, 1),
12+
"version": (0, 0, 1),
13+
"location": "File > Import > Lithtech DAT",
14+
"warning": "",
15+
"category": "Import-Export"
16+
}
17+
18+
# Register and add to the "file selector" menu (required to use F3 search)
19+
20+
21+
def menu_func_import(self, context):
22+
self.layout.operator(ImportLithtechDat.bl_idname,
23+
text="Import Lithtech DAT map (.dat)")
24+
25+
26+
def register():
27+
bpy.utils.register_class(ImportLithtechDat)
28+
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
29+
30+
31+
def unregister():
32+
bpy.utils.unregister_class(ImportLithtechDat)
33+
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
34+
35+
36+
if __name__ == "__main__":
37+
register()
38+
39+
# test call
40+
bpy.ops.import_scene.lithtech_dat('INVOKE_DEFAULT')

kaitai_lithtech_dat_struct

Submodule kaitai_lithtech_dat_struct added at 2ee0613

kaitai_struct_python_runtime

lithtech_dat_import.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import bpy
2+
import bmesh
3+
import numpy
4+
from os.path import splitext, basename
5+
from bpy_extras.io_utils import ImportHelper
6+
from bpy.props import StringProperty, BoolProperty, EnumProperty
7+
from bpy.types import Operator
8+
from .kaitai_lithtech_dat_struct import LithtechDat
9+
from mathutils import Vector
10+
11+
12+
def createRenderNode(parent, rn_name, rn):
13+
"""Create a collection for the render node and all objects within
14+
Positional arguments:
15+
parent: parent collection the node gets appended to
16+
rn_name: name for the render node collection
17+
rn: the data for the render node from type LithtechDat.RenderNode
18+
"""
19+
# renderNode
20+
renderNode_collection = bpy.data.collections.new(rn_name)
21+
#
22+
# render nodes can no vertices
23+
if 0 < rn.num_vertices:
24+
#
25+
vertices = [[*map(lambda x: x*0.01, [v.v_pos.x, v.v_pos.z, v.v_pos.y])]
26+
for v in rn.vertices]
27+
triangles = [tri.t for tri in rn.triangles]
28+
tri_counts = [s.triangle_count for s in rn.sections]
29+
#
30+
for i, s in enumerate(rn.sections):
31+
t_from = sum(tri_counts[:i])
32+
t_till = t_from + tri_counts[i]
33+
#
34+
# this steps undercuts the polyIndex
35+
section_tris = [t.t for t in rn.triangles[t_from:t_till]]
36+
# dont forget about polyIndices
37+
section_tris_poly_indices = [
38+
t.poly_index for t in rn.triangles[t_from:t_till]]
39+
# preparation for next step
40+
section_tris = numpy.asarray(section_tris).reshape(-1)
41+
section_vert_indices = numpy.unique(section_tris)
42+
# find for each triangle the new correct indices to the separated vertices
43+
section_tris = numpy.asarray(
44+
[(numpy.where(section_vert_indices == t))[0][0] for t in section_tris])
45+
# reshape back to triangle
46+
section_tris = section_tris.reshape(
47+
len(section_tris) // 3, 3).tolist()
48+
#
49+
# now we separate the verticies from the render node to the section
50+
# one bmesh for each section
51+
bm = bmesh.new()
52+
for i in section_vert_indices:
53+
bm.verts.new([
54+
rn.vertices[i].v_pos.x * 0.01,
55+
rn.vertices[i].v_pos.z * 0.01,
56+
rn.vertices[i].v_pos.y * 0.01
57+
])
58+
continue
59+
bm.verts.ensure_lookup_table()
60+
# for debugging (does the same thing)
61+
# section_vertices = [[rn.vertices[i].v_pos.x * 0.01,rn.vertices[i].v_pos.z * 0.01,rn.vertices[i].v_pos.y * 0.01] for j in section_vert_indices]
62+
# create the faces from triangles
63+
for t, poly_index in zip(section_tris, section_tris_poly_indices):
64+
verts = [bm.verts[i] for i in t]
65+
# face can already exist
66+
try:
67+
face = bm.faces.new(verts)
68+
bm.faces.ensure_lookup_table()
69+
face.material_index = poly_index
70+
except:
71+
pass
72+
continue
73+
#
74+
# still missing the texture from the section data can be empty if StrWithLen2 has length 0
75+
# texture_name0 = s.texture_name[0].data
76+
# texture_name1 = s.texture_name[1].data
77+
#
78+
m = bpy.data.meshes.new(f"{rn_name}_Section_{i:04}")
79+
bm.to_mesh(m)
80+
o = bpy.data.objects.new(f"{rn_name}_Object_{i:04}", m)
81+
renderNode_collection.objects.link(o)
82+
continue
83+
pass
84+
# no vertices available only create empty object
85+
else:
86+
o = bpy.data.objects.new(f"{rn_name}_Object", None)
87+
o.empty_display_type = 'CUBE'
88+
o.location = Vector((rn.v_center.x, rn.v_center.z, rn.v_center.y))
89+
o.empty_display_size = max(
90+
[rn.v_half_dims.x, rn.v_half_dims.z, rn.v_half_dims.y])
91+
renderNode_collection.objects.link(o)
92+
pass
93+
parent.children.link(renderNode_collection)
94+
return
95+
96+
# requires createRenderNode
97+
98+
99+
def createWMRenderNode(parent, wmrn):
100+
"""create a World Model Render Node
101+
Positional Arguments:
102+
parent: collection the render node is going to be append to
103+
wmrn: dat_importer.lithtech_dat.LithtechDat.RenderNode node to be created
104+
"""
105+
node_collection = bpy.data.collections.new(f"WMRN_{wmrn.name.data}")
106+
for i, render_node in enumerate(wmrn.render_nodes):
107+
createRenderNode(
108+
node_collection, f"{wmrn.name.data}_RN_{i}", render_node)
109+
continue
110+
parent.children.link(node_collection)
111+
return
112+
113+
# requires createRenderNode
114+
115+
116+
def createRenderNodes(parent, world):
117+
"""create Render Nodes
118+
Positional Arguments:
119+
parent: collection the render node group is append to
120+
world: dat_importer.lithtech_dat.LithtechDat imported map file
121+
"""
122+
render_nodes = bpy.data.collections.new("RenderNodes")
123+
for i, render_node in enumerate(world.render_data.render_nodes):
124+
createRenderNode(render_nodes, f"RenderNode_{i:03}", render_node)
125+
continue
126+
parent.children.link(render_nodes)
127+
return
128+
129+
# requires createRenderNode
130+
# requires createWMRenderNode
131+
132+
133+
def createWMRenderNodes(parent, world):
134+
"""Create the World Model Render Nodes
135+
Positional Arguments:
136+
parent: the parent collection the world model render node group is append to
137+
world: dat_importer.lithtech_dat.LithtechDat imported map file
138+
"""
139+
wm_render_nodes = bpy.data.collections.new("WMRenderNodes")
140+
for node in world.render_data.world_model_render_nodes:
141+
createWMRenderNode(wm_render_nodes, node)
142+
continue
143+
parent.children.link(wm_render_nodes)
144+
return
145+
146+
147+
def createPhysicsData(parent, world):
148+
"""Create the physics/ collision data
149+
Positional Arguments:
150+
parent: collection the data physics collection is append to
151+
world: dat_importer.lithtech_dat.LithtechDat imported map file
152+
"""
153+
physics = vertigo.collision_data.polygons
154+
poly_vertices = []
155+
poly_triangles = []
156+
index = 0
157+
scale = 0.01
158+
for poly in physics:
159+
poly_triangles.append([])
160+
for vec in poly.vertices_pos:
161+
poly_vertices.append([vec.x * scale, vec.z * scale, vec.y * scale])
162+
poly_triangles[-1].append(index)
163+
index += 1
164+
continue
165+
continue
166+
m = bpy.data.meshes.new("CollisionData")
167+
m.from_pydata(poly_vertices, [], poly_triangles)
168+
o = bpy.data.objects.new("CollisionData", m)
169+
parent.objects.link(o)
170+
return
171+
172+
173+
def read_some_data(C, filepath, use_some_setting):
174+
print("running read_some_data...")
175+
dat = LithtechDat.from_file(filepath)
176+
name = basename(splitext(filepath)[0])
177+
map = bpy.data.collections.new(name)
178+
createRenderNodes(map, dat)
179+
createWMRenderNodes(map, dat)
180+
C.collection.children.link(map)
181+
182+
return {'FINISHED'}
183+
184+
185+
class ImportLithtechDat(Operator, ImportHelper):
186+
"""Import a Lithtech DAT map file
187+
This importer is developed and tested for Combat Arms maps.
188+
It should work for all Lithtech engine map files, but no guarantees!
189+
If you encounter any issue feel free to open a issue on GitHub.
190+
"""
191+
# # important since its how bpy.ops.import_scene.lithtech_dat is constructed
192+
bl_idname = "import_scene.lithtech_dat"
193+
bl_label = "Import Lithtech DAT map (.dat)"
194+
195+
# ImportHelper mixin class uses this
196+
filename_ext = ".dat"
197+
198+
filter_glob: StringProperty(
199+
default="*.dat",
200+
options={'HIDDEN'},
201+
maxlen=255, # Max internal buffer length, longer would be clamped.
202+
)
203+
204+
# List of operator properties, the attributes will be assigned
205+
# to the class instance from the operator settings before calling.
206+
use_setting: BoolProperty(
207+
name="Example Boolean",
208+
description="Example Tooltip",
209+
default=True,
210+
)
211+
212+
type: EnumProperty(
213+
name="Example Enum",
214+
description="Choose between two items",
215+
items=(
216+
('OPT_A', "First Option", "Description one"),
217+
('OPT_B', "Second Option", "Description two"),
218+
),
219+
default='OPT_A',
220+
)
221+
222+
def execute(self, context):
223+
return read_some_data(context, self.filepath, self.use_setting)

0 commit comments

Comments
 (0)