This release is a pre-release and may not be stable for production use.
trackshape-utils
A collection of utilities for working with MSTS and ORTS track shapes.
List of companion modules:
- shapeio - offers functions to convert shapes between structured text format and Python objects.
- shapeedit - provides a wrapper for modifying the shape data structure safely.
- pyffeditc - handles compression and decompression of shape files through the
ffeditc_unicode.exeutility found in MSTS installations. - pytkutils - handles compression and decompression of shape files through the
TK.MSTS.Tokens.dlllibrary by Okrasa Ghia.
Installation
Install from PyPI
pip install --upgrade trackshapeutils
Install from wheel
If you have downloaded a .whl file from the Releases page, install it with:
pip install path/to/trackshapeutils‑<version>‑py3‑none‑any.whl
Replace <version> with the actual version number in the filename.
Install from source
git clone https://github.com/pgroenbaek/trackshape-utils.git
pip install --upgrade ./trackshape-utils
Capabilities
This Python module provides additional utilities which, when used alongside shapeedit, enable the creation of scripts to edit both straight and curved track shapes.
Usage
The functionality in this module largely relies on the concept of trackcenters. These can be loaded from either a global tsection.dat, a local tsection.dat or be created manually to match whatever shape you are working on.
Using trackcenters as a reference makes it easy to determine which part of the track a given vertex belongs to, which side of the track it is on, and how far from the start of the track the vertex is positioned.
Finally, trackcenters allow for calculating new positions of vertices relative to the trackcenter.
Loading trackcenters
From the included global tsection.dat
Loading trackcenters is straightforward. You need to specify the shape name exactly as it appears in the global tsection.dat. This is not case-sensitive, but any prefixes or suffixes must be removed from shape names, e.g., DB2f_a1t10mStrt.s must be a1t10mStrt.s.
The function returns a list of trackcenters. For single-track shapes, such as a1t10mStrt.s, the list will contain only one item.
Additionally, the fidelity of the generated trackcenters can be controlled using the num_points_per_meter parameter. Smaller values make calculations faster but less accurate. This is a tradeoff that may require experimentation. Typically, 7-12 points per meter provides good results without being too slow.
import trackshapeutils as tsu
trackcenters = tsu.trackcenters_from_global_tsection(
"a1t10mStrt.s",
num_points_per_meter=8
)
From your own global tsection.dat
If you want to use your own global tsection.dat, you can specify it using the tsection_file_path parameter.
import trackshapeutils as tsu
trackcenters = tsu.trackcenters_from_global_tsection(
"a1t10mStrt.s",
tsection_file_path="/path/to/your/global/tsection.dat",
num_points_per_meter=8
)
If you specify a global tsection extension file, for example from the /OpenRails/tsection.dat directory of a route, then set the include_global_tsection parameter to True. Otherwise, any tracksections from the standardised global tsection.dat cannot be found because this Python module currently does not resolve any include (...) statements.
Doing this appends the standardised global tsection.dat build #60 to your extension file, so that any track section references within it can be found.
import trackshapeutils as tsu
trackcenters = tsu.trackcenters_from_global_tsection(
"a1t10mStrt.s",
tsection_file_path="/path/to/your/global/extension/tsection.dat",
include_global_tsection=True,
num_points_per_meter=8
)
From a local tsection.dat
For any track shapes that rely on dynamic track sections, for example those created using tools like DynaTrax, you need to load the trackcenter from the local tsection.dat.
Here, it is not shape names but the TrackPath index that must be specified (NOT the TrackSection index). In other words, this refers to the number used as SectionIdx within world files.
This function returns a single trackcenter rather than a list, as these are always single-track.
import trackshapeutils as tsu
trackcenter = tsu.trackcenter_from_local_tsection(
41023,
tsection_file_path="/path/to/your/local/tsection.dat",
num_points_per_meter=8
)
Manual creation of trackcenters
If needed, trackcenters can also be created manually.
These two functions normally do not need to be called directly, as they are invoked within trackcenters_from_global_tsection and trackcenter_from_local_tsection.
Trackcenters can be combined using the + operator or the Trackcenter.average() class method. The + operator appends points from one trackcenter to another, while Trackcenter.average() computes the average of corresponding points to create a new trackcenter.
import trackshapeutils as tsu
# Generate a straight trackcenter
straight_trackcenter = tsu.generate_straight_centerpoints(
length=10,
start_angle=0,
start_point=Point(0.0, 0.0, 0.0),
num_points=80
)
# Generate a curved trackcenter
curved_trackcenter = tsu.generate_curve_centerpoints(
curve_radius=1500,
curve_angle=10,
start_angle=0,
start_point=Point(0.0, 0.0, 10.0),
num_points=80
)
# Combine trackcenters by appending points from the curved trackcenter
combined_trackcenter = straight_trackcenter + curved_trackcenter
# Note: The '+' operator appends points from the second trackcenter to the first,
# preserving the order without modifying the original trackcenters.
If needed, trackcenters can also be averaged, for example to compute the centerline between two parallel tracks.
import trackshapeutils as tsu
from trackshapeutils import Trackcenter
# Generate two parallel straight trackcenters, e.g., representing parallel tracks
straight_trackcenter1 = tsu.generate_straight_centerpoints(
length=10,
start_angle=0,
start_point=Point(2.5, 0.0, 0.0),
num_points=80
)
straight_trackcenter2 = tsu.generate_straight_centerpoints(
length=10,
start_angle=0,
start_point=Point(-2.5, 0.0, 0.0),
num_points=80
)
# Create a new trackcenter by averaging corresponding points from the two trackcenters
averaged_trackcenter = Trackcenter.average([straight_trackcenter1, straight_trackcenter2])
# Note: The averaged_trackcenter contains points located at the midpoint between
# corresponding points from straight_trackcenter1 and straight_trackcenter2.
Calculating distances
Distance to closest trackcenter
import trackshapeutils as tsu
from shapeio.shape import Point
point_along_track = Point(1.0, 1.0, 1.0)
trackcenters = tsu.trackcenters_from_global_tsection(
"a4t10mStrt.s",
num_points_per_meter=8
)
closest_trackcenter = tsu.find_closest_trackcenter(point_along_track, trackcenters, plane="xz")
closest_centerpoint = tsu.find_closest_centerpoint(point_along_track, closest_trackcenter, plane="xz")
distance_from_center = tsu.distance_between(point_along_track, closest_centerpoint, plane="xz")
Or, you can use signed_distance_between to be able to determine which side of the trackcenter the point is located at:
signed_distance_from_center = tsu.signed_distance_between(point_along_track, closest_centerpoint, plane="xz")
Distance along the length of the track
import trackshapeutils as tsu
from shapeio.shape import Point
point_along_track = Point(1.0, 1.0, 1.0)
trackcenters = tsu.trackcenters_from_global_tsection(
"a4t10mStrt.s",
num_points_per_meter=8
)
closest_trackcenter = tsu.find_closest_trackcenter(point_along_track, trackcenters, plane="xz")
closest_centerpoint = tsu.find_closest_centerpoint(point_along_track, closest_trackcenter, plane="xz")
distance_from_start = tsu.distance_along_trackcenter(point_along_track, closest_centerpoint, start_point=Point(0.0, 0.0, 0.0))
Calculating new vertex positions
Perpendicular to a trackcenter
Recalculates position of point_along_track to be one meter further away from the closest track center.
import trackshapeutils as tsu
from shapeio.shape import Point
point_along_track = Point(1.0, 1.0, 1.0)
trackcenters = tsu.trackcenters_from_global_tsection(
"a4t10mStrt.s",
num_points_per_meter=8
)
closest_trackcenter = tsu.find_closest_trackcenter(point_along_track, trackcenters, plane="xz")
closest_centerpoint = tsu.find_closest_centerpoint(point_along_track, closest_trackcenter, plane="xz")
signed_distance_from_center = tsu.signed_distance_between(point_along_track, closest_centerpoint, plane="xz")
if signed_distance_from_center > 0:
new_distance = signed_distance_from_center + 1
else:
new_distance = signed_distance_from_center - 1
new_point = tsu.get_new_position_from_trackcenter(new_distance, point_along_track, closest_trackcenter)
Along the length of a trackcenter
Move point_along_track 5 meters back along the track center from the original point. Keeping the XYZ offset between the original point and what was previously the closest point of the track center.
import trackshapeutils as tsu
from shapeio.shape import Point
point_along_track = Point(7.0, 7.0, 1.0)
trackcenters = tsu.trackcenters_from_global_tsection(
"a4t10mStrt.s",
num_points_per_meter=8
)
closest_trackcenter = tsu.find_closest_trackcenter(point_along_track, trackcenters, plane="xz")
closest_centerpoint = tsu.find_closest_centerpoint(point_along_track, closest_trackcenter, plane="xz")
new_point = tsu.get_new_position_along_trackcenter(-5, closest_centerpoint, closest_trackcenter)
Example Scripts
Conversion of DB1z to V4hs_RKL slab track (script)
Modifying NR_Emb with XTracks rails to fit ATracks (script)
The edited NR_Emb shapes with ATracks rails are available for download at trainsim.com.
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 pytest
Then, run tests with:
tox
This will execute tests for all Python versions specified in tox.ini.
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.
The module includes the standardized global tsection.dat build #60 by Derek Morton. This file is also distributed under the GNU General Public License.
Metadata
Release files for trackshapeutils 0.5.0b3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| trackshapeutils-0.5.0b3.tar.gz | 375.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| trackshapeutils-0.5.0b3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 751.2 kB
Release files / trackshapeutils-0.5.0b3.tar.gz
| Download URL | trackshapeutils-0.5.0b3.tar.gz |
|---|---|
| Size | 375.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0744625dcc18ce94c3e952706d47fa5186a0df57a98909c7bda11ba747e89fd1
|
|
BLAKE2b-256 checksum How to use checksums |
5d5060c083b4a6517881ff60fb15a783a1502b350a057cef41dc5ffbeec7aa6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.12.3
|
Release files / trackshapeutils-0.5.0b3-py3-none-any.whl
| Download URL | trackshapeutils-0.5.0b3-py3-none-any.whl |
|---|---|
| Size | 375.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
38b91402b307c266cf60569231522c147eb36d652a0ecea52c308e3ba3782b71
|
|
BLAKE2b-256 checksum How to use checksums |
d01003ec3538183e553b693f9fe9cb6868f89043697585a0a75dacaef0d69b9a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.12.3
|