{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Welding Experiment Design" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "nbsphinx": "hidden" }, "outputs": [], "source": [ "# enable interactive plots on Jupyterlab with ipympl and jupyterlab-matplotlib installed\n", "# %matplotlib widget" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This tutorial will show a slightly more advanced use case example of how to combine different possibilities and do some calculations and analysis with the tools provided by `weldx`.\n", "\n", "The tasks covered are the following:\n", "\n", "- create a linear weld seam\n", "- add a simple (linear) welding trace along the weld seam\n", "- create two different groove profiles that vary along the weld seam\n", "- generate interpolated pointcloud data from the resulting workpiece\n", "- add two more complex TCP movements:\n", " - a horizontal weaving motion\n", " - a vertical weaving motion\n", "- calculate and plot the minimal distance between the the workpiece geometry and the different TCP movements along the entire weld seam" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Python imports" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "# some python imports that will be used throughout the tutorial\n", "import numpy as np\n", "import pandas as pd\n", "from IPython.display import display" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# importing the weldx package with prevalent default abbreviations\n", "import weldx\n", "import weldx.geometry as geo\n", "from weldx import Q_, get_groove\n", "from weldx import LocalCoordinateSystem as LCS\n", "from weldx.welding.util import sine" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## helper function\n", "We define small helper function that will calculate the minimal (Euclidean) distance between a point in space (our current TCP position) and a 3d pointcloud that describes our workpiece geometry.\n", "\n", "Keep in mind that this is a very simple function that works only on generated point data without any meshing or interpolation. Results will be more accurate for denser point clouds with many points." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def distance(ptc, lcs_name, time):\n", " \"\"\"Calculate minimal distance between geometry rasterization pointcloud\n", " and 3D trace\n", " .\n", " \"\"\"\n", " lcs_interp = csm.get_cs(\n", " coordinate_system_name=lcs_name, reference_system_name=\"workpiece\", time=time\n", " )\n", "\n", " trace = Q_(lcs_interp.coordinates.data, \"mm\")\n", "\n", " ptc = ptc.T\n", "\n", " ptc = np.expand_dims(ptc, 1)\n", " trace = np.expand_dims(trace, 0)\n", "\n", " return np.min(np.sqrt(np.sum((ptc - trace) ** 2, axis=-1)), axis=0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Trace and CSM setup" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lets define the timestamps that we will use to visualize our experiment:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "time = pd.timedelta_range(\"0s\", \"10s\", freq=\"50ms\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For this example we will be working with a linear weld seam of 100 mm ." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# define the weld seam length in mm\n", "seam_length = Q_(100, \"mm\")\n", "\n", "trace_segment = geo.LinearHorizontalTraceSegment(seam_length)\n", "trace = geo.Trace(trace_segment)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We set up the following coordinate systems:\n", "- the default `base` system\n", "- a `workpiece` system that corresponds to the baseline of our workpiece\n", "- a `tcp_wire` system that represents the very simple case of moving along the weld seam 2 mm above the `workpiece` system with a weld speed of 10 mm/s" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# crete a new coordinate system manager with default base coordinate system\n", "csm = weldx.transformations.CoordinateSystemManager(\"base\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# add the workpiece coordinate system\n", "csm.create_cs(\n", " coordinate_system_name=\"workpiece\",\n", " reference_system_name=\"base\",\n", " orientation=trace.coordinate_system.orientation,\n", " coordinates=Q_(trace.coordinate_system.coordinates.data, \"mm\"),\n", ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tcp_start_point = Q_([0.0, 0.0, 2.0], \"mm\")\n", "tcp_end_point = np.append(seam_length, Q_([0, 2.0], \"mm\"))\n", "\n", "v_weld = Q_(10, \"mm/s\")\n", "s_weld = (tcp_end_point - tcp_start_point)[0] # length of the weld\n", "t_weld = s_weld / v_weld\n", "\n", "t_start = pd.Timedelta(\"0s\")\n", "t_end = pd.Timedelta(str(t_weld.to_base_units()))\n", "\n", "coords = np.stack((tcp_start_point, tcp_end_point))\n", "\n", "tcp_wire = LCS(coordinates=coords, time=[t_start, t_end])\n", "\n", "# add the workpiece coordinate system\n", "csm.add_cs(\n", " coordinate_system_name=\"tcp_wire\",\n", " reference_system_name=\"workpiece\",\n", " lcs=tcp_wire,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### add weaving motions\n", "We now want two define and add two different weaving motions, one in y-direction (towards the groove sidewalls) and another on in z-direction (up and down)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### add y weaving" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sine_y = sine(f=Q_(1, \"Hz\"), amp=Q_([0, 1, 0], \"mm\"))\n", "csm.create_cs(\n", " coordinate_system_name=\"tcp_sine_y\",\n", " reference_system_name=\"tcp_wire\",\n", " coordinates=sine_y,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### add z weaving" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sine_z = sine(f=Q_(1, \"Hz\"), amp=Q_([0, 0, 2], \"mm\"), bias=Q_([0, 0, 0], \"mm\"))\n", "csm.create_cs(\n", " coordinate_system_name=\"tcp_sine_z\",\n", " reference_system_name=\"tcp_wire\",\n", " coordinates=sine_z,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's visualize the different coordinate systems." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "csm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## generate I-Groove pointcloud\n", "Let's finish the workpiece creation by adding groove profiles to the start end end of the welding trace.\n", "\n", "For the first example we will use I-Groove profiles and have the root gap open up along the weld seam from 2 mm to 6 mm." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "groove_1 = get_groove(\n", " groove_type=\"IGroove\",\n", " workpiece_thickness=Q_(5, \"mm\"),\n", " root_gap=Q_(2, \"mm\"),\n", ")\n", "\n", "groove_2 = get_groove(\n", " groove_type=\"IGroove\",\n", " workpiece_thickness=Q_(5, \"mm\"),\n", " root_gap=Q_(6, \"mm\"),\n", ")\n", "\n", "display(groove_1)\n", "display(groove_2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v_profile = geo.VariableProfile(\n", " [groove_1.to_profile(), groove_2.to_profile()],\n", " Q_([0, 100], \"mm\"),\n", " [geo.linear_profile_interpolation_sbs],\n", ")\n", "\n", "# create 3d workpiece geometry from the groove profile and trace objects\n", "geometry = geo.Geometry(profile=v_profile, trace_or_length=trace)\n", "pointcloud_I = geometry.rasterize(\n", " profile_raster_width=Q_(1, \"mm\"), trace_raster_width=Q_(0.5, \"mm\")\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Calculate distance (simple trace in I-Groove center)\n", "The first analysis example is very simple and used to check if our tools work as expected.\n", "\n", "If the wire TCP moves on a straight line along the groove center, the distance to the workpiece geometry is equivalent to the distance of each sidewall to the groove center. Since the root gap of the groove changes linearly from 2 mm to 6 mm, we expect the distance to change from 1 mm to 3 mm accordingly." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d = distance(pointcloud_I, \"tcp_wire\", time=time)\n", "plt.plot(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Calculate distance (y-weaving in I-Groove)\n", "Now let's analyze the distance when weaving along the y-axis (towards the sidewalls).\n", "We can see the expected superposition of the weaving motion with the opening of the root gap.\n", "Note the doubling of the observed frequency with regards to the weaving frequency of 1 Hz because we calculate the minimum distance instead of the distance to each sidewall separately." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d = distance(pointcloud_I, \"tcp_sine_y\", time=time)\n", "plt.plot(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we analyze the weaving motion in z-direction alongside the I-Groove, we see no change in distance depending on the z-Position as expected.\n", "The small disturbances are due to the coarse rasterisation of the workpiece geometry." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d = distance(pointcloud_I, \"tcp_sine_z\", time=time)\n", "plt.plot(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generate V-Groove geometry\n", "Lets look a more complex interaction of groove shapes and TCP motion.\n", "\n", "For this example we define two V-Groove profiles that begin at a groove angle of 60 degree with a root face and root gap of 1 mm each. At the end of the weld seam thee groove shape changes to a steep 20 degree angle with a root gap and root face of 3 mm." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "groove_1 = get_groove(\n", " groove_type=\"VGroove\",\n", " workpiece_thickness=Q_(5, \"mm\"),\n", " groove_angle=Q_(60, \"deg\"),\n", " root_face=Q_(1, \"mm\"),\n", " root_gap=Q_(1, \"mm\"),\n", ")\n", "\n", "groove_2 = get_groove(\n", " groove_type=\"VGroove\",\n", " workpiece_thickness=Q_(5, \"mm\"),\n", " groove_angle=Q_(20, \"deg\"),\n", " root_face=Q_(3, \"mm\"),\n", " root_gap=Q_(3, \"mm\"),\n", ")\n", "\n", "display(groove_1)\n", "display(groove_2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v_profile = geo.VariableProfile(\n", " [groove_1.to_profile(), groove_2.to_profile()],\n", " Q_([0, 100], \"mm\"),\n", " [geo.linear_profile_interpolation_sbs],\n", ")\n", "\n", "# create 3d workpiece geometry from the groove profile and trace objects\n", "geometry = geo.Geometry(profile=v_profile, trace_or_length=trace)\n", "pointcloud_V = geometry.rasterize(\n", " profile_raster_width=Q_(0.25, \"mm\"), trace_raster_width=Q_(0.5, \"mm\")\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's look at the CTWD with our previously defined weaving motion in z-direction." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d = distance(pointcloud_V, \"tcp_sine_z\", time=time)\n", "plt.plot(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are a few things to note:\n", "\n", "- as expected, we once again see the linear trend where the root gap opens as a baseline.\n", "- in addition, the distance now consists of two distinct phases:\n", " - one phase where the TCP is \"submerged\" into the root face part of the weld. In this case, the distance is not impacted by the z weaving motion\n", " - the second phase, where the TCP is above the root face and the distance of the V shaped groove section is mimicking the z weaving motion.\n", "- we can also see that the weaving motion reflection is more distinctly discernible if the groove opening angle is large." ] } ], "metadata": { "kernelspec": { "display_name": "", "language": "python", "name": "" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.12" } }, "nbformat": 4, "nbformat_minor": 4 }