top of page

Pipe Generator Tool (Full Blog)

  • Writer: Jesse Olchawa
    Jesse Olchawa
  • Jun 16
  • 18 min read

Updated: Jul 12

ree

Week 1)

To dive into doing more Houdini I’ve decided to complete a 4 week industry brief to create a spline-based pipe tool. I’m pretty confident in creating shaders but creating procedural geometry to support them will be even more fun. Let’s jump into analysing the brief requirements and expectations


Project Brief:

Project Brief
Project Brief

The project aims are to create a procedural pipe tool with shaders.

Requirements: tool needs to utilise metric units, come with documentation and good UI. Everything must be modelled and inserted or procedurally generated for geometry. For textures there is a max limit of 2 x 2048 maps which can be reduced to say 4x 1024. Tube designs must be rigid pipes and support placement of gauges, valves and other geometry. All this needs to be optimised for current generation, outputting data that is automatically named. Splines should also support intersection (T-junctions).


Planning and Timespan:

Starting Planner
Starting Planner

This is a 4-week brief however as I am writing this, I will be attending some events at the end of the month and have a lot of busy weekends means I want to ideally get it working before then. For now, the deadline is full competition for the 7th of July. I may leave documentation right till the end to ensure I can get the rest of the tool up. My schedule looks like the following on top of my fulltime job.


Research – Pipes

Research board for pipes
Research board for pipes

I needed to do some research into how pipes really work for easier modelling. I initially considered making them as modular kits to be inserted however this will be more costly in the long run especially for covering long distances. To alleviate this all geometry will be procedurally generated. To summarise my research, copper pipes which are my goal to recreate can be used in a ton of spaces but what really matters is inner thickness. Theres type L marked with blue, or type M marked with red which have different uses. Type L (remember this as less light L) is used for water lines in houses and Type M (lighter M) is used for heating, light water and gas stuff such as central heating.


Types of Copper Pipes
Types of Copper Pipes

So why does this even matter, I want this tool to allow the artist to change the thickness of the pipe to create either type or as stretch goals a shader that showcases the colouring would be great. I will also be aiming to hit my master references as my visual outcomes. Which means the generator needs to be able to spawn 90-degree elbows, T shaped half inch pieces (3-way connector), couplers, valves, t-bracket metal things and insulation overlay. To meet my brief, I can push some elements like brackets and insulation to stretch goals.


Research – Demos

This is my first rodeo to procedurally spawning geometry in Houdini. So, I have taken to researching a few demos to help me make some tools which I can build upon and improve for my brief. The first is a short video by Viktors Anfimovs (Anfimovs, 2020) that uses curves to a round tube sweep node. This immediately gave me insight on how I can create points to spawn flanges in between curved parts of the tube which was super helpful. However, there’s not a lot of control nor do I understand how I could add couplers to the mix, would I blast cut geometry around these corners or spawn another set of tubes that is slighter thicker at these intersections?


My second found demo which was a whole series created a bit longer ago in 2019 by Dokai (Dokai, 2019) was an absolute godsend. The organisation, UI setup, UV unwrapping and masking using spheres extruded which are then added to sweep to recreate the tube pipes. I am not kidding when I state that this series rewired the way I thought to create user friendly design for tools, doing some extra math to convert parameters into consistent scale values and sliders. I really loved this series and the outcome came out well minus a few issues I encountered due to nodes being updated featured below.


Making Pipes:

Curve for Pipes
Curve for Pipes

To make the pipes, the attached curve has corners smoothed using a controlled Polybevel. A circle is used with a sweep to create the tubes.

Final pipe geo
Final pipe geo

For making the corners/flanges, a subnet containing another circle is used but with polyextrude. If set with the toggle, a more jagged flanges can be spawned on corners. For isolating the corners of the original curve, a carve and group by range is used.

Making Corners
Making Corners

The carve is set to 0-1 to expose the entire curve and the group by range for 1-2 (offset 1) forces selection of every other point. This targets the corners as every straight ends up into a corner. Copy to points is then used to grab the flanges/corners to the cut outside sections.

Making Flanges
Making Flanges

For making the valve, the entire set is modelled out of extruded circles again. However unlike the demo I had some issues with making sure the locations the pieces were spawning in changing based on thickness. For example, if I adjusted the radius of the pope below the valve, the valve would become floating.

Broken Valve
Broken Valve

