Skip to main content

A library that provides experimental operations for modifying existing MSTS/ORTS shape files.

Project description

shapeedit

GitHub release (latest by date) Python 3.7+ License GNU GPL v3

This Python module provides a wrapper around the shape data structure from shapeio, offering operations for modifying existing MSTS and ORTS shape files.

Depending on how the module is used, you may end up with unusual-looking shapes; however, the implemented operations ensure that shapes remain error-free and usable in MSTS and Open Rails.

At this stage, only a limited set of operations is implemented. If you need additional functionality or have any other suggestions, you are welcome to request them by creating an issue.

List of companion modules:

  • shapeio - offers functions to convert shapes between structured text format and Python objects.
  • trackshape-utils - offers additional utilities for working with track shapes.
  • pyffeditc - handles compression and decompression of shape files through the ffeditc_unicode.exe utility found in MSTS installations.
  • pytkutils - handles compression and decompression of shape files through the TK.MSTS.Tokens.dll library by Okrasa Ghia.

Installation

Install from PyPI

pip install --upgrade shapeedit

Install from wheel

If you have downloaded a .whl file from the Releases page, install it with:

pip install path/to/shapeedit-<version>‑py3‑none‑any.whl

Replace <version> with the actual version number in the filename.

Install from source

git clone https://github.com/pgroenbaek/shapeedit.git
pip install --upgrade ./shapeedit

Capabilities

The exact capabilities of this Python module might not be immediately obvious, so here's an overview with some examples and screenshots.

You can perform pretty cool edits on existing shapes using this set of tools.

Modification of existing geometry

Existing geometry within shapes can be modified. Vertex positions, texture coordinates, and normal vectors are all adjustable.

The original shapes and textures below are by Norbert Rieger. In the first example, the LZB cable is moved below the trackbed, and texture images are swapped to convert DB1s track sections into DB1fb.

In the second example, trackbed vertices are repositioned to mimic the concrete slabs of the V4hs_RKL track sections. Here, texture images are also swapped.

DB1s vs. DB1fb

DB1s vs. V4hs1t_RKL

Addition of new geometry

Brand-new geometry, i.e., new vertices and triangles, can also be inserted into existing shapes.

In this example, the original NR_Emb shapes are by Norbert Rieger. The square railheads that match the old XTracks track system are extended with new geometry and folded up to match Eric's newer ATracks shapes.

This particular example is somewhat more advanced compared to the others, as the script quite literally weaves new geometry into both curved and straight track sections from stratch.

NR_Emb vs. NR_Emb_AT

The edited NR_Emb shapes with ATracks rails are available for download at trainsim.com.

Removal of geometry

Triangles connecting existing vertices can also be removed, effetively making geometry no longer visible even though the vertices technically remain.

In this example, the side mirrors of the Nohab MY locomotive model by Pál Tamás are removed.

Nohab MY mirror removal

Transferring geometry

Geometry can also be transferred from one shape to another.

In this example, the overhead wires are copied from one of Norbert Rieger's DblSlip7_5d shapes onto the animated DblSlip7_5d switches by Laci1959.

DB2_DblSlip7_5d vs. DB2f_DblSlip7_5d

The edited DblSlip7_5d shapes are available for download at the-train.de.

Addition of new textures, primitives or sub-objects

At present, operations that add new textures, primitives, or sub-objects are not implemented in this module. The goal is to eventually support those capabilites.

You can attempt such modifications manually through shapeio, as the entire shape data structure can be accessed and modified using that module.

Just keep in mind that you are not going to have the abstraction layer provided in this module. The one that makes things easy and keeps the shape error-free.

Usage

Before using this Python module, it is important to understand the basic structure of how geometry is stored within a shape.

The overall structure consists of six elements. These are all accessible through the API of the module:

  • LOD Control: The top-level element, which contains one or more distance levels. Usually, there is only one of these per shape.
  • Distance Level: Defines a distance level in meters. All sub-objects contained within it are visible when the shape is viewed within that range in the simulator. Typically, shapes have one or more of these, with the father distance levels containing less detailed geometry than those closer to the shape.
  • Sub Object: Defines a sub-part of the overall shape at a given distance level. Typically, there are one or more of these within each distance level. Each sub-object contains a list of vertices and primitives.
  • Vertex: Defines a point within the shape geometry. Each vertex also references a texture coordinate (UV) and a normal vector used for lighting calculations.
  • Primitive: A collection of triangles. Typically, one or more are defined, and each primitive is associated with a primstate and a matrix. Primstates define which lighting configurations and which texture gets applied to the primitive's faces. Matrices define how the internal coordinate system within the shape is transformed into world-space coordinates when the shape is rendered.
  • Triangle: A set of three vertices that forms a triangle within the shape geometry. Each triangle also references a normal vector that determine its facing direction.

The basic structure of these elements is as follows:

LOD Controls
└── Distance Levels
    └── Sub Objects
        ├── Vertices
        └── Primitives
            └── Triangles

In the module's API, vertices that are associated with a specific primitive or triangle can be accessed through those elements for convenience.

