Creating Scripts to Automate stuff in Blender and maybe even OpenRails

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • pwillard
    3000 Post Veteran



    • Nov 1999
    • 3070
    • Cumming Ga
    • ORTS

    #1

    Creating Scripts to Automate stuff in Blender and maybe even OpenRails

    See Title - This topic has a tiny audience. At least, that is my assumption.

    While I'm not going to add any restrictions to this thread myself, I'm hoping we share helpful stuff that sometimes lives only on our local storage devices that could benefit others.

    For example, Blender uses a version of Python as its add-on programming language, allowing you to write simple scripts to solve problems or handle repetitive tasks as well. You don't necessarily need to write full-fledged add-ons, which come with their own strict formatting requirements.

    This topic is also ideal for discussing existing add-ons/extensions that can be used with Blender, should we need to discuss "what's out there" already.

    There are multiple entries about scripts in https://github.com/pwillard/ebook-OR...C-Notebook.pdf, for example.



    I know Scott and I are not the only ones writing Python scripts, so now is probably a good time to speak up.





    http://www.railsimstuff.com
    Just Blender now, 3DCrafter only when I have to.
    Formerly The Keystone Works (All Permissions Granted)
    Things I work on: https://github.com/pwillard
  • pgroenbaek
    Member
    • Jan 2024
    • 98
    • Denmark
    • ORTS

    #2
    I have some examples laying around on my GitHub (not all of the scripts are properly configured tho).

    One of the things I've done is automating an export of about a thousand mileposts and then generating the textures from GIMP.

    GIMP has python scripting available too. You can't export .ace directly from GIMP but you can export to .png and then call AceIt in python afterwards using the subprocess module.

    I also have a script laying around there for calculating the .sd bounding box within Blender.

    Maybe this is useful for some of you.
    - Peter

    Route Projects: DK24
    Utilities and Tools: ShapeIO, ShapeEdit, TrackshapeUtils

    Comment

    • scottb613
      2500 Post Veteran


      • Apr 2003
      • 2848
      • Downeast Maine (soon)
      • ORTS

      #3
      Hi Pete,

      Great Idea.

      I had started this script when the Align Tool went haywire. Full disclosure - I do use Chat GPT to help write the Python Scripts - which still takes some work. I do know Unix Scripting - so - that helps when you want to get a working program out of Chat GPT.


      SCO Butt Objects

      A Blender tool for aligning selected objects to either an active object or the 3D Cursor. You can align objects along the X, Y, or Z axis to the minimum, maximum, or center bounds of the reference. The tool also supports inside and outside alignment modes for added flexibility. It retrieves the 3D Cursor position when used, ensuring accurate placement in the event you move the cursor. The tool is located in the Item tab of the 3D Viewport Sidebar, making it easy to access and use during your workflow.


      Align/Butt To (Objects)
      • Selected Active Object
      • 3D Cursor
      • Processes Multiple Objects in a Single Operation
      • X, Y, or Z Axis
      • Bounds Extent: Min, Ctr, or Max
      • Bounds Position: Inside or Outside

      BLENDER ADDON File Attached Below (Install as you would the MSTS/ORTS exporter - [From File] )

      Suggest you throw down a bunch of cubes and play to see how it works.



      Butt.jpg


      Regards,
      Scott


      ​
      Attached Files
      Last edited by scottb613; 12-06-2024, 12:36.
      <a href=https://www.trainsim.com/forums/filedata/fetch?filedataid=80663&type=full title=thumb_80663.png >thumb_80663.png</a>​ My Blender Models

      Comment

      • pwillard
        3000 Post Veteran



        • Nov 1999
        • 3070
        • Cumming Ga
        • ORTS

        #4
        Quick Peanut Gallery Comment here regarding
        Full disclosure - I do use Chat GPT to help write the Python Scripts
        This is something we should all do and not feel guilty about. I can't imagine we have professional Python coders here (willing to be wrong). It's a tool much like your favorite pocket knife or screwdriver, so you *should* use it, even if it just gets you over the hurdle of "where do I start?".

        NOTE: I will admit that Mr. Campbell's MSTS exporter S file exporter for Blender is a serious bit of Python coding ingenuity.


        pgroenbaek - Do we still need to calculate the bounding box for Open Rails? I am trying to remember.

        Last edited by pwillard; 12-06-2024, 12:34.
        http://www.railsimstuff.com
        Just Blender now, 3DCrafter only when I have to.
        Formerly The Keystone Works (All Permissions Granted)
        Things I work on: https://github.com/pwillard

        Comment

        • pgroenbaek
          Member
          • Jan 2024
          • 98
          • Denmark
          • ORTS

          #5
          Originally posted by pwillard
          Quick Peanut Gallery Comment here regarding

          pgroenbaek - Do we still need to calculate the bounding box for Open Rails? I am trying to remember.
          No Open Rails seems to do just fine for static objects regardless of what it is set to. I don't even think Open Rails complains when the sd files are missing completely (but don't hang me up on that).

          But I like to add it anyway. because then the bounding box is displayed correctly in TSRE5.
          Last edited by pgroenbaek; 12-06-2024, 12:53.
          - Peter

          Route Projects: DK24
          Utilities and Tools: ShapeIO, ShapeEdit, TrackshapeUtils

          Comment

          • pwillard
            3000 Post Veteran



            • Nov 1999
            • 3070
            • Cumming Ga
            • ORTS

            #6
            So, as a continuation of a discussion we had about the lack of a "3D CANVAS" feature we liked called "BUTT OBJECTS" which will move an object up against another, I asked CHATGPT to help out... as an exercise to show how you can get help to solve a problem.

            With a couple of guidance prompts, here's what I got back...

            Code:
            import bpy
            import mathutils
            from mathutils import Vector
            
            def get_edges_world_coordinates(obj):
                """
                Retrieve edges in world-space coordinates.
                """
                edges = []
                mesh = obj.data
                obj_matrix = obj.matrix_world  # Convert to world space
            
                # Ensure the mesh is up to date
                obj.update_from_editmode()
            
                for edge in mesh.edges:
                    vert1 = obj_matrix @ mesh.vertices[edge.vertices[0]].co
                    vert2 = obj_matrix @ mesh.vertices[edge.vertices[1]].co
                    edges.append((Vector(vert1), Vector(vert2)))
                return edges
            
            def find_closest_edges(edges1, edges2):
                """
                Find the pair of edges (one from each list) that are closest to each other.
                Returns the two closest edges and their distance.
                """
                closest_distance = float("inf")
                closest_pair = None
            
                for edge1 in edges1:
                    for edge2 in edges2:
                        # Compute the minimum distance between all vertices in edge pairs
                        for v1 in edge1:
                            for v2 in edge2:
                                distance = (v1 - v2).length
                                if distance < closest_distance:
                                    closest_distance = distance
                                    closest_pair = (edge1, edge2)
                return closest_pair, closest_distance
            
            def compute_edge_midpoint(edge):
                """
                Compute the midpoint of an edge.
                """
                return (edge[0] + edge[1]) / 2
            
            def edge_vector(edge):
                """
                Compute the vector of an edge.
                """
                return edge[1] - edge[0]
            
            def align_object_to_target_no_overlap(obj1, obj2):
                """
                Align obj1 to obj2 so that their closest edges touch but do not overlap.
                """
                # Get edges in world coordinates
                edges1 = get_edges_world_coordinates(obj1)
                edges2 = get_edges_world_coordinates(obj2)
            
                # Find the closest pair of edges
                closest_edges, closest_distance = find_closest_edges(edges1, edges2)
            
                if not closest_edges:
                    print("No edges found to align.")
                    return
            
                edge1, edge2 = closest_edges
            
                # Compute midpoints of the closest edges
                midpoint1 = compute_edge_midpoint(edge1)
                midpoint2 = compute_edge_midpoint(edge2)
            
                # Compute the edge vectors
                vector1 = edge_vector(edge1)
                vector2 = edge_vector(edge2)
            
                # Compute the direction vector for alignment
                direction = (midpoint2 - midpoint1).normalized()
            
                # Compute distances to avoid overlap
                edge1_length = vector1.length
                edge2_length = vector2.length
                offset_distance = edge1_length / 2 + edge2_length / 2
            
                # Move the object by the alignment vector with an offset to prevent overlap
                move_vector = (midpoint2 - midpoint1) + direction * offset_distance
            
                # Apply the translation to the object in world space
                obj1.location += move_vector
                print(f"Object {obj1.name} moved by {move_vector} to align with {obj2.name} without overlap.")
            
            def main():
                selected_objects = bpy.context.selected_objects
                if len(selected_objects) != 2:
                    print("Please select exactly two objects!")
                    return
            
                obj1, obj2 = selected_objects
                align_object_to_target_no_overlap(obj1, obj2)
            
            # Run the script
            main()
            ​
            Assorted Blender Code Snippets and such. Contribute to pwillard/Blender-Code development by creating an account on GitHub.




            BTW: I don't recommend "butt objects" as a search term.
            Last edited by pwillard; 12-06-2024, 13:18.
            http://www.railsimstuff.com
            Just Blender now, 3DCrafter only when I have to.
            Formerly The Keystone Works (All Permissions Granted)
            Things I work on: https://github.com/pwillard

            Comment

            • rdayt
              1000 Post Veteran

              • Nov 2007
              • 1291
              • SW MO
              • ORTS

              #7
              Just a note about .SD files, I only use them to turn on the lights.

              Randy

              Comment

              • scottb613
                2500 Post Veteran


                • Apr 2003
                • 2848
                • Downeast Maine (soon)
                • ORTS

                #8
                Hi Pete,

                I posted a working addon for Butt Objects - on my initial post - it's attached.


                Regards,
                Scott
                <a href=https://www.trainsim.com/forums/filedata/fetch?filedataid=80663&type=full title=thumb_80663.png >thumb_80663.png</a>​ My Blender Models

                Comment

                • pwillard
                  3000 Post Veteran



                  • Nov 1999
                  • 3070
                  • Cumming Ga
                  • ORTS

                  #9
                  Well, I was trying to show off Chatgpt there... not creating a missing tool. (I just needed a quick topic)

                  I should share some of the prompts I went through.

                  Generate a blender python script that takes an object and positions it so that it butts up to a selected object using the closest edges.
                  Fail: Tried to use a deprecated feature no longer available

                  The script runs now, but the first object doesn't move to the second objects nearest edge

                  FAIL: Failed to do anything

                  it doesn't work
                  FAIL: Ok, better. But i don't want any vertices from object1 to overlap with object2.

                  OK, that seems to work.
                  And that's how we got the code we ended up with and also to show that CHATGPT *will* get it wrong a few times before you see success.

                  Also, to be clear... this example is writing code that executes in the scripting Window, not a nice and pretty add-on that integrates like Scott's.


                  If you read the code you get back from Chatgpt, you can see the amount of typing it saved you and the fact that this 3D stuff is kind of a Black Art that I assume not all of us are very familiar with.

                  For Example, I feel like John Snow with regards to the paragraph below (I know nothing) :
                  In 3D graphics, matrix math is used to represent and perform linear transformations like rotation, scaling, translation, and projection on 3D points and objects, allowing for efficient manipulation of their positions and orientations within a scene, all through the power of matrix multiplication to combine multiple transformations into one operation
                  ​

                  ========================

                  But I like to add it anyway. because then the bounding box is displayed correctly in TSRE5.
                  I did not know that... A good thing to remember.
                  Last edited by pwillard; 12-06-2024, 13:53.
                  http://www.railsimstuff.com
                  Just Blender now, 3DCrafter only when I have to.
                  Formerly The Keystone Works (All Permissions Granted)
                  Things I work on: https://github.com/pwillard

                  Comment

                  • scottb613
                    2500 Post Veteran


                    • Apr 2003
                    • 2848
                    • Downeast Maine (soon)
                    • ORTS

                    #10
                    Hi Pete,

                    LOL - my bad... Sorry.


                    Regards,
                    Scott
                    <a href=https://www.trainsim.com/forums/filedata/fetch?filedataid=80663&type=full title=thumb_80663.png >thumb_80663.png</a>​ My Blender Models

                    Comment

                    • pwillard
                      3000 Post Veteran



                      • Nov 1999
                      • 3070
                      • Cumming Ga
                      • ORTS

                      #11
                      It would be interesting to collect some of these scripts discussed here and create a single ADD-ON that contains all of them, with a name like TrainSimTools.

                      http://www.railsimstuff.com
                      Just Blender now, 3DCrafter only when I have to.
                      Formerly The Keystone Works (All Permissions Granted)
                      Things I work on: https://github.com/pwillard

                      Comment

                      • pwillard
                        3000 Post Veteran



                        • Nov 1999
                        • 3070
                        • Cumming Ga
                        • ORTS

                        #12
                        One of the things we need set up correctly with the Blender exporter is the proper Collections configuration so the Exporter knows where to find what we want to export as an S file. The exporter's expectations are reasonable, but the setup needs to be done right for it to work.

                        Here is an example of a script that sets up the basics for the exporter:

                        Code:
                        import bpy
                        
                        # Function to create a new collection if it doesn't exist
                        def create_collection(name, parent_collection=None):
                            if name not in bpy.data.collections:
                                new_collection = bpy.data.collections.new(name)
                                if parent_collection:
                                    parent_collection.children.link(new_collection)
                                else:
                                    bpy.context.scene.collection.children.link(new_collection)
                                return new_collection
                            else:
                                return bpy.data.collections[name]
                        
                        # Create the main collection "MAIN"
                        main_collection = create_collection("MAIN")
                        
                        # Create the sub-collection "MAIN_700" under "MAIN"
                        sub_collection = create_collection("MAIN_700", parent_collection=main_collection)
                        
                        print(f"Created collections: {main_collection.name}, {sub_collection.name}")
                        
                        sub_collection = create_collection("MAIN_2000", parent_collection=main_collection)
                        
                        print(f"Created collections: {main_collection.name}, {sub_collection.name}")​
                        Yes, it's just just a few steps, but it does it properly, using uppercase correctly.
                        http://www.railsimstuff.com
                        Just Blender now, 3DCrafter only when I have to.
                        Formerly The Keystone Works (All Permissions Granted)
                        Things I work on: https://github.com/pwillard

                        Comment

                        • eric
                          9500 Post Veteran and Administrator



                          • Dec 2002
                          • 9583
                          • NS MP245.4 CNO&TP South District
                          • English, SQL
                          • General

                          #13
                          These are a few things I've been working with -- it uses the trackshape.py script created by Doug Jones, and I've been using that framework to develop shapes to go under various track systems.

                          I'd previously built all of these in Sketchup before, which is where I had the measurements to use.

                          They're essentially the same concept as the TrProfile used by OpenRails and the profiles used in Dynatrax, with a slightly different format. That allows Blender to perform extrusions that follow the track geometry defined in the TSection.dat (which is also saved as a JSON file piece by piece).

                          capture_5100105a.jpg capture_5100105b.jpg capture_5100105c.jpg capture_5100105d.jpg

                          The JSON itself is rather meaningless for copying, but essentially it uses polyline definitions that create either a mesh or a face.

                          Parts defined with "end" in are what get rendered as a face at the start and end of the track geometry centerline, parts defined as "wall" get rendered as the leftmost and rightmost items when there is more than one centerline (e.g. a 2T or 3T section). Items simply labeled as "ballast" get replicated for each centerline.

                          In the example below, there are three sets of beams under the rails (ballast) with corresponding ends, a left wall and right wall, and left and right ends to close off the beams.

                          capture_5100106.jpg

                          This is an example of the JSON -- it's for the station viaduct at the top with the platform and the fence... probably the most complex one I've done next to the girder bridge...

                          Code:
                          {
                           "Name": "DigitalRails.com berm profile",
                           "gauge": 1.435,
                           "railhead": 0.072,
                           "flangeway": 0.050,
                           "parts": true,
                           "LODs": [
                             {
                            "CutoffRadius": 2000,
                             "Name": "Ballast",
                             "TexName": "upred.ace",
                             "ShaderName": "TexDiff",
                             "LightModelName": "OptSpecular0",
                             "MipMapLevelOfDetailBias": -1,
                             "Polylines": [ {
                              "Name": "ballast",
                              "part": "ballast",
                              "DeltaTexCoord": [ 0, 0.219 ],
                              "Vertices": [
                               { "Position": [ -3.35, 0.00 ], "TexCoord": [ -0.54, 0 ]  },      
                               { "Position": [     0, 0.00 ], "TexCoord": [ 0, 0 ]  },    
                               { "Position": [  3.35, 0.00 ], "TexCoord": [0.54, 0 ]  }    
                              ]
                             } ]
                           }
                          ,
                            {
                            "CutoffRadius": 2000,
                             "Name": "Wall",
                             "TexName": "stone2.ace",
                             "ShaderName": "TexDiff",
                             "LightModelName": "OptSpecular0",
                             "MipMapLevelOfDetailBias": -1,
                             "Polylines": [ {
                              "Name": "leftwall",
                              "part": "leftwall",
                              "DeltaTexCoord": [ 0.2, 0.0 ],
                              "Vertices": [
                               { "Position": [ 3.0, -20.00 ], "TexCoord": [  0, 1  ]  },
                               { "Position": [ 0, -20 ], "TexCoord": [0, 0 ]  },          
                               { "Position": [ -3.35, -20.0 ], "TexCoord": [ 0, 1 ]  },    
                               { "Position": [ -3.35, -0.07 ], "TexCoord": [0, 0 ]  }    ,
                               { "Position": [ -3.35,  0.0 ], "TexCoord": [ 0, 0.01 ]  },  
                               { "Position": [ -3.25,  0.0 ], "TexCoord": [  0 , 0 ]  },          
                               { "Position": [ -3.25, -0.07 ], "TexCoord": [ 0, 0.01 ]  }    
                              ]
                             } ]
                           }
                           ,
                             {
                            "CutoffRadius": 2000,
                             "Name": "Wall",
                             "TexName": "stone2.ace",
                             "ShaderName": "TexDiff",
                             "LightModelName": "OptSpecular0",
                             "MipMapLevelOfDetailBias": -1,
                             "Polylines": [ {
                              "Name": "rightwall",
                              "part": "rightwall",
                              "DeltaTexCoord": [ 0.2, 0.0 ],
                              "Vertices": [
                               { "Position": [ 3.25, -0.07 ], "TexCoord": [ 0, 0.01 ]  },      
                               { "Position": [ 3.25,  0.0 ], "TexCoord": [  0 , 0 ]  },      
                               { "Position": [ 3.35,  0.0 ], "TexCoord": [ 0 , 0.01  ]  },      
                               { "Position": [ 3.35, -0.07 ], "TexCoord": [  0 , 0  ]  },      
                               { "Position": [ 3.35, -20.00 ], "TexCoord": [  0 , 1   ]  },
                               { "Position": [ 0, -20], "TexCoord": [ 0 , 0  ]  },          
                               { "Position": [ -3, -20.0 ], "TexCoord": [ 0 , 1  ]  }
                              ]
                             } ]
                           },
                            {
                            "CutoffRadius": 2000,
                             "Name": "endballast",
                             "TexName": "stone2.ace",
                             "ShaderName": "TexDiff",
                             "LightModelName": "OptSpecular0",
                             "MipMapLevelOfDetailBias": -1,
                             "Polylines": [ {
                              "part": "end",
                              "DeltaTexCoord": [ 0, 0 ],
                              "Vertices": [
                               { "Position": [ 3.35, 0 ], "TexCoord": [ 1, 0 ] },
                               { "Position": [ -3.35, 0 ], "TexCoord": [ 0, 0 ] },
                               { "Position": [ -3.35, -20 ], "TexCoord": [ 0, 1 ] },
                               { "Position": [ 3.35, -20 ], "TexCoord": [ 1, 1 ] },
                               { "Position": [ 3.35, 0 ], "TexCoord": [ 1, 0 ] }
                              ]
                             } ]
                           }
                          ,
                             {
                            "CutoffRadius": 2000,
                             "Name": "Ballast",
                             "TexName": "asphalt_new.ace",
                             "ShaderName": "TexDiff",
                             "LightModelName": "OptSpecular0",
                             "MipMapLevelOfDetailBias": -1,
                             "Polylines": [ {
                              "Name": "ballast",
                              "part": "ballast",
                              "DeltaTexCoord": [ 0, 0.219 ],
                              "Vertices": [
                               { "Position": [ -1.3028, 0.3048 ], "TexCoord": [1, 1 ]  },
                               { "Position": [ -1.1507, 0.3048 ], "TexCoord": [ 0, 0 ]  }
                              ]
                             } ]
                           }
                          ,
                             {
                            "CutoffRadius": 2000,
                             "Name": "Ballast",
                             "TexName": "asphalt_new.ace",
                             "ShaderName": "TexDiff",
                             "LightModelName": "OptSpecular0",
                             "MipMapLevelOfDetailBias": -1,
                             "Polylines": [ {
                              "Name": "ballast",
                              "part": "ballast",
                              "DeltaTexCoord": [ 0, 0.219 ],
                              "Vertices": [
                               { "Position": [ -3.3551, 0.3048 ], "TexCoord": [1, 1 ]  },
                               { "Position": [ -1.4552, 0.3048 ], "TexCoord": [ 0, 0 ]  }
                          
                              ]
                             } ]
                           }
                          ,
                             {
                            "CutoffRadius": 2000,
                             "Name": "Ballast",
                             "TexName": "noisy-white.ace",
                             "ShaderName": "TexDiff",
                             "LightModelName": "OptSpecular0",
                             "MipMapLevelOfDetailBias": -1,
                             "Polylines": [ {
                              "Name": "ballast",
                              "part": "ballast",
                              "DeltaTexCoord": [ 0, 0.219 ],
                              "Vertices": [
                               { "Position": [ -1.4552, 0.3048 ], "TexCoord": [1, 1 ]  } ,
                               { "Position": [ -1.3028, 0.3048 ], "TexCoord": [ 0, 0 ]  }
                              ]
                             } ]
                           }
                          ,
                             {
                            "CutoffRadius": 2000,
                             "Name": "Ballast",
                             "TexName": "woodgrain.ace",
                             "ShaderName": "TexDiff",
                             "LightModelName": "OptSpecular0",
                             "MipMapLevelOfDetailBias": -1,
                             "Polylines": [ {
                              "Name": "ballast",
                              "part": "ballast",
                              "DeltaTexCoord": [ 0, 0.219 ],
                              "Vertices": [
                               { "Position": [ -1.1507, 0.3048 ], "TexCoord": [1, 1 ]  },
                               { "Position": [ -1.1507, -0.20 ], "TexCoord": [ 0, 0 ]  }
                              ]
                             } ]
                           }
                          ,
                             {
                            "CutoffRadius": 2000,
                             "Name": "Ballast",
                             "TexName": "woodgrain.ace",
                             "ShaderName": "TexDiff",
                             "LightModelName": "OptSpecular0",
                             "MipMapLevelOfDetailBias": -1,
                             "Polylines": [ {
                              "Name": "ballast",
                              "part": "ballast",
                              "DeltaTexCoord": [ 0, 0.219 ],
                              "Vertices": [
                               { "Position": [ -3.3551, -0.20 ], "TexCoord": [1, 1 ]  },
                               { "Position": [ -3.3551, 0.3048 ], "TexCoord": [ 0, 0 ]  }
                              ]
                             } ]
                           },
                            {
                            "CutoffRadius": 2000,
                             "Name": "Ballast",
                             "TexName": "woodgrain.ace",
                             "ShaderName": "TexDiff",
                             "LightModelName": "OptSpecular0",
                             "MipMapLevelOfDetailBias": -1,
                             "Polylines": [ {
                              "part": "end",
                              "DeltaTexCoord": [ 0, 0 ],
                              "Vertices": [
                          
                               { "Position": [ -3.3551, -0.20 ], "TexCoord": [   0, 1  ]  },
                               { "Position": [ -1.1507, -0.20 ], "TexCoord": [  1, 1  ]  },
                               { "Position": [ -1.1507, 0.3048 ], "TexCoord": [  1, 0  ]  },
                               { "Position": [ -3.3551, 0.3048 ], "TexCoord": [  0, 0  ]  },
                               { "Position": [ -3.3551, -0.20 ], "TexCoord": [   0, 1  ]  }
                              ]
                             } ]
                           }
                          ,
                             {
                            "CutoffRadius": 2000,
                             "Name": "Ballast",
                             "TexName": "Fencing_metal_straight.ace",
                             "ShaderName": "BlendATexDiff",
                             "LightModelName": "OptSpecular0",
                             "MipMapLevelOfDetailBias": -1,
                             "Polylines": [ {
                              "Name": "ballast",
                              "part": "rightwall",
                              "DeltaTexCoord": [ 1, 0 ],
                              "Vertices": [
                               { "Position": [ -3.35, 0.0008 ], "TexCoord": [ 0, 0 ]  },      
                               { "Position": [ -3.35, 1.3048 ], "TexCoord": [ 0, 1 ]  },    
                               { "Position": [ -3.35, 1.3048 ], "TexCoord": [ 0, 1 ]  },    
                               { "Position": [ -3.35, 0.0008 ], "TexCoord": [ 0, 0 ]  }          
                              ]
                             } ]
                           }
                           
                           ]
                          }
                          ​
                          With one profile, I can run a batch script to generate 1000+ iterations with different dimensions and geometry, creating a 1:1 ratio of trackshapes to scenery objects.

                          Yeah, it's probably overkill to have that many variations, but it creates a more seamless look when they're paired up.

                          This was my use case for the extruded platforms, which grew into the bridges and viaducts:

                          capture_5100108.jpg capture_5100109.jpg

                          Trying to create the platforms for this in 2010 was a nightmare. Today, it's like a Lego set.
                          Last edited by eric; 12-27-2024, 11:33.
                          If you like what you see here at Trainsim.com, be it the discussions and knowledge in the forums, or items available in our library, I hope you'll consider a paid membership to help support keeping the site operating.... Thanks!

                          Comment

                          • pwillard
                            3000 Post Veteran



                            • Nov 1999
                            • 3070
                            • Cumming Ga
                            • ORTS

                            #14
                            Ok, that is very cool. I guess I'm hardly that ambitious.

                            But I am trying stuff out... Like a "get a project started" tool I'm calling "bogey positioner" at the moment. You answer some questions and it places some items in your scene. VERY unfinished at the moment and I really just wanted to see how hard it was to have some input fields and then "do stuff".

                            Screenshot 2024-12-31 154101.jpg
                            http://www.railsimstuff.com
                            Just Blender now, 3DCrafter only when I have to.
                            Formerly The Keystone Works (All Permissions Granted)
                            Things I work on: https://github.com/pwillard

                            Comment

                            • pwillard
                              3000 Post Veteran



                              • Nov 1999
                              • 3070
                              • Cumming Ga
                              • ORTS

                              #15
                              Something I need to do often, and got tired of firing up GIMP or Paint.net

                              Code:
                              #-------------------------------------------------------------------------------
                              # Name:        png2dds.py
                              # Purpose:     quickly convert PNG files to DDS
                              #
                              # Author:      willard
                              #
                              # Created:     24/01/2025
                              # Licence:     Public Domain
                              #-------------------------------------------------------------------------------
                              import os
                              from wand import image
                              
                              # Iterate through all files in the current directory
                              for filename in os.listdir('.'):
                                  # Check if the file is a PNG file
                                  if filename.lower().endswith('.png'):
                                      with image.Image(filename=filename) as img:
                                          # Set the compression to DXT5
                                          img.compression = "dxt5"
                              
                                          # Create the output DDS filename by replacing the extension
                                          output_filename = os.path.splitext(filename)[0] + '.dds'
                                          # Save the image in DDS format
                                          img.save(filename=output_filename)
                                          print(f"Converted {filename} to {output_filename}")

                              `
                              http://www.railsimstuff.com
                              Just Blender now, 3DCrafter only when I have to.
                              Formerly The Keystone Works (All Permissions Granted)
                              Things I work on: https://github.com/pwillard

                              Comment

                              Working...