So to fix this I blasted off the top face of the valve and other components, then used a great bit of script by DPixel8R (2019) I found on Reddit to get the centre of the face. Then I used this as my spawn location on a transform node. This worked well!

Fixed Valve Spawner
Fixed Valve Spawner

UVS and Bugs:

As the tutorial is quite a bit old there are multiple nodes that work differently the core ones being sweep. I initially wanted to use sweeps UV methods to easily create UVS but found that the result was very scrunched up on corners. This would result in poor texel density in engine. I have some screenshots of my testing there:

Poor UVS
Poor UVS

To remedy this I used a script that Dokai thankfully covered in another video to wrangle manual UVS then fuse each point. This resulted in way smoother result.

Better UVS
Better UVS

A bug I haven’t been able to resolve yet is to do with the valve spawning system. Currently when a valve is spawned, by duplicating the previous Polybevel, carve then group in range blast to get rid of corners a curve is fixed to only spawn on straight sections. This entire bit of code is in a loop function called for each number.

Odd Placement
Odd Placement

It looks good right until execution where regardless the valve will ghost along the paths anyway. Despite the fact another carve further down limits the starting area forcefully chugging the valve along the path, the valve will still be allowed to be placed there. As it is not breaking the generator, moreso a inconvenience I will let this live.

Weird Bug
Weird Bug

Tool Progress in UE:

This is how the pipe generator looks and works so far in UE! I'm quite happy with the UI setup especially as it can stop generation until a good curve is set by using workmode toggle.



Next Steps:

I want to add further controls for more pipes being generated from the same splines, intersections (t connectors) and more parts in general. Alongside this I need to create better shaders for metal that showcase wear, rust and welding to a more realistic degree.

Week 2:

Redesigning UI:

Old List UI vs New Tabbed Layout
Old List UI vs New Tabbed Layout

I knew there was a lot of changes I needed to add such as override controls and material adjustments parameters so I redesigned the UI to help get a better foundation for adding these in. As shown the higher amount of toggles and drop-downs would mean swapping from the Simple to Tabbed view (on folders).

Folder and Parameter Structure
Folder and Parameter Structure

Additionally some sections could be moved around depending on how well they work such as the pipe details and pieces parameters. You can see the current implemented state here which is still subject to change as I add or remove features later. In this state however I should meet all brief requirements should the features work well later.

UI Overview after Redesign

Duplicating Pipes:

After reviewing my reference I realized that a lot of pipes are duplicated as heating systems need multiple lines to transfer water or gases up to a room. To make this process easier I added a optional toggle to duplicate the mesh through a for loop with iterations. To duplicate the pipes I use a for each node to grab the initial pipe and duplicate it on the specificed number of iterations. I can then multiply this value on a transform to offset it by grabbing "iteration" from the metadata.

Changing Offset Values for Duplicated Mesh

However I encountered a issue here with rotation as offsetting tall pipe structures on the same axis they face would make them clip into each-other. So I added rotation control too by exposing the transforms value and adding two extra transform nodes before/after the effect which can be toggled by a switch to transform after or before, preventing clipping.

Duplication Node Logic
Duplication Node Logic

To finalize this I used a toggle switch to add the original pipe back on for more variation using a merge node if needed.

Overlapping Pipes using Improper Offsetting
Overlapping Pipes using Improper Offsetting

Inserting Valves using Collision Box (and a ton of troubleshooting):

Spawn Logic (prior to cleanup)
Spawn Logic (prior to cleanup)

My hardest challenge so far was getting chosen parts to spawn where required. I started in Houdini giving boxes names such as "valve", "socket" and "bracket" then coloring boxes to spawn. After doing more research into how Vex works, checkout Vex isn't scary series it's a phenomenal watch!, (Nine Between, 2021) , I was able to grab the name and assign a number for the switch node to use using if statements. These temporary placeholder box colors worked well however I encountered a large issue when it came to consistently spawning actual geometry.

Float Bug for Repeating Geometry
Float Bug for Repeating Geometry

To spawn, I use a boolean to cutout the pipe and grab the center (centroid) of the mesh as a point. Then using copy to point I can spawn to that point the needed piece. However this lacked the normal direction of the curve below so I used a transfer node to grab the N value across, correcting the direction. At times this would bug out so I opted for a third attempt at booleaning the pipe piece out, centroid the mesh there fora point to use copy to points the needed piece. This worked most consistently so far.