You will need to inspect the shape manually in a text editor to determine the exact distance level values, sub-object indexes, and primstate names/indexes to use.

Modification of existing geometry

import shapeio
from shapeedit import ShapeEditor

my_shape = shapeio.load("./path/to/example.s")

shape_editor = ShapeEditor(my_shape)

sub_object = shape_editor.lod_control(0).distance_level(200).sub_object(0)

# Set point/uv_point/normal data of all vertices within the subobject to zero.
for vertex in sub_object.vertices():
    vertex.point.x = 0.0
    vertex.point.y = 0.0
    vertex.point.z = 0.0
    vertex.uv_point.u = 0.0
    vertex.uv_point.v = 0.0
    vertex.normal.x = 0.0
    vertex.normal.y = 0.0
    vertex.normal.z = 0.0

shapeio.dump(my_shape, "./path/to/output.s")

Addition of new vertices and triangles

import shapeio
from shapeio.shape import Point, UVPoint, Vector
from shapeedit import ShapeEditor

my_shape = shapeio.load("./path/to/example.s")

shape_editor = ShapeEditor(my_shape)

# Add three new vertices to primitives associated with prim_state_idx 22.
# Connect the three vertices added to each primitive with a triangle.
for lod_control in shape_editor.lod_controls():
    for distance_level in lod_control.distance_levels():
        for sub_object in distance_level.sub_objects():
            for primitive in sub_object.primitives(prim_state_index=22):
                new_point1 = Point(0.0, 0.0, 0.0)
                new_point2 = Point(1.0, 0.0, 0.0)
                new_point3 = Point(2.0, 0.0, 1.0)
                new_uv_point = UVPoint(0.0, 0.0)
                new_normal = Vector(0.0, 0.0, 0.0)

                new_vertex1 = primitive.add_vertex(new_point1, new_uv_point, new_normal)
                new_vertex2 = primitive.add_vertex(new_point2, new_uv_point, new_normal)
                new_vertex3 = primitive.add_vertex(new_point3, new_uv_point, new_normal)

                primitive.insert_triangle(new_vertex1, new_vertex2, new_vertex3)

shapeio.dump(my_shape, "./path/to/output.s")

Removal of triangles

import shapeio
from shapeedit import ShapeEditor

my_shape = shapeio.load("./path/to/example.s")

shape_editor = ShapeEditor(my_shape)

# Remove all triangles from primitives associated with any prim_state named "Rails".
for lod_control in shape_editor.lod_controls():
    for distance_level in lod_control.distance_levels():
        for sub_object in distance_level.sub_objects():
            for primitive in sub_object.primitives(prim_state_name="Rails"):
                for vertex in primitive.vertices():
                    primitive.remove_triangles_connected_to(vertex)

shapeio.dump(my_shape, "./path/to/output.s")

Running Tests

You can run tests manually or use tox to test across multiple Python versions.

Run Tests Manually

First, install the required dependencies:

pip install pytest

Then, run tests with:

pytest

Run Tests with tox

First, install the required dependencies:

pip install tox

Then, run tests with:

tox

This will execute tests for all Python versions specified in tox.ini.

Roadmap

Possible future features to be added:

  • Ability to add new textures and primitives
  • Ability to remove vertices from primitives
  • Ability to edit things like light configurations, animations, etc.
  • And possibly more..

Please make an issue if you have any good ideas, or if you need something that has not yet been implemented.

Pull requests are also welcome.

Contributing

Contributions of all kinds are welcome. These could be suggestions, issues, bug fixes, documentation improvements, or new features.

For more details see the contribution guidelines.

License

This Python module was created by Peter Grønbæk Andersen and is licensed under GNU GPL v3.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

shapeedit-0.5.0b3.tar.gz (42.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

shapeedit-0.5.0b3-py3-none-any.whl (61.9 kB view details)

Uploaded Python 3

File details

Details for the file shapeedit-0.5.0b3.tar.gz.

File metadata

  • Download URL: shapeedit-0.5.0b3.tar.gz
  • Upload date:
  • Size: 42.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.3

File hashes

Hashes for shapeedit-0.5.0b3.tar.gz
Algorithm Hash digest
SHA256 89ff6bc8353e926bf92b8a2c57480c9fbbc74cadef8f3c3ac6cbe3b9008a2f4b
MD5 a22a6fedbf2fcec67b1060c64d1b88ab
BLAKE2b-256 49ecfe88daa1585f2ea4dfe3187a04107722409bcf98cda228d6ae4163be25ef

See more details on using hashes here.

File details

Details for the file shapeedit-0.5.0b3-py3-none-any.whl.

File metadata

  • Download URL: shapeedit-0.5.0b3-py3-none-any.whl
  • Upload date:
  • Size: 61.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.3

File hashes

Hashes for shapeedit-0.5.0b3-py3-none-any.whl
Algorithm Hash digest
SHA256 63a66b33059f15f886fa40578facd3070d32f370680bc390fbe42f5394ece085
MD5 e97ca1d4744bd2f1faf689e72251c14a
BLAKE2b-256 648bc93be77af9fd8afe0bd319d7ee2f9241327bbbbf5b93ed5552a34f14b9a7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page