-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit bfc60a8
Showing
5 changed files
with
795 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
|
||
bl_info={ | ||
"name": "Import and export old Unreal .T3D format", | ||
"author": "Crapola", | ||
"version": (1,0,0), | ||
"blender": (2,80,0), | ||
"location": "File > Import-Export ; Object", | ||
"description": "Import and export UnrealED .T3D files.", | ||
"doc_url":"", # TODO: Link to repo. | ||
"tracker_url":"", # TODO: Link to repo. | ||
"support":"COMMUNITY", | ||
"category":"Import-Export", # Category in Add-ons browser. | ||
} | ||
|
||
import bpy | ||
|
||
from . import exporter, importer | ||
|
||
INVALID_FILENAME="Invalid file name." | ||
|
||
class OBJECT_OT_export_t3d_clipboard(bpy.types.Operator): | ||
"""Export selected meshes to T3D into the clipboard.""" | ||
bl_idname="object.export_t3d_clipboard" | ||
bl_label="Export T3D to clipboard" | ||
|
||
scale:bpy.props.FloatProperty(name="Scale Multiplier",default=128.0) | ||
|
||
@classmethod | ||
def poll(cls,context): | ||
return context.selected_objects | ||
|
||
def execute(self,context): | ||
sel_objs=[obj for obj in context.selected_objects if obj.type=='MESH'] | ||
num_brushes,txt=exporter.export(sel_objs,self.scale) | ||
context.window_manager.clipboard=txt | ||
self.report({'INFO'},f"{num_brushes} brushes exported to clipboard.") | ||
return {'FINISHED'} | ||
|
||
def invoke(self, context, event): | ||
wm=context.window_manager | ||
return wm.invoke_props_dialog(self) | ||
|
||
class BT3D_MT_file_export(bpy.types.Operator): | ||
"""Export T3D file.""" | ||
bl_idname="bt3d.file_export" | ||
bl_label="Export Unreal T3D (.t3d)" | ||
filename:bpy.props.StringProperty(subtype='FILE_NAME') | ||
filepath:bpy.props.StringProperty(subtype='FILE_PATH') | ||
scale:bpy.props.FloatProperty( | ||
name="Scale Multiplier", | ||
default=1.0, | ||
subtype='FACTOR') | ||
def execute(self,context): | ||
if not self.filename.split(".")[0]: | ||
self.report({'ERROR'},INVALID_FILENAME) | ||
return {'CANCELLED'} | ||
# Get meshes in scene. | ||
objs=[obj for obj in context.scene.objects if obj.type=='MESH'] | ||
if not objs: | ||
self.report({'WARNING'},"There are no meshes in scene to export.") | ||
return {'CANCELLED'} | ||
num_brushes,txt=exporter.export(objs,self.scale) | ||
self.filepath=bpy.path.ensure_ext(self.filepath,".t3d") | ||
if not num_brushes: | ||
self.report({'WARNING'},"Nothing was converted.") | ||
return {'CANCELLED'} | ||
with open(self.filepath,"w") as f: | ||
f.write(txt) | ||
self.report({'INFO'},f"{num_brushes} brushes saved to {self.filepath}.") | ||
return {'FINISHED'} | ||
def invoke(self, context, event): | ||
if not self.filepath: | ||
self.filepath=bpy.path.ensure_ext(bpy.data.filepath,".t3d") | ||
context.window_manager.fileselect_add(self) | ||
return {'RUNNING_MODAL'} | ||
|
||
class BT3D_MT_file_import(bpy.types.Operator): | ||
"""Import T3D file.""" | ||
bl_idname="bt3d.file_import" | ||
bl_label="Import Unreal T3D (.t3d)" | ||
|
||
filename:bpy.props.StringProperty( | ||
name="input filename", | ||
subtype='FILE_NAME' | ||
) | ||
filepath:bpy.props.StringProperty( | ||
name="input file", | ||
subtype='FILE_PATH' | ||
) | ||
filter_glob:bpy.props.StringProperty( | ||
default="*.t3d", | ||
options={'HIDDEN'}, | ||
) | ||
# Options. | ||
flip:bpy.props.BoolProperty( | ||
name="Flip normals", | ||
description="Flip normals of CSG_Subtract brushes", | ||
default=False | ||
) | ||
snap_vertices:bpy.props.BoolProperty( | ||
name="Snap vertices", | ||
description="Snap to grid.", | ||
default=False | ||
) | ||
snap_distance:bpy.props.FloatProperty( | ||
name="Snap distance", | ||
default=1.0 | ||
) | ||
|
||
def execute(self,context): | ||
if not self.filename.split(".")[0]: | ||
self.report({'ERROR'},INVALID_FILENAME) | ||
return {'CANCELLED'} | ||
importer.import_t3d_file( | ||
context, | ||
self.filepath, | ||
self.filename, | ||
self.snap_vertices, | ||
self.snap_distance, | ||
self.flip) | ||
return {'FINISHED'} | ||
|
||
def invoke(self, context, event): | ||
wm=context.window_manager | ||
wm.fileselect_add(self) | ||
return {'RUNNING_MODAL'} | ||
|
||
classes = ( | ||
BT3D_MT_file_export, | ||
BT3D_MT_file_import, | ||
OBJECT_OT_export_t3d_clipboard, | ||
) | ||
register_classes, unregister_classes = bpy.utils.register_classes_factory(classes) | ||
|
||
menus=( | ||
lambda x,_:x.layout.operator(OBJECT_OT_export_t3d_clipboard.bl_idname), | ||
lambda x,_:x.layout.operator(BT3D_MT_file_export.bl_idname), | ||
lambda x,_:x.layout.operator(BT3D_MT_file_import.bl_idname), | ||
) | ||
|
||
def register(): | ||
print("Registering.") | ||
register_classes() | ||
# Add to menu. | ||
bpy.types.VIEW3D_MT_object.append(menus[0]) | ||
bpy.types.TOPBAR_MT_file_export.append(menus[1]) | ||
bpy.types.TOPBAR_MT_file_import.append(menus[2]) | ||
|
||
def unregister(): | ||
print("Unregistering.") | ||
# Remove from menu. | ||
bpy.types.VIEW3D_MT_object.remove(menus[0]) | ||
bpy.types.TOPBAR_MT_file_export.remove(menus[1]) | ||
bpy.types.TOPBAR_MT_file_import.remove(menus[2]) | ||
unregister_classes() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import math | ||
|
||
import bmesh | ||
import bpy | ||
from mathutils import Euler, Matrix, Vector | ||
|
||
try: | ||
from .t3d import Brush, Polygon, Vertex | ||
except: | ||
from t3d import Brush, Polygon, Vertex | ||
|
||
DEBUG=0 | ||
def _print(*_):pass | ||
if DEBUG:_print=print | ||
|
||
TEXTURE_SIZE=256 | ||
|
||
def basis_from_points(points:list)->Matrix: | ||
""" 2D Basis, middle point is origin. """ | ||
m=Matrix( ((1,0,0),(0,1,0),(0,0,1)) ) | ||
v0,v1,v2=points | ||
m[0].xy=(v2-v1) #X | ||
m[1].xy=(v0-v1) #Y | ||
m[2].xy=v1 # Origin | ||
m.transpose() | ||
return m | ||
|
||
def export(object_list,scale_multiplier=128.0)->tuple: | ||
stuff="" | ||
for obj in object_list: | ||
mesh:bpy.types.Mesh=obj.data | ||
bm=bmesh.new() | ||
bm.from_mesh(mesh) | ||
|
||
uv_layer_0=None | ||
if bm.loops.layers.uv: | ||
uv_layer_0: bmesh.types.BMLayerItem = bm.loops.layers.uv[0] | ||
layer_texture=bm.faces.layers.string.get("texture") | ||
poly_list=[] | ||
for f in bm.faces: | ||
# Vertices. | ||
verts=[Vertex(v.co*scale_multiplier) for v in f.verts] | ||
poly=Polygon(verts,f.normal) | ||
|
||
# Get texture name, either from custom attribute if it exists (brush | ||
# was imported), or material. | ||
if layer_texture: | ||
poly.texture=f[layer_texture].decode('utf-8') | ||
elif len(obj.data.materials)>0: | ||
name=obj.data.materials[f.material_index].name | ||
poly.texture=name | ||
|
||
_print(f"---- Face {poly.texture} ----") | ||
|
||
# Texture coordinates. | ||
|
||
# Compute floor/UV (plane where Z is 0) to surface transform, with | ||
# the first three verts of polygon. | ||
n=f.normal | ||
first_three=f.loops[0:3] | ||
v0,v1,v2=[v.vert.co for v in first_three] | ||
b=Matrix() | ||
b[2].xyz=n | ||
b[0].xyz=(v0-v1) | ||
b[1].xyz=(v2-v1) | ||
b[3].xyz=v0 | ||
b.transpose() # Needed. | ||
b.invert() | ||
axis_x=Vector((1,0,0)) | ||
axis_y=Vector((0,1,0)) | ||
# Basic texturing. | ||
poly.u=b.transposed()@axis_x | ||
poly.v=b.transposed()@axis_y | ||
|
||
# Convert Blender UV Map if it exists. | ||
if uv_layer_0: | ||
v0i=(b@v0) | ||
v1i=(b@v1) | ||
v2i=(b@v2) | ||
# We assume UVs are a linear transform of the polygon shape. | ||
# Figure out that transform by using the first three verts. | ||
first_three_uvs=[l[uv_layer_0].uv for l in f.loops[0:3]] | ||
u0,u1,u2=first_three_uvs | ||
# mv=quad->poly, mu=poly->uv. | ||
mv=basis_from_points( (v0i.xy,v1i.xy,v2i.xy) ) | ||
mu=basis_from_points( (u0,u1,u2) ) | ||
t=mu @ mv.inverted() | ||
t.resize_4x4() | ||
t.transpose() | ||
b.transpose() | ||
|
||
poly.u=b @ (t @ axis_x*TEXTURE_SIZE/scale_multiplier) | ||
poly.v=b @ (t @ axis_y*TEXTURE_SIZE/scale_multiplier) | ||
|
||
# Pan. | ||
pan=mu[1]*TEXTURE_SIZE | ||
if pan: | ||
poly.pan=(int(pan.x),int(pan.y)) | ||
|
||
poly_list.append(poly) | ||
|
||
brush=Brush(poly_list,obj.location*scale_multiplier,obj.name) | ||
|
||
if obj.rotation_euler!=Euler((0,0,0)): | ||
brush.rotation=Vector(obj.rotation_euler)*65536/math.tau | ||
if obj.scale!=Vector((0,0,0)): | ||
brush.mainscale=obj.scale | ||
if obj.get("csg"): | ||
brush.csg=obj["csg"] | ||
|
||
stuff+=str(brush) | ||
|
||
bm.to_mesh(mesh) | ||
bm.free() | ||
|
||
everything=f"""Begin Map\n{stuff}End Map\n""" | ||
return len(object_list),everything |
Oops, something went wrong.