Cleaner Spawn Logic and Results
Cleaner Spawn Logic and Results


Remodeling the Valve:

Valve Mesh Comparison
Valve Mesh Comparison

The valve I have at the moment is a direct recreation from the tutorial and overall has a more industrial design. Its a large valve, which doesn't really fit a smaller household set of heating copper pipes which I want the tool to generate. So I found a much smaller valve similar to a ball valve which I procedurally modelled using a similar setup. Below is a breakdown of all my nodes I kept it simple and spawned elements where they needed to be using blast and copy to point nodes for the ear parts of the valve for example.

Breakdown Procedural Valve Modelling Logic

Modelling Procedural Brackets:

Procedural Bracket
Procedural Bracket

I used very similar nodes to make the brackets. However to save on creating the bolts I duplicated it and removed a lot of division control. This is to prevent the bolts faces from being changed from hexagonal, the standard type of bolt. Furthermore the scale of the bolt is very small so the overall piece is low poly and needes scaling uniformly.

Procedurally Modelling Bracket

Modelling Sockets:

Socket Piece (length depends on input box size)
Socket Piece (length depends on input box size)

For modelling the socket I wanted artist control to make larger or smaller pieces depending on the bounding box. To create it I reuse the final pipe geometry and boolean it to the inputted box. There is a small tendancy for the boolean to go haywire and grab the entire pipe to extrude so for this portion I added a switch. If the extruded count is too high the socket is ignored using a bit of code for npoints node. This has helped resolve the issue and by adding more subdivisions to the pipe below being booleaned, the issue happens less frequently. Unlike the corner pieces, the socket is fitted closer to a t-junction piece and is welded for a water tight seal hence there being a lack of raised start/end points.

Procedural Socket Model Node Breakdown

For-Loop Placement in Engine (and more bugs!):

To make the tool easier to use I knew I wanted a easy way to spawn the pieces using some sort of names. In Houdini the tool worked well using the for each named piece node that looped based on different primitive names. This however did not work once in engine as the static meshes were all technically the same cube mesh but with different names ontop.

No Spawning (mesh name not found?)
No Spawning (mesh name not found?)

The most common thing shared between between cubes would be the material which I could luckily grab using a wrangle for unreal_material. I could then search for a keyword in the wrangle and assign it the correct switch number. This worked well until I tried adding duplicates cubes sharing the same mat in the same input slot which the graph just ended up ignoring.

Problem Areas of Loop
Problem Areas of Loop

I fixed this by adusting the for each loop function, as the primitives have the same name of unreal mat it would disregard those after it unless I used a connectivity node. This would automatically add a numerical class to all primitives inputted and worked well.


Correct Spawning (getting mat_name instead)
Correct Spawning (getting mat_name instead)

Honorable Mention Scale Bugs (base mesh logic scaled parts individually to match pipe size but this would break at smaller sizes) Now all pieces have been converted to uniform scaling.

ree

Next Steps:

I'm quite happy with the spawning systems so far and the new design for my user interface has helped shape what I need to add next. A crucial feature of pipes is their t-shape intersections to help connect varying pipes and flows together. This is a big element I need to add next to finalise the construction side of my generator before doing some shader work.


Week 3:

Creating T-Intersections:

One of the hardest challenges so far was making a self detecting intersection logic. The easiest way to set this up is to allow another curve input and then use a curve intersection node to cut out the areas where they cross however to make it easier for a artist to use I wanted the initial curve to be able to create intersections.

Creating T-Intersection Pieces
Creating T-Intersection Pieces

My first attempt I cut the curve out manually and then extruded the shape ontop to recreate the pipe. This ended up not working well as I had difficulties cutting the curve with a cube shape. I then tried booleaning the final pipe which worked much better as I could cut out the box area directly. Similar to how the sockets work.

ree

However I ended up encountering a weird bug when spawning the edge corners, the cutting would sometimes go haywire and blow up triangles. To avoid this event that happened every so often I added a switch n-points node to look for when values would go insane and stop the spawn altogether. I will fix this eventually but for now need to focus on finishing this tool.

Eliminating the Spaghetti Spawn Issue
Eliminating the Spaghetti Spawn Issue

Vertex Painting Setup

Original Vertex Painting Plan
Original Vertex Painting Plan

