Generating mesh from imported text data

Hello.

I’ve recently been updating an old import/export plugin written by someone else. It deals with meshes stored in text format. I think I’ve gotten the exporter working correctly but I’m having trouble with the importer. The existing code already parses the file and generates python data, but I can’t quite get my head round converting them to meshes.

Here’s an example of what it generates:
(there can be multiple meshes, the existing loop for all of them calls them “obj” )

'NAME': 	'@h01', 
'POINTS': [(-0.439504, -0.058225, 0.76561), (-0.439504, -0.058225, 0.882061), (-0.439504, 0.058225, 0.76561), (-0.439504, 0.058225, 0.882061), (-0.323053, -0.058225, 0.76561), (-0.323053, -0.058225, 0.882061), (-0.323053, 0.058225, 0.76561), (-0.323053, 0.058225, 0.882061)], 
'FACES': [
	{'VISIBILITY': 'N', 'MATERIAL': 0, 'VERTICES': (5, 6, 7), 'UV': ((0.625, 0.75), (0.375, 0.5), (0.625, 0.5))}, 
	{'VISIBILITY': 'N', 'MATERIAL': 0, 'VERTICES': (1, 4, 5), 'UV': ((0.625, 1.0), (0.375, 0.75), (0.625, 0.75))},
...(one of each above line for each face)...
]

Visiblity seems redundant (it always seems to be N)
Material will be the face’s material index, and is related to a set of materials that are specified elsewhere in the file.
Vertices refer to the indices in the points array and UV is the UV mapping.

I’ve been searching around but I think I’m going round in circles, which is funny because I’m sure the answer lies in loops.

So far I’ve been able to generate meshes (but with no UV mapping or materials) with this code:

for obj in <some list>:
    faces = []
    for f in obj['FACES']:
        faces.append(f['VERTICES'])  # only get the vertex IDs
    
    mesh = bpy.data.meshes.new(obj['NAME'])
    mesh.from_pydata(obj['POINTS'], [], faces)
    mesh.uv_layers.new() #probably needed for the UV coords, definitely for the exporter

Can anyone advise me on what I should be doing to get all the data in? Is the above code a good start with just a couple of extra things needed, or is there a much better approach, such as take off and nuke the site from orbit?

I’d say generally the approach is fine. Mat indices are set per face. Uv coordinates go in the same sequence as the faces come.

Not a big sample to test from, but here’s an example of how to deal with uvs and mat indices.

import bpy

data = {'NAME':     '@h01', 
'POINTS': [(-0.439504, -0.058225, 0.76561), (-0.439504, -0.058225, 0.882061), (-0.439504, 0.058225, 0.76561), (-0.439504, 0.058225, 0.882061), (-0.323053, -0.058225, 0.76561), (-0.323053, -0.058225, 0.882061), (-0.323053, 0.058225, 0.76561), (-0.323053, 0.058225, 0.882061)], 
'FACES': [
    {'VISIBILITY': 'N', 'MATERIAL': 0, 'VERTICES': (5, 6, 7), 'UV': ((0.625, 0.75), (0.375, 0.5), (0.625, 0.5))}, 
    {'VISIBILITY': 'N', 'MATERIAL': 0, 'VERTICES': (1, 4, 5), 'UV': ((0.625, 1.0), (0.375, 0.75), (0.625, 0.75))},
]}

verts = data['POINTS']
faces = [f['VERTICES'] for f in data['FACES']]
uv_data = [uv for f in data['FACES'] for uv in f['UV']]

def run():
    obj = bpy.data.objects.new("obj", bpy.data.meshes.new("obj"))
    bpy.context.scene.collection.objects.link(obj)

    obj.data.from_pydata(verts, [], faces)         
    obj.data.validate()

    for source, target in zip(data['FACES'], obj.data.polygons):
        target.material_index = source['MATERIAL']

    for uvs in obj.data.uv_layers:
        obj.data.uv_layers.remove(uvs)

    uv_map = obj.data.uv_layers.new(do_init=False)
    for loop, uv in zip(uv_map.data, uv_data):
        loop.uv = uv

if __name__ == '__main__':
    run()

Also not sure what VISIBILITY in this case means - so skipped that.

hi, may i ask you why you want the sahpe in text format ?
i was working on a similar project for our team, and we found unrelevant to store shape data in text format. we were more interested to be able to call any shape from a library (in .blend), because we can modifiy the object parameters and store thoses modifications :wink: wich is not convenient for the text format…
after all, both solutions are ok, depend of your objectives :slight_smile:
have a nice day

Many thanks for your replies. The code sample worked very well. I had to do some tweaking with the materials assignment. The file lists all required materials at the start, so the code initially loads them into bpy.data.materials (unless they’re already present). A string array also stores the material names from the file (array name: importedMaterials)

The material assignment is like this:

for source, target in zip(obj['FACES'], newObj.data.polygons):
    fileMatID = source['MATERIAL']  # 0 indexed material ID
    importedName = importedMaterials[fileMatID] # from previously generated array
    if importedName not in obj.data.materials:
        obj.data.materials.append(bpy.data.materials[importedName]) #new material slot added automatically

    target.material_index = newObj.data.materials.find(importedName)

Why a text fie? It’s a sort of intermediate format. The overall purpose is to make objects for an old game engine (Thief, 1998/2000). The original workflow was to make an object, export it as .3ds, convert that to .e (The text file’s extension) and convert that to the game’s binary format.
(The developers released the command-line tools for this conversion)

[This topic is of course about going the other way. Someone developed two programs that do the opposite to the above process, binary to text and then to 3ds. Blender 2.80 doesn’t have a working 3ds addon. I tried to update the old one but it was way too complicated, so I thought I’d try updating this ‘text file’ addon. I’m very happy now that it works.]

I don’t have all the info* but bear in mind the file formats were developed in the mid-late 1990s. It may have been that the .3ds format didn’t support some of the material parameters the developers wanted for the game. Converting to the .e text format would allow the modellers to set those params in notepad before final conversion to binary.

Secondly, the game supports very simple joints for rotating a sliding. A two point line defines an axle. I think there’s a problem with 3dsMax where two point objects are not exported (considered an error?), so one way around it is to make a triangle. The .e file can be edited to delete the unnecessary vertex.

That might explain the use of the text file. For older versions of Blender other people have written a couple of import/export addons. One of them relies on the 3ds addon, another works directly with the binary files but that hasn’t been updated and it seems to lack support for objects with multiple parts (e.g. invisible points for light to be emitted or particle effects attached).

*or what would Kyle Reese say?

1 Like