3
3
"""
4
4
import math
5
5
import time
6
+ import typing
6
7
from pathlib import Path
7
8
8
9
import bmesh
10
+ from bmesh .types import BMLayerItem
9
11
import bpy
12
+ from bpy .types import Material , Mesh
10
13
from mathutils import Euler , Vector
11
14
12
15
try :
@@ -26,20 +29,33 @@ def convert_uv(vertex:Vector,
26
29
v :Vector = vertex - origin
27
30
return Vector ((v .dot (texture_u ),v .dot (texture_v )))+ pan
28
31
32
+ def find_material (name :str )-> Material | None :
33
+ """
34
+ Case insensitive material search in Blender file.
35
+ It returns the first match in cases of case collisions.
36
+ """
37
+ for m in bpy .data .materials :
38
+ if name .lower ()== m .name .lower ():
39
+ return m
40
+ return None
41
+
29
42
def material_index_by_name (obj ,matname :str )-> int :
30
43
"""
31
44
Get material index using material name in object.
32
45
If material is not found on object, return 0.
33
46
"""
34
- mat_dict = {mat .name : i for i , mat in enumerate (obj .data .materials )}
47
+ mat_dict : dict [ str , int ] = {mat .name : i for i , mat in enumerate (obj .data .materials )}
35
48
try :
36
- ret = mat_dict [matname ]
49
+ ret : int = mat_dict [matname ]
37
50
return ret
38
51
except KeyError :
39
52
return 0
40
53
41
- def create_object (collection :bpy .types .Collection ,b :t3d .Brush )-> bpy .types .Object :
54
+ def create_object (collection :bpy .types .Collection ,b :t3d .Brush )-> tuple [ bpy .types .Object , set [ str ]] :
42
55
""" Create blender object from t3d.Brush. """
56
+ # Keep track of missing materials for this object.
57
+ missing_materials :set [str ]= set ()
58
+ # Create mesh.
43
59
m :bpy .types .Mesh = bpy .data .meshes .new (b .actor_name )
44
60
m .from_pydata (* b .get_pydata ())
45
61
# Create object.
@@ -52,9 +68,9 @@ def create_object(collection:bpy.types.Collection,b:t3d.Brush)->bpy.types.Object
52
68
mainscale = Vector (b .mainscale or (1 ,1 ,1 ))
53
69
pivot = Vector (b .prepivot or (0 ,0 ,0 ))
54
70
postscale = Vector (b .postscale or (1 ,1 ,1 ))
55
- rotation = Vector (b .rotation or (0 ,0 ,0 ))* math .tau / 65536
71
+ rotation : Vector | Euler = Vector (b .rotation or (0 ,0 ,0 ))* math .tau / 65536
56
72
rotation .xy = - rotation .xy
57
- rotation = Euler (rotation )
73
+ rotation = Euler (rotation . to_tuple () )
58
74
59
75
pivot .rotate (rotation )
60
76
pivot *= postscale * mainscale
@@ -68,28 +84,35 @@ def create_object(collection:bpy.types.Collection,b:t3d.Brush)->bpy.types.Object
68
84
bm :bmesh .types .BMesh = bmesh .new ()
69
85
bm .from_mesh (m )
70
86
# Create UV layer.
71
- #uv_map=o.data.uv_layers.new(name='uvmap')
72
- uv_layer = bm .loops .layers .uv .verify ()
87
+ uv_layer :bmesh .types .BMLayerItem = bm .loops .layers .uv .verify ()
73
88
# Polygon attributes.
74
- texture_names = [p .texture for p in b .polygons ]
75
- flags = [p .flags for p in b .polygons ]
76
- layer_texture = bm .faces .layers .string .get ("texture" ) or bm .faces .layers .string .new ("texture" )
77
- layer_flags = bm .faces .layers .int .get ("flags" ) or bm .faces .layers .int .new ("flags" )
89
+ texture_names :list [str ]= [p .texture for p in b .polygons ]
90
+ flags :list [int ]= [p .flags for p in b .polygons ]
91
+ layer_texture :BMLayerItem [bytes ]= bm .faces .layers .string .get ("texture" ) or bm .faces .layers .string .new ("texture" )
92
+ layer_flags :BMLayerItem [int ]= bm .faces .layers .int .get ("flags" ) or bm .faces .layers .int .new ("flags" )
93
+ i :int
94
+ face :bmesh .types .BMFace
78
95
for i ,face in enumerate (bm .faces ):
79
96
if texture_names [i ]:
80
97
face [layer_texture ]= bytes (str (texture_names [i ]),'utf-8' )
81
- # Note: material names are case sensitive in Blender.
82
- scene_mat = bpy .data .materials .get (texture_names [i ])
83
- # Add material to object if it's not there yet.
84
- if scene_mat and not texture_names [i ] in o .data .materials :
85
- o .data .materials .append (scene_mat )
86
- # Assign the face.
87
- face .material_index = material_index_by_name (o ,texture_names [i ])
98
+ # Note: material names are case sensitive in Blender but
99
+ # not in UnrealEd.
100
+ scene_mat :Material | None = find_material (texture_names [i ])
101
+ if scene_mat :
102
+ object_mesh_data :Mesh = typing .cast (Mesh ,o .data )
103
+ # Add material to object if it's not there yet.
104
+ if not scene_mat .name in object_mesh_data .materials :
105
+ object_mesh_data .materials .append (scene_mat )
106
+ # Assign the face.
107
+ face .material_index = material_index_by_name (o ,scene_mat .name )
108
+ else :
109
+ # Missing material.
110
+ missing_materials .add (texture_names [i ])
88
111
face [layer_flags ]= flags [i ]
89
112
# UV coordinates.
90
- poly = b .polygons [i ]
113
+ poly : t3d . Polygon = b .polygons [i ]
91
114
for loop in face .loops :
92
- vert = loop .vert .co
115
+ vert : Vector = loop .vert .co
93
116
tu = Vector (poly .u )
94
117
tv = Vector (poly .v )
95
118
origin = Vector (poly .origin )
@@ -104,7 +127,6 @@ def create_object(collection:bpy.types.Collection,b:t3d.Brush)->bpy.types.Object
104
127
collection .objects .link (o )
105
128
# PostScale requires applying previous transforms.
106
129
if b .postscale :
107
- #print("Postscale ",b.postscale)
108
130
o .select_set (True )
109
131
bpy .context .view_layer .objects .active = o
110
132
bpy .ops .object .transform_apply (scale = True ,rotation = True ,location = False )
@@ -115,36 +137,44 @@ def create_object(collection:bpy.types.Collection,b:t3d.Brush)->bpy.types.Object
115
137
o ["group" ]= b .group
116
138
o ["polyflags" ]= b .polyflags
117
139
118
- return o
140
+ return o , missing_materials
119
141
120
142
def import_t3d_file (
121
143
context :bpy .types .Context ,
122
144
filepath :str ,
123
145
snap_vertices :bool ,
124
146
snap_distance :float ,
125
- flip :bool ,
126
- )-> None :
147
+ flip :bool
148
+ )-> dict [ str , list [ str ]] :
127
149
""" Import T3D file into scene. """
128
-
150
+ # Missing materials that will be reported.
151
+ missing_materials :set [str ]= set ()
129
152
# Parse T3D file.
130
153
brushes :list [t3d .Brush ]= t3d_parser .t3d_open (filepath )
131
154
time_start :float = time .time ()
132
155
# Create a collection bearing the T3D file's name.
133
156
coll :bpy .types .Collection = bpy .data .collections .new (Path (filepath ).name )
157
+ # Add it to the scene.
134
158
context .scene .collection .children .link (coll )
135
159
# Turn every t3d.Brush into a Blender object.
136
160
for b in brushes :
137
- #print(f"Importing {b.actor_name}...")
161
+ obj : bpy . types . Object
138
162
if b .group == 'cube' :
139
163
# Ignore red brush.
140
- print (f"blender_t3d import : { b .actor_name } is the red brush, so it won't be imported." )
164
+ print (f"blender_t3d: { b .actor_name } is the red brush, so it won't be imported." )
141
165
continue
142
166
# Snap to grid.
143
167
if snap_vertices :
144
168
b .snap (snap_distance )
145
- obj = create_object (coll ,b )
169
+ obj_missing_mats :set [str ]
170
+ obj ,obj_missing_mats = create_object (coll ,b )
171
+ missing_materials .update (obj_missing_mats )
146
172
# Flip.
147
173
if b .csg .lower ()== "csg_subtract" and flip :
148
174
obj .data .flip_normals ()
149
175
# Output time to console.
150
- print (f"Created { len (brushes )} meshes in { time .time ()- time_start } seconds." )
176
+ print (f"blender_t3d: Created { len (brushes )} meshes in { time .time ()- time_start } seconds." )
177
+ results :dict = {"WARNING" :[]}
178
+ if missing_materials :
179
+ results ["WARNING" ]= [f"{ len (missing_materials )} materials missing: { ', ' .join (sorted (missing_materials ))} " ]
180
+ return results
0 commit comments