diff --git a/info/pic/robot_frontView_forearm_0.pdf b/info/pic/robot_frontView_forearm_0.pdf new file mode 100644 index 0000000..e3a77ff Binary files /dev/null and b/info/pic/robot_frontView_forearm_0.pdf differ diff --git a/info/pic/robot_image_a.png b/info/pic/robot_image_a.png new file mode 100644 index 0000000..c56a0bf Binary files /dev/null and b/info/pic/robot_image_a.png differ diff --git a/info/pic/robot_sideView_measurements.pdf b/info/pic/robot_sideView_measurements.pdf new file mode 100644 index 0000000..b3ce3ad Binary files /dev/null and b/info/pic/robot_sideView_measurements.pdf differ diff --git a/info/pic/robot_sideView_measurements.svg b/info/pic/robot_sideView_measurements.svg new file mode 100644 index 0000000..ceba15a --- /dev/null +++ b/info/pic/robot_sideView_measurements.svg @@ -0,0 +1,8107 @@ + + + +61mm243229198250 mm250 mmyz35mm35mmd229 diff --git a/info/pic/robot_sideView_measurements_0.pdf b/info/pic/robot_sideView_measurements_0.pdf new file mode 100644 index 0000000..b6d927e Binary files /dev/null and b/info/pic/robot_sideView_measurements_0.pdf differ diff --git a/info/position.aux b/info/position.aux new file mode 100644 index 0000000..ab6cf62 --- /dev/null +++ b/info/position.aux @@ -0,0 +1,11 @@ +\relax +\providecommand \babel@aux [2]{\global \let \babel@toc \@gobbletwo } +\@nameuse{bbl@beforestart} +\catcode `"\active +\babel@aux{ngerman}{} +\@writefile{toc}{\contentsline {section}{\numberline {1}Intro}{1}{}\protected@file@percent } +\@writefile{toc}{\contentsline {section}{\numberline {2}Angles}{1}{}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {2.1}Bizeps {\bf \tt y}}{1}{}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {2.2}Ellbow -- Rotation}{2}{}\protected@file@percent } +\@writefile{toc}{\contentsline {subsection}{\numberline {2.3}Forearm}{2}{}\protected@file@percent } +\gdef \@abspage@last{2} diff --git a/info/position.log b/info/position.log new file mode 100644 index 0000000..7e8e14c --- /dev/null +++ b/info/position.log @@ -0,0 +1,271 @@ +This is pdfTeX, Version 3.141592653-2.6-1.40.27 (MiKTeX 25.4) (preloaded format=pdflatex 2025.6.3) 28 APR 2026 18:37 +entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. +**./position.tex +(position.tex +LaTeX2e <2024-11-01> patch level 2 +L3 programming layer <2025-04-29> +(C:\Program Files\MiKTeX\tex/latex/base\article.cls +Document Class: article 2024/06/29 v1.4n Standard LaTeX document class +(C:\Program Files\MiKTeX\tex/latex/base\size11.clo +File: size11.clo 2024/06/29 v1.4n Standard LaTeX file (size option) +) +\c@part=\count272 +\c@section=\count273 +\c@subsection=\count274 +\c@subsubsection=\count275 +\c@paragraph=\count276 +\c@subparagraph=\count277 +\c@figure=\count278 +\c@table=\count279 +\abovecaptionskip=\skip49 +\belowcaptionskip=\skip50 +\bibindent=\dimen146 +) +(C:\Program Files\MiKTeX\tex/latex/base\inputenc.sty +Package: inputenc 2024/02/08 v1.3d Input encoding file +\inpenc@prehook=\toks17 +\inpenc@posthook=\toks18 +) +(C:\Program Files\MiKTeX\tex/latex/base\fontenc.sty +Package: fontenc 2021/04/29 v2.0v Standard LaTeX package +) +(C:\Program Files\MiKTeX\tex/latex/graphics\graphicx.sty +Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR) + +(C:\Program Files\MiKTeX\tex/latex/graphics\keyval.sty +Package: keyval 2022/05/29 v1.15 key=value parser (DPC) +\KV@toks@=\toks19 +) +(C:\Program Files\MiKTeX\tex/latex/graphics\graphics.sty +Package: graphics 2024/08/06 v1.4g Standard LaTeX Graphics (DPC,SPQR) + +(C:\Program Files\MiKTeX\tex/latex/graphics\trig.sty +Package: trig 2023/12/02 v1.11 sin cos tan (DPC) +) +(C:\Program Files\MiKTeX\tex/latex/graphics-cfg\graphics.cfg +File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration +) +Package graphics Info: Driver file: pdftex.def on input line 106. + +(C:\Program Files\MiKTeX\tex/latex/graphics-def\pdftex.def +File: pdftex.def 2024/04/13 v1.2c Graphics/color driver for pdftex +)) +\Gin@req@height=\dimen147 +\Gin@req@width=\dimen148 +) +(C:\Program Files\MiKTeX\tex/generic/babel\babel.sty +Package: babel 2025/05/14 v25.9 The multilingual framework for pdfLaTeX, LuaLaT +eX and XeLaTeX +\babel@savecnt=\count280 +\U@D=\dimen149 +\l@unhyphenated=\language79 + +(C:\Program Files\MiKTeX\tex/generic/babel\txtbabel.def) +\bbl@readstream=\read2 +\bbl@dirlevel=\count281 + +************************************* +* Local config file bblopts.cfg used +* +(C:\Program Files\MiKTeX\tex/latex/arabi\bblopts.cfg +File: bblopts.cfg 2005/09/08 v0.1 add Arabic and Farsi to "declared" options of + babel +) +(C:\Program Files\MiKTeX\tex/latex/babel-german\ngerman.ldf +Language: ngerman 2024/12/10 v2.15 German support for babel (post-1996 orthogra +phy) + +(C:\Program Files\MiKTeX\tex/generic/babel/locale/de\babel-ngerman.tex +Package babel Info: Importing font and identification data for ngerman +(babel) from babel-de.ini. Reported on input line 11. +) +(C:\Program Files\MiKTeX\tex/latex/babel-german\ngermanb.ldf +Language: ngermanb 2024/12/10 v2.15 German support for babel (post-1996 orthogr +aphy) +Package babel Info: Making " an active character on input line 122. +))) +(C:\Program Files\MiKTeX\tex/latex/lm\lmodern.sty +Package: lmodern 2015/05/01 v1.6.1 Latin Modern Fonts +LaTeX Font Info: Overwriting symbol font `operators' in version `normal' +(Font) OT1/cmr/m/n --> OT1/lmr/m/n on input line 22. +LaTeX Font Info: Overwriting symbol font `letters' in version `normal' +(Font) OML/cmm/m/it --> OML/lmm/m/it on input line 23. +LaTeX Font Info: Overwriting symbol font `symbols' in version `normal' +(Font) OMS/cmsy/m/n --> OMS/lmsy/m/n on input line 24. +LaTeX Font Info: Overwriting symbol font `largesymbols' in version `normal' +(Font) OMX/cmex/m/n --> OMX/lmex/m/n on input line 25. +LaTeX Font Info: Overwriting symbol font `operators' in version `bold' +(Font) OT1/cmr/bx/n --> OT1/lmr/bx/n on input line 26. +LaTeX Font Info: Overwriting symbol font `letters' in version `bold' +(Font) OML/cmm/b/it --> OML/lmm/b/it on input line 27. +LaTeX Font Info: Overwriting symbol font `symbols' in version `bold' +(Font) OMS/cmsy/b/n --> OMS/lmsy/b/n on input line 28. +LaTeX Font Info: Overwriting symbol font `largesymbols' in version `bold' +(Font) OMX/cmex/m/n --> OMX/lmex/m/n on input line 29. +LaTeX Font Info: Overwriting math alphabet `\mathbf' in version `normal' +(Font) OT1/cmr/bx/n --> OT1/lmr/bx/n on input line 31. +LaTeX Font Info: Overwriting math alphabet `\mathsf' in version `normal' +(Font) OT1/cmss/m/n --> OT1/lmss/m/n on input line 32. +LaTeX Font Info: Overwriting math alphabet `\mathit' in version `normal' +(Font) OT1/cmr/m/it --> OT1/lmr/m/it on input line 33. +LaTeX Font Info: Overwriting math alphabet `\mathtt' in version `normal' +(Font) OT1/cmtt/m/n --> OT1/lmtt/m/n on input line 34. +LaTeX Font Info: Overwriting math alphabet `\mathbf' in version `bold' +(Font) OT1/cmr/bx/n --> OT1/lmr/bx/n on input line 35. +LaTeX Font Info: Overwriting math alphabet `\mathsf' in version `bold' +(Font) OT1/cmss/bx/n --> OT1/lmss/bx/n on input line 36. +LaTeX Font Info: Overwriting math alphabet `\mathit' in version `bold' +(Font) OT1/cmr/bx/it --> OT1/lmr/bx/it on input line 37. +LaTeX Font Info: Overwriting math alphabet `\mathtt' in version `bold' +(Font) OT1/cmtt/m/n --> OT1/lmtt/m/n on input line 38. +) +LaTeX Font Info: Trying to load font information for T1+lmr on input line 20 +. + +(C:\Program Files\MiKTeX\tex/latex/lm\t1lmr.fd +File: t1lmr.fd 2015/05/01 v1.6.1 Font defs for Latin Modern +) +(C:\Program Files\MiKTeX\tex/latex/l3backend\l3backend-pdftex.def +File: l3backend-pdftex.def 2025-04-14 L3 backend support: PDF output (pdfTeX) +\l__color_backend_stack_int=\count282 +) +(position.aux +Package babel Info: 'ngerman' activates 'ngerman' shorthands. +(babel) Reported on input line 5. +) +\openout1 = `position.aux'. + +LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 20. +LaTeX Font Info: ... okay on input line 20. +LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 20. +LaTeX Font Info: ... okay on input line 20. +LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 20. +LaTeX Font Info: ... okay on input line 20. +LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 20. +LaTeX Font Info: ... okay on input line 20. +LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 20. +LaTeX Font Info: ... okay on input line 20. +LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 20. +LaTeX Font Info: ... okay on input line 20. +LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 20. +LaTeX Font Info: ... okay on input line 20. + (C:\Program Files\MiKTeX\tex/context/base/mkii\supp-pdf.mkii +[Loading MPS to PDF converter (version 2006.09.02).] +\scratchcounter=\count283 +\scratchdimen=\dimen150 +\scratchbox=\box53 +\nofMPsegments=\count284 +\nofMParguments=\count285 +\everyMPshowfont=\toks20 +\MPscratchCnt=\count286 +\MPscratchDim=\dimen151 +\MPnumerator=\count287 +\makeMPintoPDFobject=\count288 +\everyMPtoPDFconversion=\toks21 +) (C:\Program Files\MiKTeX\tex/latex/epstopdf-pkg\epstopdf-base.sty +Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf +Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4 +85. + +(C:\Program Files\MiKTeX\tex/latex/00miktex\epstopdf-sys.cfg +File: epstopdf-sys.cfg 2021/03/18 v2.0 Configuration of epstopdf for MiKTeX +)) +LaTeX Font Info: Trying to load font information for OT1+lmr on input line 2 +2. + +(C:\Program Files\MiKTeX\tex/latex/lm\ot1lmr.fd +File: ot1lmr.fd 2015/05/01 v1.6.1 Font defs for Latin Modern +) +LaTeX Font Info: Trying to load font information for OML+lmm on input line 2 +2. + +(C:\Program Files\MiKTeX\tex/latex/lm\omllmm.fd +File: omllmm.fd 2015/05/01 v1.6.1 Font defs for Latin Modern +) +LaTeX Font Info: Trying to load font information for OMS+lmsy on input line +22. + +(C:\Program Files\MiKTeX\tex/latex/lm\omslmsy.fd +File: omslmsy.fd 2015/05/01 v1.6.1 Font defs for Latin Modern +) +LaTeX Font Info: Trying to load font information for OMX+lmex on input line +22. + +(C:\Program Files\MiKTeX\tex/latex/lm\omxlmex.fd +File: omxlmex.fd 2015/05/01 v1.6.1 Font defs for Latin Modern +) +LaTeX Font Info: External font `lmex10' loaded for size +(Font) <12> on input line 22. +LaTeX Font Info: External font `lmex10' loaded for size +(Font) <8> on input line 22. +LaTeX Font Info: External font `lmex10' loaded for size +(Font) <6> on input line 22. +LaTeX Font Info: Trying to load font information for T1+lmtt on input line 3 +4. + +(C:\Program Files\MiKTeX\tex/latex/lm\t1lmtt.fd +File: t1lmtt.fd 2015/05/01 v1.6.1 Font defs for Latin Modern +) + +File: pic/robot_image_a.png Graphic file (type png) + +Package pdftex.def Info: pic/robot_image_a.png used on input line 43. +(pdftex.def) Requested size: 360.0pt x 163.77846pt. +LaTeX Font Info: External font `lmex10' loaded for size +(Font) <10.95> on input line 48. +LaTeX Font Info: Trying to load font information for TS1+lmr on input line 5 +4. + +(C:\Program Files\MiKTeX\tex/latex/lm\ts1lmr.fd +File: ts1lmr.fd 2015/05/01 v1.6.1 Font defs for Latin Modern +) +Underfull \hbox (badness 2293) in paragraph at lines 55--57 +\T1/lmr/m/n/10.95 (each po-si-ti-on on its + [] + + +File: pic/robot_sideView_measurements.pdf Graphic file (type pdf) + +Package pdftex.def Info: pic/robot_sideView_measurements.pdf used on input lin +e 62. +(pdftex.def) Requested size: 216.0022pt x 155.8966pt. + + +[1 + +{C:/Users/kech/AppData/Local/MiKTeX/fonts/map/pdftex/pdftex.map}{C:/Program Fil +es/MiKTeX/fonts/enc/dvips/lm/lm-ec.enc}{C:/Program Files/MiKTeX/fonts/enc/dvips +/lm/lm-mathsy.enc} <./pic/robot_image_a.png>] + +[2{C:/Program Files/MiKTeX/fonts/enc/dvips/lm/lm-ts1.enc}{C:/Program Files/MiKT +eX/fonts/enc/dvips/lm/lm-rm.enc}{C:/Program Files/MiKTeX/fonts/enc/dvips/lm/lm- +mathit.enc} <./pic/robot_sideView_measurements.pdf>] (position.aux) + *********** +LaTeX2e <2024-11-01> patch level 2 +L3 programming layer <2025-04-29> + *********** + ) +Here is how much of TeX's memory you used: + 2937 strings out of 469923 + 49608 string characters out of 5479241 + 434765 words of memory out of 5000000 + 29733 multiletter control sequences out of 15000+600000 + 661525 words of font info for 64 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 57i,6n,65p,227b,236s stack positions out of 10000i,1000n,20000p,200000b,200000s + +Output written on position.pdf (2 pages, 1242265 bytes). +PDF statistics: + 1451 PDF objects out of 1728 (max. 8388607) + 0 named destinations out of 1000 (max. 500000) + 11 words of extra memory for PDF output out of 10000 (max. 10000000) + diff --git a/info/position.pdf b/info/position.pdf new file mode 100644 index 0000000..6af98a5 Binary files /dev/null and b/info/position.pdf differ diff --git a/info/position.synctex.gz b/info/position.synctex.gz new file mode 100644 index 0000000..ca3027c Binary files /dev/null and b/info/position.synctex.gz differ diff --git a/info/position.tex b/info/position.tex new file mode 100644 index 0000000..b5f49c1 --- /dev/null +++ b/info/position.tex @@ -0,0 +1,90 @@ +\documentclass[a4paper,11pt]{article} + +% Zeichencodierung für deutsche Umlaute +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} + +% Grafiken einbinden +\usepackage{graphicx} + +% Deutsche Sprache (Datum, Trennungen, etc.) +\usepackage[ngerman]{babel} + +% Optional: bessere Schrift +\usepackage{lmodern} + +\title{How to get a robot position from video image} +\author{Christoph Kendel} +\date{\today} + +\begin{document} + +\maketitle + +\section{Intro} + +My Robot-Arm does not have homing switches. I want to use WebCams and Aruco--Markers to get the position of each joint. This way +I can get the position as well as double--check the position while working under load. + +I work with Arucos. With my low resolution WebCam I can get reliable positions (when using two cams). But the angle resolution is not usable. +So I have to calculate the angle of each element from the relative positions of the markers. + +\section{Angles} + +\subsection{Bizeps {\bf \tt y}} + +To find the angle of the bizeps (upper arm) there are different options, depending on the angle-of-view and which +ArUcos are visable. + + +\vspace{2mm} + +\noindent +\includegraphics[width=\linewidth]{pic/robot_image_a.png} + +\vspace{2mm} + +\noindent +The calculation is based on two approaches: Position of the Markers 243 etc on the one hand, and relative position 198 $\leftrightarrow$ 229 on the other hand. + +\noindent +\begin{minipage}{0.39\linewidth} +Several ways to calculate {\tt y}: +\begin{itemize} +\item 229 $\leftrightarrow$ 198 $\tan(y) = \frac{\Delta z}{\Delta y} $ +\item From $y_{\rm Axis}$ and the positions of 243, 229, 198 (each position on its own) we can calculate $\tan(y+\delta)$ + with a known $\delta$ from the geometry. + +Thus we get {\tt y} +\end{itemize} +\end{minipage}\hfill +\begin{minipage}{0.6\linewidth} +\includegraphics[width=\linewidth]{pic/robot_sideView_measurements.pdf} +\end{minipage} + +\vspace{2mm} + +\noindent +Each availiable (if marker is visible) approach to calculate {\tt y} is done, and the mean is calculated. We can check if they deviate too much, and give +warnings. + +\subsection{Ellbow -- Rotation} + +At the ellbow there is a motor that turns the forearm arround. Depending on which Arucus are visible, the position {\tt a} of the motor can be calculated: + +\begin{itemize} +\item {\tt x} of 223 or ... +\end{itemize} +% +% +the +{\tt y} and {\tt z} position of 223 etc can't be used, as it depends on the angle of the forearm and the angle of the bizeps. Thus is it less reliable. (Although it would be a nice +thing to use as a double--check). + +\subsection{Forearm} + + + + + +\end{document} \ No newline at end of file diff --git a/public/calculateActions.js b/public/calculateActions.js index 4b2c63a..0b437d4 100755 --- a/public/calculateActions.js +++ b/public/calculateActions.js @@ -374,7 +374,8 @@ async function calculate() { const row242 = getRow(242, 3); const row200 = getRow(200, 3); const row204 = getRow(204, 3); - const row222 = getRow(222, 3); + const row222 = getRow(222, 3); // Ellbow + const row223 = getRow(223, 3); // Lower Arm const angleYCandidates = []; @@ -451,6 +452,7 @@ async function calculate() { }); } + if (angleYCandidates.length > 0) { buildFeatureFromCandidates( result, @@ -521,14 +523,15 @@ async function calculate() { let x226 = 0; let xCount = 0; - if(row222){ - x226 += row222.x_mm * 5; - xCount += 5; - } if (row226) { x226 += row226.x_mm * 5; xCount += 5; } + if(row222){ + /// 222 should have the same as x226 + x226 += row222.x_mm * 5; + xCount += 5; + } if (row229) { x226 += row229.x_mm + 90; xCount += 1; @@ -618,6 +621,27 @@ async function calculate() { } } } + + // Ellbow-Rotation wenn X Position OK und 223 oder so bekannt ist. + if(row223 && xCount > 2){ + // unterarm-roll => a aus der position von 223 + dx = row223.x_mm - x226; + // aus der X Position kann der Unterarm-Winkel (a) berechnet werden + + const angleRad = Math.asin(dx / 35); + const angleDeg = 90 - angleRad * 180 / Math.PI; + addLog(result, `(xEllbowRotation = ${angleDeg.toFixed(2)}° = ${angleRad.toFixed(4)} rad ) aus x von 223 und Ellbogen x226`); + addCalculation(result, { + type: "elbowRotationAFromX223", + input: { + x223: row223.x_mm, + x226 + }, + output: { + angleRad, angleDeg + } + }); + } } result.summary = { diff --git a/test/calculateAction.03_ellbowRotation.test.js b/test/calculateAction.03_ellbowRotation.test.js new file mode 100644 index 0000000..29e7557 --- /dev/null +++ b/test/calculateAction.03_ellbowRotation.test.js @@ -0,0 +1,265 @@ +/** + * @jest-environment jsdom + */ + +const fs = require("fs"); +const path = require("path"); + +describe("calculate() row223 Ellbow-Rotation Tests", () => { + + let calculate; + + beforeEach(() => { + // DOM erzeugen + document.body.innerHTML = ` + + `; + + // Fetch mocken - wird pro Test konfiguriert + global.fetch = jest.fn(); + + // Modul erst JETZT laden (DOM existiert) + ({ calculate } = require("../public/calculateActions.js")); + }); + + /** + * Hilfsfunktion: Erstellt ein CSV mit definierten row223.x_mm und x226 Wert + * @param {number} row223_x_mm - x_mm Wert für row223 (id=223) + * @param {number} x226_base - Basis-x_mm Wert für x226-Punkt (wird durch mehrere Punkte erreicht) + */ + function createTestCSV(row223_x_mm, x226_base) { + const csvContent = `id,x_mm,y_mm,z_mm,roll_deg,pitch_deg,seen_by +222,${x226_base},120,65,0,0,3 +226,${x226_base},115,61,0,0,3 +229,${x226_base - 90},110,55,0,0,3 +223,${row223_x_mm},125,62,0,0,3 +`; + return csvContent; + } + + /** + * Berechnet den erwarteten Winkel basierend auf Formel aus calculateActions.js + * angleRad = Math.asin(dx / 35) + * angleDeg = 90 - angleRad * 180 / Math.PI + */ + function calculateExpectedAngle(row223_x_mm, x226) { + const dx = row223_x_mm - x226; + if (Math.abs(dx) > 35) { + return null; // Math.asin() erlaubt nur Werte zwischen -1 und 1 + } + const angleRad = Math.asin(dx / 35); + const angleDeg = 90 - angleRad * 180 / Math.PI; + return { angleRad, angleDeg, dx }; + } + + // Test 1: row223 mit x_mm = x226 (dx = 0, erwarteter Winkel = 90°) + test("row223.x_mm = x226 sollte Winkel von 90° ergeben", async () => { + const x226_base = 100; + const row223_x_mm = x226_base; // dx = 0 + const csvContent = createTestCSV(row223_x_mm, x226_base); + + global.fetch.mockResolvedValueOnce({ + ok: true, + headers: { get: () => "text/csv" }, + text: async () => csvContent + }); + + const result = await calculate(); + + // xCount sollte > 2 sein (wir haben 4 Zeilen) + expect(result.status).toBe("ok"); + + // Überprüfe, ob die elbowRotationAFromX223 Berechnung vorhanden ist + const elbowRotationCalc = result.calculations.find( + c => c.type === "elbowRotationAFromX223" + ); + + expect(elbowRotationCalc).toBeDefined(); + expect(elbowRotationCalc.output.angleDeg).toBeCloseTo(90, 1); + expect(elbowRotationCalc.output.angleRad).toBeCloseTo(0, 4); + }); + + // Test 2: row223 mit x_mm = x226 + 17.5 (dx = 17.5, erwarteter Winkel = 45°) + test("row223.x_mm = x226 + 17.5 sollte Winkel von 45° ergeben", async () => { + const x226_base = 100; + const row223_x_mm = x226_base + 17.5; // dx = 17.5 + const csvContent = createTestCSV(row223_x_mm, x226_base); + + global.fetch.mockResolvedValueOnce({ + ok: true, + headers: { get: () => "text/csv" }, + text: async () => csvContent + }); + + const result = await calculate(); + const expected = calculateExpectedAngle(row223_x_mm, x226_base); + + expect(result.status).toBe("ok"); + + const elbowRotationCalc = result.calculations.find( + c => c.type === "elbowRotationAFromX223" + ); + + expect(elbowRotationCalc).toBeDefined(); + expect(elbowRotationCalc.output.angleDeg).toBeCloseTo(expected.angleDeg, 1); + expect(elbowRotationCalc.output.angleRad).toBeCloseTo(expected.angleRad, 3); + }); + + // Test 3: row223 mit x_mm = x226 + 35 (dx = 35, erwarteter Winkel = 0°) + test("row223.x_mm = x226 + 35 sollte Winkel von 0° ergeben", async () => { + const x226_base = 100; + const row223_x_mm = x226_base + 35; // dx = 35, asin(1) = π/2 + const csvContent = createTestCSV(row223_x_mm, x226_base); + + global.fetch.mockResolvedValueOnce({ + ok: true, + headers: { get: () => "text/csv" }, + text: async () => csvContent + }); + + const result = await calculate(); + const expected = calculateExpectedAngle(row223_x_mm, x226_base); + + expect(result.status).toBe("ok"); + + const elbowRotationCalc = result.calculations.find( + c => c.type === "elbowRotationAFromX223" + ); + + expect(elbowRotationCalc).toBeDefined(); + expect(elbowRotationCalc.output.angleDeg).toBeCloseTo(expected.angleDeg, 1); + expect(elbowRotationCalc.output.angleRad).toBeCloseTo(expected.angleRad, 3); + }); + + // Test 4: row223 mit negativem dx (x_mm = x226 - 17.5, erwarteter Winkel = 135°) + test("row223.x_mm = x226 - 17.5 sollte Winkel von 135° ergeben", async () => { + const x226_base = 100; + const row223_x_mm = x226_base - 17.5; // dx = -17.5 + const csvContent = createTestCSV(row223_x_mm, x226_base); + + global.fetch.mockResolvedValueOnce({ + ok: true, + headers: { get: () => "text/csv" }, + text: async () => csvContent + }); + + const result = await calculate(); + const expected = calculateExpectedAngle(row223_x_mm, x226_base); + + expect(result.status).toBe("ok"); + + const elbowRotationCalc = result.calculations.find( + c => c.type === "elbowRotationAFromX223" + ); + + expect(elbowRotationCalc).toBeDefined(); + expect(elbowRotationCalc.output.angleDeg).toBeCloseTo(expected.angleDeg, 1); + expect(elbowRotationCalc.output.angleRad).toBeCloseTo(expected.angleRad, 3); + }); + + // Test 5: row223 mit x_mm = x226 - 35 (dx = -35, erwarteter Winkel = 180°) + test("row223.x_mm = x226 - 35 sollte Winkel von 180° ergeben", async () => { + const x226_base = 100; + const row223_x_mm = x226_base - 35; // dx = -35, asin(-1) = -π/2 + const csvContent = createTestCSV(row223_x_mm, x226_base); + + global.fetch.mockResolvedValueOnce({ + ok: true, + headers: { get: () => "text/csv" }, + text: async () => csvContent + }); + + const result = await calculate(); + const expected = calculateExpectedAngle(row223_x_mm, x226_base); + + expect(result.status).toBe("ok"); + + const elbowRotationCalc = result.calculations.find( + c => c.type === "elbowRotationAFromX223" + ); + + expect(elbowRotationCalc).toBeDefined(); + expect(elbowRotationCalc.output.angleDeg).toBeCloseTo(expected.angleDeg, 1); + expect(elbowRotationCalc.output.angleRad).toBeCloseTo(expected.angleRad, 3); + }); + + // Test 6: xCount > 2 Bedingung wird erfüllt + test("xCount muss > 2 sein, damit elbowRotationAFromX223 berechnet wird", async () => { + const x226_base = 100; + const row223_x_mm = x226_base + 10; + const csvContent = createTestCSV(row223_x_mm, x226_base); + + global.fetch.mockResolvedValueOnce({ + ok: true, + headers: { get: () => "text/csv" }, + text: async () => csvContent + }); + + const result = await calculate(); + + // Überprüfe, dass die Berechnung stattgefunden hat + const elbowXEstimate = result.calculations.find( + c => c.type === "elbowXEstimate" + ); + const elbowRotationCalc = result.calculations.find( + c => c.type === "elbowRotationAFromX223" + ); + + expect(elbowXEstimate).toBeDefined(); // xCount wurde berechnet + expect(elbowRotationCalc).toBeDefined(); // xCount > 2 und row223 existiert + }); + + // Test 7: Parametrisierter Test mit verschiedenen Winkeln + test.each([ + { dx: 0, expectedDeg: 90, desc: "dx=0" }, + { dx: 5, expectedDeg: 90 - Math.asin(5 / 35) * 180 / Math.PI, desc: "dx=5" }, + { dx: 10, expectedDeg: 90 - Math.asin(10 / 35) * 180 / Math.PI, desc: "dx=10" }, + { dx: -5, expectedDeg: 90 - Math.asin(-5 / 35) * 180 / Math.PI, desc: "dx=-5" }, + { dx: -10, expectedDeg: 90 - Math.asin(-10 / 35) * 180 / Math.PI, desc: "dx=-10" }, + ])( + "Verschiedene dx Werte sollten korrekte Winkel ergeben ($desc)", + async ({ dx, expectedDeg }) => { + const x226_base = 100; + const row223_x_mm = x226_base + dx; + const csvContent = createTestCSV(row223_x_mm, x226_base); + + global.fetch.mockResolvedValueOnce({ + ok: true, + headers: { get: () => "text/csv" }, + text: async () => csvContent + }); + + const result = await calculate(); + + const elbowRotationCalc = result.calculations.find( + c => c.type === "elbowRotationAFromX223" + ); + + expect(elbowRotationCalc).toBeDefined(); + expect(elbowRotationCalc.output.angleDeg).toBeCloseTo(expectedDeg, 1); + } + ); + + // Test 8: row223 Input wird korrekt in Output dokumentiert + test("row223.x_mm wird korrekt in Berechnung dokumentiert", async () => { + const x226_base = 100; + const row223_x_mm = 115; + const csvContent = createTestCSV(row223_x_mm, x226_base); + + global.fetch.mockResolvedValueOnce({ + ok: true, + headers: { get: () => "text/csv" }, + text: async () => csvContent + }); + + const result = await calculate(); + + const elbowRotationCalc = result.calculations.find( + c => c.type === "elbowRotationAFromX223" + ); + + expect(elbowRotationCalc).toBeDefined(); + expect(elbowRotationCalc.input.x223).toBe(row223_x_mm); + expect(elbowRotationCalc.input.x226).toBe(x226_base); + }); +});