My initial plan was to vertex paint the pieces using the following values. However after importing them into engine I realised I was wasting a lot of valuable vertex data in masking.


Vertex Paint Values - Valve
Vertex Paint Values - Valve

For example the red mask could serve to divide the top colored metal from the bottom part using a one minus node to flip the two. I then had 2 more channels (3 if you count alpha in UE) to add more masks. I ended up using the color channels shown here instead.

Vertex Paint Values - Bracket
Vertex Paint Values - Bracket

I experimented with the measure node which I wrote a bit of VEX for to apply the selected groups as values in Cd. I eventually settled on packing curvature into blue for wear edge masking and randomised noise into green for further value variation. I knew these pieces would be small however I wanted artists to have control later in engine using shaders which is why curvature is important for grabbing the data between points for wear and tear.

Vertex Paint Values - Pipes
Vertex Paint Values - Pipes

Adding Materials for UE:

Assigning Materials from Unreal into Houdini
Assigning Materials from Unreal into Houdini

This was quite simple by pasting the references of my material instances into the unreal_mat node in Houdini. However the real troubles began when realising that there is a whole /game bit that is not needed by the referencing but would appear on a copy paste from a artist point of view. I wrote a bit of vex to get rid of this by splicing the string in two parts to remove the bit before but combine the rest again.

Making References from engine readable with Attribute Wrangle cleanup
Making References from engine readable with Attribute Wrangle cleanup


Fixing Bugs - Missing Vertex Painting

Missing Vertex Color Data
Missing Vertex Color Data

Another challenge was merging the vertex colors I applied. I struggled quite a bit with this as the output kept turning black and ended up missing in Unreal. I uncovered that the color nodes had actually reset my painting and so I had to blast of pieces of the mesh to combine. Further the Cd wrangles I was doing early were applying to primitive and not points, which the merge was struggling to combine. I swapped the tabs to run on points and added a attribute delete to remove any excess Cd data when merging to prevent overwritingpieces such as amy parts. I added all these pieces to a group called "nope" to stop the overwriting with a blast node.


Adding LODS:

Creating LODS
Creating LODS

It was quite quick and easily to add LODS, by using a poly reduce node I could then group this geometry as LOD_0 or 1 etc. When poly reducing the pipes a lot of weird results began to occur due to very harsh low poly nature of the pipe so I used the vertex group painting mask I made earlier to seperate the pieces to blast those off. I then could poly reduce these meshes and merge back. This cut the budget in 50/70% at times which proved highly useful in engine. I need to expose this as a parameter to allow artists more control on the reduction as a whole + distance of screenspace.


Adding Collision:

Collision Setup and Results in engine
Collision Setup and Results in engine

I initially tagged LOD0 as simple collision. This spawned a rather large box across the entire pipe. This is not ideal especially for corridors lined with pipes as the player would no longer be able to pass through. To make it playable collision I swapped to generating complex collision however will add a toggle should the artist be making more background pipes.


Fixing LOD Bug and Finalising UI:

Fixing LOD Bug
Fixing LOD Bug

At this point there was a lot of parametres I wanted to add to improve runtime experience. Such as spawning intersection pieces once before duplicating meshes or only after duplicating mesh (for unique placements) which I made swappable with a switch. Another feature was removing LOD spawning during initial construction as the preview would have all meshes intersecting into eachother, a odd bug that fixed itself on bake.


Final UI for Tool
Final UI for Tool

Here is the final UI layout:

I ended up cutting some toggles to do with the vertex painting direction to ensure my project was completed in time for the brief.


UI Breakdown in Houdini

Creating Shaders for Metal and Pieces

Packing Noises in Designer
Packing Noises in Designer

I began by drawing out the vertex colors I would need to mask in the shader. I realised that at first I was looking at the color values quite one dimensionally. Such as the red valve the vertex painting of white values on the valve and black on the metallic segments could be used as a mask for both rather than the green as well by inverting the mask with one minus. So for green and blue I packed a curvature map for edge wear and randomised gradients to breakup values. I could then use the alpha channel in engine as a paintable wear control to allow artist control. On the shader side I plugged in the varying vertex controls into LERPS to isolate colors paired on masks. I duplicated this for roughness and metallic.


Breaking Down Shader + Instances in Engine

