Skip to content

Instantly share code, notes, and snippets.

@wallabra
Last active March 8, 2021 00:01
Show Gist options
  • Save wallabra/40987ebe8057d1726eb042a3d1d44c41 to your computer and use it in GitHub Desktop.
Save wallabra/40987ebe8057d1726eb042a3d1d44c41 to your computer and use it in GitHub Desktop.
Blender Python script to export Blender models to Unreal Engine 1 brushes
bl_info = {
"name": "Unreal Brush to Clipboard (.t3d)",
"category": "Import-Export",
"desctiption": "This is an exporter for Unreal Editor 2 (Unreal Engine 1).\n\nIt uses subprocess and Windows' clip command to paste the brush data\ninto the clipboard so it can be pasted directly into Unreal Editor.",
"author": "Gustavo Ramos 'Gustavo6046' Rehermann"
}
import functools
import bpy
brush_template = """
Begin Map
Begin Actor Class=Brush Name=Brush0
CsgOper=CSG_{}
PolyFlags={}
MainScale=(SheerAxis=SHEER_ZX)
PostScale=(SheerAxis=SHEER_ZX)
Level=LevelInfo'MyLevel.LevelInfo0'
Tag=Brush
Region=(Zone=LevelInfo'MyLevel.LevelInfo0',iLeaf=-1)
bSelected=False
Begin Brush Name=BlenderExportedBrush
Begin PolyList
{polygons}
End PolyList
End Brush
Brush=Model'MyLevel.BlenderExportedBrush'
Name=Brush1
End Actor
End Map
"""
polygon_template = """
Begin Polygon Texture=BKGND Flags={flags}
{vertices}
End Polygon
"""
vertex_template = """
Vertex {x},{y},{z}
"""
csg_kinds = {
"add": "Add",
"sub": "Subtract",
"act": "Active",
}
solidity_kinds = {
"solid": "0",
"smsld": "20",
"nonsd": "8",
}
flag_kinds = {
"masked": "2",
"modulated": "40",
"twosides": "100",
"portal": "4000000",
"translucent": "4",
"mirror": "8000000",
"unlit": "400000",
}
def get_flags(flags):
try:
return functools.reduce(lambda x, y: x | y, [int(flag_kinds[x], 16) for x in tuple(flags)])
except TypeError:
return 0
def generate_brush(polygons, csg, flags, solidity):
return brush_template.format(csg_kinds[csg], get_flags(flags) | int(solidity_kinds[solidity], 16), polygons="\n ".join(polygons))
def generate_polygon(vertices, flags, solidity):
return polygon_template.format(flags=get_flags(flags) | int(solidity_kinds[solidity], 16), vertices="\n ".join(vertices))
def generate_vertex(x, y, z):
if x >= 0: x = "+" + str(x)
if y >= 0: y = "+" + str(y)
if z >= 0: z = "+" + str(z)
return vertex_template.format(x=x, y=y, z=z)
def convert_mesh(object, scale=64, csg="add", solidity="solid", flags={"twosides"}):
mesh = object.data
vertices = [x.co for x in mesh.vertices]
polygons = mesh.polygons
result = ""
polys = []
for poly in polygons:
polygon_vertices = []
for vi in poly.vertices:
polygon_vertices.append([x - y for x, y in zip(vertices[vi], vertices[0])])
polys.append(generate_polygon([generate_vertex(*[x * scale for x in v]) for v in polygon_vertices], flags, solidity))
return generate_brush(polys, csg, flags, solidity)
class ClipUnrealBrush(bpy.types.Operator):
"""Blender to Unreal Brush to Clipboard (.t3d)"""
bl_idname = "export.unreal_brush_clipboard"
bl_label = "Export Unreal Brush to Clipboard (.t3d)"
bl_options = {'REGISTER'}
scale = bpy.props.FloatProperty(name="Scale", default=64, min=1, max=2048)
filename = bpy.props.StringProperty(name="Filename", description="The file to save .t3d to.", default="", subtype='FILE_PATH')
clipboard = bpy.props.BoolProperty(name="Clipboard", description="Whether should the data be copied in the clipboard.", default=True)
export_file = bpy.props.BoolProperty(name="Save to File", description="Whether should the data be exported to a file.", default=False)
csg = bpy.props.EnumProperty(name="CSG Type", items=(
("add", "Additive", "This kind of brush will fill empty space previously subtracted in UnrealEd. The map's void is filled entirely with this for instance."),
("sub", "Subtractive", "This kind of brush will attempt to add empty space where there isn't yet inside the brush's enclosing."),
("act", "Red Builder Brush", "This is the Red Builder Brush, which is useful in generating multiple brushes of it's kind, and of any CSG kind, including semisolid."),
), description="The CSG of the brush exported to the clipboard.", default="add")
solidity = bpy.props.EnumProperty(name="Solidity", items=(
("solid", "Solid", "Solid brushes won't do BSP cuts in semisolids. All subtractive brushes are solid."),
("smsld", "Semisolid", "Semisolid brushes won't do BSP cuts in solids, useful for avoiding BSP errors in solid brushes, but only available for Additive brushes."),
("nonsd", "Nonsolid", "Nonsolid brushes are a special kind of brush where instead of being a volume, the polygons determine each a different plane. Useful for complex zone separators, but cuts through both Solids and Semisolids as a plane. Some of the physics in Nonsolids are also present in non-manifold (in the sense of no volume) Solid and Semisolid polygons."),
), description="The Solidity of the brush exported to the clipboard.", default="solid")
flags = bpy.props.EnumProperty(name="Polygonal Flags", items=(
("masked", "Masked", "Every pixel containing as color the 1st index of the bitmap's pallete will be transparent in the polygons."),
("modulated", "Modulated", "A special kind of transparency. For more see: https://wiki.beyondunreal.com/Legacy:Color_Blending#Color_Blending_Modes"),
("twosides", "Two-Sided", "Every polygon has two sides, no matter the normal. Useful for when you are unsure as for the polygons' normals or non-manifoldness."),
("portal", "Zone Portal", "Used to separate multiple zones, e.g. a plane to separate a water's submerged zone from the outdoors lake area and the indoors factory. But this is probably my creative imagination. :3"),
("translucent", "Transparent", "The polygons are translucent (i.e. semi-transparent; not the exact notation used in Blender)."),
("mirror", "Mirror Surfaces", "Like the Glossy material in Cycles, reflects in real-time."),
("unlit", "Unlit", "The surfaces are always full-bright,no matter the light sources around in UnrealEd."),
), options={'ANIMATABLE', 'ENUM_FLAG'}, description="The flags of every polygon exported into the clipboard.")
def execute(self, context):
print(self.csg)
print(str(self.csg))
result = convert_mesh(context.object, self.scale, str(self.csg), str(self.solidity), set(self.flags))
res = result.splitlines()
result = ""
for l in res:
if l.replace(" ", "").replace(" ", "") != "":
result += "\n" + l
result = result[1:]
if self.clipboard:
bpy.context.window_manager.clipboard = result
if self.export_file:
file = open(self.filename, "w")
file.write(result)
del file
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def register():
bpy.utils.register_class(ClipUnrealBrush)
def unregister():
bpy.utils.unregister_class(ClipUnrealBrush)
if __name__ == "__main__":
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment