5. 3D Geometries#
In this tutorial we will describe how to create 3 dimensional structures that are based on one or more 2d profiles.
We assume that you already know how to create 2d profiles using the
If this is not the case, please read the corresponding tutorial first.
You will learn:
how to define a 3 dimensional geometry using the
how to specify geometries with varying cross sections using the
Before we start, run the following code cells to include everything we need.
import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import import weldx.transformations as tf from weldx import Q_ from weldx.geometry import ( Geometry, LinearHorizontalTraceSegment, Profile, RadialHorizontalTraceSegment, Shape, Trace, VariableProfile, linear_profile_interpolation_sbs, ) from weldx.transformations import LocalCoordinateSystem
Trace class describes an arbitrary path through the 3 dimensional space.
It is build from multiple segments that need to be passed to it when we create the
Currently there are 2 segment types available: the
LinearHorizontalTraceSegment that represents a straight line and
RadialHorizontalTraceSegment which describes a circular path.
Both segment types have in common that the
z-value remains constant and that they are free from torsion.
Let’s create one instance of each segment type.
All you need to specify when creating a
LinearHorizontalTraceSegment is its length:
line_segment = LinearHorizontalTraceSegment(length=Q_(10, "cm"))
RadialHorizontalTraceSegment needs a little bit more information.
You need to provide its radius, rotation angle and if the rotation is clockwise or counter-clockwise:
arc_segment = RadialHorizontalTraceSegment( radius=Q_(15, "cm"), angle=Q_(90, "degree"), clockwise=True )
Now that we have some segments, we can create a
It expects a single segment or a list of segments as first argument.
The individual segments get attached to each other in the same order as they are attached to the list.
Each segment except for the first one gets its start orientation and coordinates from the end of the previous segment.
The initial orientation and coordinates of the
Trace can be provided in form of a
LocalCoordinateSystem as optional
second argument during the creation of the class instance.
If you omit the second parameter, the
Trace starts at the origin and is oriented in positive x-direction.
Here is an example:
trace = Trace([line_segment, arc_segment, line_segment])
We can plot our
Trace using the
Let’s provide a initial coordinate system that is rotated by 45 degrees around the y-axis and see how that affects the result:
lcs_rot = LocalCoordinateSystem( tf.WXRotation.from_euler("y", Q_(45, "degree")).as_matrix() ) trace_rot = Trace([line_segment, arc_segment, line_segment], lcs_rot) trace_rot.plot(raster_width=Q_(2, "cm"))
Linear and radial trace segments already cover a lot of use cases but they are far from being enough to cover all possible needs. If you need more than those two basic segment types, there are two options. The first one is to implement your own segment type. This is discussed in detail in another tutorial.
TODO: Add link to tutorial once it is written
The second option, that you should prefer is the
It enables you to describe the course of the trace segment with a mathematical function.
TODO: Continue once the segment type is implemented
5.3. Describing a 3d geometry#
The simplest possible way to describe a 3 dimensional geometry using the
weldx.geometry package is to create a 2d
Profile and extrude it into 3d.
The class that performs this action is called
It expects a
Profile that defines the cross section as first argument and a
Trace that describes
the extrusion path as second one.
Since we already have created a
Trace all that is missing is a
So let’s create one:
shape_right = Shape().add_line_segments( Q_([[10, 8], [5, 8], [1, 2], [1, 0], [10, 0]], "cm") ) shape_left = shape_right.reflect_across_line(Q_([0, 0], "cm"), Q_([0, 1], "cm")) profile = Profile([shape_left, shape_right]) profile.plot(raster_width="1cm")
Now we can create and plot our
geometry = Geometry(profile=profile, trace_or_length=trace) geometry.plot( profile_raster_width=Q_(0.25, "cm"), trace_raster_width=Q_(10, "cm"), show_wireframe=False, ) trace.plot(raster_width=Q_(1, "cm"), axes=plt.gca(), fmt="r-")
5.4. Geometries with varying cross section#
VariableProfile class gives us the opportunity to handle 3 dimensional geometries with a varying cross section.
It can be used instead of a
Profile during the creation of the
Three pieces of information are necessary to create a
First a list of profiles is required. This list represents “snapshots” of known cross sections along the extrusion path.
The second piece of information is a list of numbers that has the same number of elements as the list of profiles.
Each of those numbers defines a coordinate on a 1 dimensional axis for the profile with the same list index.
Their magnitude can be chosen arbitrarily with the exception that the first entry has to be 0 and the list items must be
in ascending order.
Due to the previously mentioned association between the coordinates/numbers and the profiles, the profiles are ordered
by ascending coordinates.
When you pass a
VariableProfile to a
Geometry, its first profile is used as the initial profile at the start of
Trace and the last as the one at the end.
This relation enables the
Geometry class to establish a linear coordinate transformation between the coordinates you
specified in the
VariableProfile and the corresponding position on the trace.
For example, if you use three profiles with the coordinates
[0, 25, 100] and your
Trace has a length of $40 mm$,
then the second profile occurs after $10 mm$ on the
The last thing we need to provide in order to create a
VariableProfile is how the space between two profiles gets
We pass this information is form of a list that contains the corresponding interpolation functions.
For each pair of profiles we need to specify an interpolation where the first list entry is associated with the first
pair, the second entry with the second pair and so on.
The number of list items is therefore one less than the number of profiles.
Lets have look at an example after this rather long text section. First, we define two more profiles.
shape_right_2 = Shape().add_line_segments( Q_([[10, 8], [5, 8], [5, 2], [5, 0], [10, 0]], "cm") ) shape_left_2 = shape_right_2.reflect_across_line(Q_([0, 0], "cm"), Q_([0, 1], "cm")) profile_2 = Profile([shape_left_2, shape_right_2]) shape_right_3 = Shape().add_line_segments( Q_([[10, 12], [2, 12], [4, 4], [4, 0], [10, 0]], "cm") ) shape_left_3 = shape_right_3.reflect_across_line(Q_([0, 0], "cm"), Q_([0, 1], "cm")) profile_3 = Profile([shape_left_3, shape_right_3]) profile.plot(raster_width=Q_(1, "cm"), label="Profile 1") profile_2.plot(raster_width=Q_(1, "cm"), label="Profile 2", ax=plt.gca()) profile_3.plot(raster_width=Q_(1, "cm"), label="Profile 3", ax=plt.gca()) plt.gca().legend()
<matplotlib.legend.Legend at 0x7fbe6855bca0>
Now we create a
VariableProfile, pass it to a new
Geometry and plot it:
v_profile = VariableProfile( [profile_3, profile_2, profile], Q_([0, 40, 100], "cm"), [linear_profile_interpolation_sbs, linear_profile_interpolation_sbs], ) geometry_vp = Geometry(profile=v_profile, trace_or_length=trace) geometry_vp.plot(profile_raster_width=Q_(0.25, "cm"), trace_raster_width=Q_(8, "cm"))
5.5. Custom interpolation schemes#
You might notice that we used a interpolation function called
The “sbs” stands for “segment-by-segment”.
It interpolates all of the profile segments linearly in the order as they appear in both involved profiles.
So you need to assure that the segments are ordered accordingly in both profiles.
It also requires that the number of segments is identical and it only works with the
You might wonder what alternative functions are provided, but currently there are none. The reason is that as soon as you start thinking about it, there are so many options that need to be considered that don’t have a unique solution that fits all use cases. To circumvent this issue, we provide the possibility to use custom interpolation functions. All that you need to do in order to use your own interpolation scheme is to define a python function with the following interface:
def custom_interpolation(Profile, Profile, float) -> Profile
float parameter is a weighting factor representing the position on the trace segment between both profiles.
Geometry class will pass values between
0 is the beginning of the segment and associates with
the first passed
In conclusion, a value of
1 represents the end of the segment and is associated with the second
What you do with this information is up to you.
All that matters is that the function returns a valid
Profile in the end.
Lets create an example interpolation that returns the first passed profile for weighting factors below
0.4 and the
second one otherwise:
def my_interp(p0, p1, weight): if weight < 0.4: return p0 return p1
Now we just create a
VariableProfile with our new interpolation, combine it with a linear
Trace inside of a
Geometry and plot it:
v_profile_custom_interp = VariableProfile( [profile_3, profile_2], Q_([0, 1], "mm"), my_interp ) trace_custom_interp = Trace(LinearHorizontalTraceSegment("20cm")) geometry_custom_interp = Geometry(v_profile_custom_interp, trace_custom_interp) geometry_custom_interp.plot(profile_raster_width="0.25cm", trace_raster_width="2cm")