In the brief I had a 2048 total texture limit so I split my noise channel packed textures into 2 1024s. This allowed me consistent noise and wear in 8 channels which I created using Substance Designer. Back to engine, I plugged this into the lerps to add noise to the white areas. I created parameters of floats to multiply the effect and intensity. Before plugging into the master blending lerps I saturated my results to prevent values above 0 to 1 which ended up in weird visual colors. For more control with switching off blending altogether I used static switch parameters which allowed a Boolean type toggle.


Week 4:

Creating Shader Instances

Shader Instances
Shader Instances

For easier artist drop in and drop out features I created instances for metallic copper, gold, silver and worn variants. These can also be overwritten in engine to create unique instances of metals. I have multiplied and grouped parameters to make them easier to understand from a general user point of view and will expand on them in documentation.


Documentation Button and PDF:


To make it easier to access I always upload my documentation to my site via a pdf. I tried to create a python script in the Houdini node graph but struggled to incorporate a button that would work and not cook entirely when pressed. That’s when I uncovered you can just incorporate python directly to the button on the overall type properties as long as its under scripts + named python module. I used pythons webbrowser to open browser to my inserted web page.

ree

For writing my documentation, I focused on my end user being a non-technical artist. So I used a lot of visual pictures and graphs to step by step showcase how to use the tool. I also structured the chapters by way of the tabs so the further you are in the tabs e.g. material referencing; the pages should align. I also included a troubleshooting section as I encountered some frequent bugs during cooking and intersecting pieces. These ended up being an odd recompile/unreal UI bug that made other inputs disappear but can be fixed with a bit of wrangling.


Usage in Environment:

Video Overview and Usafe in Scene

To demonstrate my tool in a real environment I migrated my HDA to Factory Environment Collection by Denys Rutkovskyi (2024). The industrial structure of the factory alongside metallic robot arms really blended well with my shaders.


Final Renders:

Here are all my final renders, breakdowns and videos!

Read my documentation here!

ree
ree
ree
ree
ree
ree
ree
ree

Project Post Mortem:

Project Post Mortem Banner
Project Post Mortem Banner

Note – I am writing this post Develop Brighton where I have obtained brilliant feedback from varying devs/technical artists. I am pleased with the project outcomes however on a critical scale can see that there is a lot of holes with this tool. Artist editability is quite limited on a part-by-part basis alongside finalised mesh size being quite large. There is more optimisation that needs to be applied to create better LODS, I have been advised to use methods such as directional culling to grab the location of the camera and cut out unnecessary back faces. Furthermore whilst this is my largest venture in Houdini, I had a large struggle with fixing bugs that occurred parsing data back and forth.

Future Plans
Future Plans

Not even mentioning the number of crashes that occurred. Whilst I really love Houdini and want to do more in it, I have been advised that PCG at least for Unreal Engine is being heralded as the next best thing in procedural generation. And I can understand why, its already in Unreal so grabbing classes, names, all sorts of data is not an issue. I think my next project is going to be based in PCG to dip my toe into this world, however the lessons I have learnt in Houdini will definitely carry through to this project.


To be fully honest I’m not 100% satisfied with this project but I have enjoyed the journey I have had learning Houdini. If I was to make it again I would focus on trying to breakup generations. Such as randomising the mat id per piece by counting primitives to make random metal pipes or exposing each part as an instance so they could be adjusted after generation. Small changes that if considered earlier would have impacted the tool in a much larger scale. I want to return in the future to add these features, but that will be an add-on update post brief. I will leave it here for now, and pop onto more Unreal! There is still so much to learn and I’m excited to dig in. Stay tuned!


Bibliography:

Dokai Tutorials (2019) Procedural Generated Pipe - Part 1: Generating Geometry, YouTube. Available at: https://www.youtube.com/watch?v=0Kk0tHp0Ax8 (Accessed: 12 June 2025).

Skill Builder (2022) How To Solder Copper Pipes Like A Pro, YouTube. Available at: https://www.youtube.com/watch?v=pl_Q2_hcs-8 (Accessed: 12 June 2025).

Viktors Anfimovs (2020) Creating procedural pipes in Houdini tutorial. (free HDA download), YouTube. Available at: https://www.youtube.com/watch?v=Ovj1m6pV1rE (Accessed: 12 June 2025).

Work Life (2022) Types of Copper Pipe, YouTube. Available at: https://www.youtube.com/watch?v=lyUxhWZoNCM (Accessed: 12 June 2025).

