4
\$\begingroup\$

Unreal Engine 1 nowadays is slightly useless, but it still is (kinda) flexible and allows for modifications of extensive kinds, including many sorts of maps.

However, one of it's editor, Unreal Editor (2 in this case) 's biggest limitations is the small number of possibilities when trying to do complex brushes. The 2D Face Editor is a nice addition, but it doesn't always fulfill all slots, like when doing a complex brush like e.g. a detailed bottle, car, etc.

This is where my new Blender to Unreal Engine 1 brushes exporter enters in action: instead of doing a hassle with BSP and medleys of thousands of brushes, now you can in a few clicks export data from the mesh object selected in Blender and paste it in UnrealEd 2 or save into a file for later use!

And the best feature is probably that you can simply choose the solidity, CSG type, polygonal flags, scale, texture (with the name of the material of the polygon to export) and even whether you want to copy to clipboard, save into a file, both or even none if you are dumb enough!

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
import bmesh

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={texture} 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, texture="BKGND"):
    if not (texture and isinstance(texture, str)):
        texture = "BKGND"

    return polygon_template.format(flags=get_flags(flags) | int(solidity_kinds[solidity], 16), vertices="\n ".join(vertices), texture=texture)

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,object.material_slots[poly.material_index].material.get("unreal_texture", object.material_slots[poly.material_index].material.name)))

    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()

An example:

Blender model

in Blender becomes

UE1 brush

in UnrealEd 2.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.