DPixel8R (2021) Reddit - The heart of the internet, Reddit.com. Available at: https://www.reddit.com/r/Houdini/comments/r5zht9/get_middle_points_on_any_geo_code_in_comments/ (Accessed: 13 June 2025).

bolegna (2020). Drive a switch with a detail. [online] od|forum. Available at: https://forums.odforce.net/topic/46745-drive-a-switch-with-a-detail/ [Accessed 15 June 2025].

Ellyfish (2018). Boolean subtract geometry from curves? [online] od|forum. Available at: https://forums.odforce.net/topic/32637-boolean-subtract-geometry-from-curves/page/2/ [Accessed 15 June 2025].

Oskar Świerad (2018). How to create vertex color masks for Unreal in Houdini – Tech Art Aid. [online] Tech Art Aid. Available at: https://techartaid.com/tutorials/store-data-in-vertex-color-using-houdini/ [Accessed 15 June 2025].

reddit (2021). Reddit - The heart of the internet. [online] Reddit.com. Available at: https://www.reddit.com/r/Houdini/comments/q0eqqv/vex_integer_to_float/ [Accessed 15 June. 2025].

sidefx (2006). How do you check to see if a curve intersects itself? | Forums | SideFX. [online] Sidefx.com. Available at: https://www.sidefx.com/forum/topic/6311/ [Accessed 15 June 2025].

sidefx (2017). Select primitives or edges by angle | Forums | SideFX. [online] Sidefx.com. Available at: https://www.sidefx.com/forum/topic/48424/?page=1#post-218882 [Accessed 15 June 2025].

sidefx (2018a). How remove letters from end of a string ? | Forums | SideFX. [online] Sidefx.com. Available at: https://www.sidefx.com/forum/topic/54924/?page=1#post-343200 [Accessed 15 June 2025].

sidefx (2018b). Quick Python: callback script fom button parm ? | Forums | SideFX. [online] Sidefx.com. Available at: https://www.sidefx.com/forum/topic/59236/ [Accessed 15 June 2025].

sidefx (2019). Attribute Value as Label? | Forums | SideFX. [online] Sidefx.com. Available at: https://www.sidefx.com/forum/topic/67955/ [Accessed15 June 2025].

sidefx (2021). How To Access Polygon Count And Set As An Integer Value | Forums | SideFX. [online] Sidefx.com. Available at: https://www.sidefx.com/forum/topic/82030/?page=1#post-352988 [Accessed 15 June 2025].

sidefx (2025a). Attributes and Groups. [online] Sidefx.com. Available at: https://www.sidefx.com/docs/houdini/unreal/attributes.html#utohinput [Accessed 15 June 2025].

sidefx (2025b). Attributes and Groups. [online] Sidefx.com. Available at: https://www.sidefx.com/docs/houdini/unreal/attributes.html#uetohoudini [Accessed 15 June 2025].

sidefx (2025c). foreach houdini documentation. [online] Sidefx.com. Available at: https://www.sidefx.com/docs/houdini/vex/functions/foreach.html [Accessed 15 June 2025].

Nine Between (2021). VEX Isn’t Scary - Part 1: Basics. [online] YouTube. Available at: https://www.youtube.com/watch?v=k4q1UQp4x6U [Accessed 12 June 2025].

sidefx (2025d). Measure. [online] Sidefx.com. Available at: https://www.sidefx.com/docs/houdini/nodes/sop/measure.html [Accessed 15 June 2025].

sidefx (2025e). Parameters. [online] Sidefx.com. Available at: https://www.sidefx.com/docs/houdini/unreal/parameters.html [Accessed 15 June 2025].

sidefx (2025f). Strings. [online] Sidefx.com. Available at: https://www.sidefx.com/docs/houdini/vex/strings.html [Accessed 15 June 2025].

Sidefx.com. (2025). Arrays. [online] Available at: https://www.sidefx.com/docs/houdini/vex/arrays.html#declaring-array-types [Accessed 15 June 2025].

Fab (2025). Fab. [online] Fab.com. Available at: https://www.fab.com/listings/2ee66462-8c2b-4303-892c-83f7fc0d9b3e [Accessed 12 June 2025].

Comments


© 2025-2026 Jesse Olchawa

The content provided on this website cannot be utilised for any AI training or data gathering purposes!

bottom of page