예제 #1
0
            double[] GetClosestSolution(Transform transform, double[] prevJoints, out RobotConfigurations configuration, out List <string> errors, out double difference)
            {
                var solutions       = new double[8][];
                var solutionsErrors = new List <List <string> >(8);

                for (int i = 0; i < 8; i++)
                {
                    solutions[i] = InverseKinematics(transform, (RobotConfigurations)i, out var solutionErrors);
                    solutions[i] = JointTarget.GetAbsoluteJoints(solutions[i], prevJoints);
                    solutionsErrors.Add(solutionErrors);
                }

                int    closestSolutionIndex = 0;
                double closestDifference    = double.MaxValue;

                for (int i = 0; i < 8; i++)
                {
                    double currentDifference = prevJoints.Zip(solutions[i], (x, y) => SquaredDifference(x, y)).Sum();

                    if (currentDifference < closestDifference)
                    {
                        closestSolutionIndex = i;
                        closestDifference    = currentDifference;
                    }
                }

                difference    = closestDifference;
                configuration = (RobotConfigurations)closestSolutionIndex;
                errors        = solutionsErrors[closestSolutionIndex];
                return(solutions[closestSolutionIndex]);
            }
예제 #2
0
            List <string> SubModule(int file, int group)
            {
                bool   multiProgram = program.MultiFileIndices.Count > 1;
                string groupName    = cell.MechanicalGroups[group].Name;

                int start = program.MultiFileIndices[file];
                int end   = (file == program.MultiFileIndices.Count - 1) ? program.Targets.Count : program.MultiFileIndices[file + 1];
                var code  = new List <string>();

                if (multiProgram)
                {
                    code.Add($"MODULE {program.Name}_{groupName}_{file:000}");
                    code.Add($"PROC Main()");
                    code.Add("ConfL \\Off;");
                }

                for (int j = start; j < end; j++)
                {
                    var    programTarget = program.Targets[j].ProgramTargets[group];
                    var    target        = programTarget.Target;
                    string moveText      = null;
                    string zone          = target.Zone.IsFlyBy ? target.Zone.Name : "fine";
                    string id            = (cell.MechanicalGroups.Count > 1) ? id = $@"\ID:={programTarget.Index}" : "";
                    string external      = "extj";

                    if (cell.MechanicalGroups[group].Externals.Count > 0)
                    {
                        double[] values    = cell.MechanicalGroups[group].RadiansToDegreesExternal(target);
                        var      externals = new string[6];

                        for (int i = 0; i < 6; i++)
                        {
                            externals[i] = "9E9";
                        }

                        if (target.ExternalCustom == null)
                        {
                            for (int i = 0; i < target.External.Length; i++)
                            {
                                externals[i] = $"{values[i]:0.####}";
                            }
                        }
                        else
                        {
                            for (int i = 0; i < target.External.Length; i++)
                            {
                                string e = target.ExternalCustom[i];
                                if (!string.IsNullOrEmpty(e))
                                {
                                    externals[i] = e;
                                }
                            }
                        }

                        external = $"[{string.Join(",", externals)}]";
                    }

                    if (programTarget.IsJointTarget)
                    {
                        var      jointTarget = programTarget.Target as JointTarget;
                        double[] joints      = jointTarget.Joints;
                        joints   = joints.Select((x, i) => cell.MechanicalGroups[group].RadianToDegree(x, i)).ToArray();
                        moveText = $"MoveAbsJ [[{joints[0]:0.####},{joints[1]:0.####},{joints[2]:0.####},{joints[3]:0.####},{joints[4]:0.####},{joints[5]:0.####}],{external}]{id},{target.Speed.Name},{zone},{target.Tool.Name};";
                    }
                    else
                    {
                        var cartesian = programTarget.Target as CartesianTarget;
                        var plane     = cartesian.Plane;
                        // var quaternion = Quaternion.Rotation(Plane.WorldXY, plane);
                        Quaternion quaternion = GetRotation(plane);

                        switch (cartesian.Motion)
                        {
                        case Motions.Joint:
                        {
                            string pos    = $"[{plane.OriginX:0.###},{plane.OriginY:0.###},{plane.OriginZ:0.###}]";
                            string orient = $"[{quaternion.A:0.#####},{quaternion.B:0.#####},{quaternion.C:0.#####},{quaternion.D:0.#####}]";

                            int cf1 = (int)Floor(programTarget.Kinematics.Joints[0] / (PI / 2));
                            int cf4 = (int)Floor(programTarget.Kinematics.Joints[3] / (PI / 2));
                            int cf6 = (int)Floor(programTarget.Kinematics.Joints[5] / (PI / 2));

                            if (cf1 < 0)
                            {
                                cf1--;
                            }
                            if (cf4 < 0)
                            {
                                cf4--;
                            }
                            if (cf6 < 0)
                            {
                                cf6--;
                            }

                            RobotConfigurations configuration = programTarget.Kinematics.Configuration;
                            bool shoulder = configuration.HasFlag(RobotConfigurations.Shoulder);
                            bool elbow    = configuration.HasFlag(RobotConfigurations.Elbow);
                            if (shoulder)
                            {
                                elbow = !elbow;
                            }
                            bool wrist = configuration.HasFlag(RobotConfigurations.Wrist);

                            int cfx = 0;
                            if (wrist)
                            {
                                cfx += 1;
                            }
                            if (elbow)
                            {
                                cfx += 2;
                            }
                            if (shoulder)
                            {
                                cfx += 4;
                            }

                            string conf      = $"[{cf1},{cf4},{cf6},{cfx}]";
                            string robtarget = $"[{pos},{orient},{conf},{external}]";

                            moveText = $@"MoveJ {robtarget}{id},{target.Speed.Name},{zone},{target.Tool.Name} \WObj:={target.Frame.Name};";
                            break;
                        }

                        case Motions.Linear:
                        {
                            string pos       = $"[{plane.OriginX:0.###},{plane.OriginY:0.###},{plane.OriginZ:0.###}]";
                            string orient    = $"[{quaternion.A:0.#####},{quaternion.B:0.#####},{quaternion.C:0.#####},{quaternion.D:0.#####}]";
                            string robtarget = $"[{pos},{orient},conf,{external}]";
                            moveText = $@"MoveL {robtarget}{id},{target.Speed.Name},{zone},{target.Tool.Name} \WObj:={target.Frame.Name};";
                            break;
                        }
                        }
                    }

                    foreach (var command in programTarget.Commands.Where(c => c.RunBefore))
                    {
                        code.Add(command.Code(program, target));
                    }

                    code.Add(moveText);

                    foreach (var command in programTarget.Commands.Where(c => !c.RunBefore))
                    {
                        code.Add(command.Code(program, target));
                    }
                }

                if (!multiProgram)
                {
                    if (cell.MechanicalGroups.Count > 1)
                    {
                        code.Add($"SyncMoveOff sync2;");
                    }
                }

                code.Add("ENDPROC");
                code.Add("ENDMODULE");
                return(code);
            }
예제 #3
0
파일: Target.cs 프로젝트: visose/Robots
 public CartesianTarget(Plane plane, Target target, RobotConfigurations? configuration = null, Motions motion = Motions.Joint, IEnumerable<double> external = null) : this(plane, configuration, motion, target.Tool, target.Speed, target.Zone, target.Command, target.Frame, external ?? target.External) { }
예제 #4
0
파일: Target.cs 프로젝트: visose/Robots
 public CartesianTarget(Plane plane, RobotConfigurations? configuration = null, Motions motion = Motions.Joint, Tool tool = null, Speed speed = null, Zone zone = null, Command command = null, Frame frame = null, IEnumerable<double> external = null) : base(tool, speed, zone, command, frame, external)
 {
     this.Plane = plane;
     this.Motion = motion;
     this.Configuration = configuration;
 }
예제 #5
0
            /// <summary>
            /// Inverse kinematics for a offset wrist 6 axis robot.
            /// Code adapted from https://github.com/ros-industrial/universal_robot/blob/indigo-devel/ur_kinematics/src/ur_kin.cpp
            /// </summary>
            /// <param name="target">Cartesian target</param>
            /// <returns>Returns the 6 rotation values in radians.</returns>
            override protected double[] InverseKinematics(Transform transform, RobotConfigurations configuration, out List <string> errors)
            {
                errors = new List <string>();

                bool shoulder = configuration.HasFlag(RobotConfigurations.Shoulder);
                bool elbow    = configuration.HasFlag(RobotConfigurations.Elbow);

                if (shoulder)
                {
                    elbow = !elbow;
                }
                bool wrist = !configuration.HasFlag(RobotConfigurations.Wrist);

                if (shoulder)
                {
                    wrist = !wrist;
                }

                double[] joints        = new double[6];
                bool     isUnreachable = false;

                transform *= Transform.Rotation(PI / 2, Point3d.Origin);

                double[] a = mechanism.Joints.Select(joint => joint.A).ToArray();
                double[] d = mechanism.Joints.Select(joint => joint.D).ToArray();

                // shoulder
                {
                    double A = d[5] * transform[1, 2] - transform[1, 3];
                    double B = d[5] * transform[0, 2] - transform[0, 3];
                    double R = A * A + B * B;

                    double arccos = Acos(d[3] / Sqrt(R));
                    if (double.IsNaN(arccos))
                    {
                        errors.Add($"Overhead singularity.");
                        arccos = 0;
                    }

                    double arctan = Atan2(-B, A);

                    if (!shoulder)
                    {
                        joints[0] = arccos + arctan;
                    }
                    else
                    {
                        joints[0] = -arccos + arctan;
                    }
                }

                // wrist 2
                {
                    double numer = (transform[0, 3] * Sin(joints[0]) - transform[1, 3] * Cos(joints[0]) - d[3]);
                    double div   = numer / d[5];

                    double arccos = Acos(div);
                    if (double.IsNaN(arccos))
                    {
                        errors.Add($"Overhead singularity 2.");
                        arccos        = PI;
                        isUnreachable = true;
                    }

                    if (!wrist)
                    {
                        joints[4] = arccos;
                    }
                    else
                    {
                        joints[4] = 2.0 * PI - arccos;
                    }
                }

                // rest
                {
                    double c1 = Cos(joints[0]);
                    double s1 = Sin(joints[0]);
                    double c5 = Cos(joints[4]);
                    double s5 = Sin(joints[4]);

                    joints[5] = Atan2(Sign(s5) * -(transform[0, 1] * s1 - transform[1, 1] * c1), Sign(s5) * (transform[0, 0] * s1 - transform[1, 0] * c1));

                    double c6 = Cos(joints[5]), s6 = Sin(joints[5]);
                    double x04x = -s5 * (transform[0, 2] * c1 + transform[1, 2] * s1) - c5 * (s6 * (transform[0, 1] * c1 + transform[1, 1] * s1) - c6 * (transform[0, 0] * c1 + transform[1, 0] * s1));
                    double x04y = c5 * (transform[2, 0] * c6 - transform[2, 1] * s6) - transform[2, 2] * s5;
                    double p13x = d[4] * (s6 * (transform[0, 0] * c1 + transform[1, 0] * s1) + c6 * (transform[0, 1] * c1 + transform[1, 1] * s1)) - d[5] * (transform[0, 2] * c1 + transform[1, 2] * s1) + transform[0, 3] * c1 + transform[1, 3] * s1;
                    double p13y = transform[2, 3] - d[0] - d[5] * transform[2, 2] + d[4] * (transform[2, 1] * c6 + transform[2, 0] * s6);
                    double c3   = (p13x * p13x + p13y * p13y - a[1] * a[1] - a[2] * a[2]) / (2.0 * a[1] * a[2]);

                    double arccos = Acos(c3);
                    if (double.IsNaN(arccos))
                    {
                        arccos        = 0;
                        isUnreachable = true;
                    }

                    if (!elbow)
                    {
                        joints[2] = arccos;
                    }
                    else
                    {
                        joints[2] = 2.0 * PI - arccos;
                    }

                    double denom = a[1] * a[1] + a[2] * a[2] + 2 * a[1] * a[2] * c3;
                    double s3    = Sin(arccos);
                    double A     = (a[1] + a[2] * c3);
                    double B     = a[2] * s3;

                    if (!elbow)
                    {
                        joints[1] = Atan2((A * p13y - B * p13x) / denom, (A * p13x + B * p13y) / denom);
                    }
                    else
                    {
                        joints[1] = Atan2((A * p13y + B * p13x) / denom, (A * p13x - B * p13y) / denom);
                    }

                    double c23_0 = Cos(joints[1] + joints[2]);
                    double s23_0 = Sin(joints[1] + joints[2]);

                    joints[3] = Atan2(c23_0 * x04y - s23_0 * x04x, x04x * c23_0 + x04y * s23_0);
                }

                if (isUnreachable)
                {
                    errors.Add($"Target out of reach.");
                }

                //   joints[5] += PI / 2;

                for (int i = 0; i < 6; i++)
                {
                    if (joints[i] > PI)
                    {
                        joints[i] -= 2.0 * PI;
                    }
                    if (joints[i] < -PI)
                    {
                        joints[i] += 2.0 * PI;
                    }
                }

                return(joints);
            }
예제 #6
0
            /// <summary>
            /// Inverse kinematics for a spherical wrist 6 axis robot.
            /// Code adapted from https://github.com/whitegreen/KinematikJava
            /// </summary>
            /// <param name="target">Cartesian target</param>
            /// <returns>Returns the 6 rotation values in radians.</returns>
            override protected double[] InverseKinematics(Transform transform, RobotConfigurations configuration, out List <string> errors)
            {
                errors = new List <string>();

                bool shoulder = configuration.HasFlag(RobotConfigurations.Shoulder);
                bool elbow    = configuration.HasFlag(RobotConfigurations.Elbow);

                if (shoulder)
                {
                    elbow = !elbow;
                }
                bool wrist = !configuration.HasFlag(RobotConfigurations.Wrist);

                bool isUnreachable = false;

                double[] a = mechanism.Joints.Select(joint => joint.A).ToArray();
                double[] d = mechanism.Joints.Select(joint => joint.D).ToArray();

                Plane flange = Plane.WorldXY;

                flange.Transform(transform);

                double[] joints = new double[6];

                double  l2     = Sqrt(a[2] * a[2] + d[3] * d[3]);
                double  ad2    = Atan2(a[2], d[3]);
                Point3d center = flange.Origin - flange.Normal * d[5];

                joints[0] = Atan2(center.Y, center.X);
                double  ll = Sqrt(center.X * center.X + center.Y * center.Y);
                Point3d p1 = new Point3d(a[0] * center.X / ll, a[0] * center.Y / ll, d[0]);

                if (shoulder)
                {
                    joints[0] += PI;
                    var rotate = Transform.Rotation(PI, new Point3d(0, 0, 0));
                    center.Transform(rotate);
                }

                double l3   = (center - p1).Length;
                double l1   = a[1];
                double beta = Acos((l1 * l1 + l3 * l3 - l2 * l2) / (2 * l1 * l3));

                if (double.IsNaN(beta))
                {
                    beta          = 0;
                    isUnreachable = true;
                }
                if (elbow)
                {
                    beta *= -1;
                }

                double ttl = new Vector3d(center.X - p1.X, center.Y - p1.Y, 0).Length;

                // if (p1.X * (center.X - p1.X) < 0)
                if (shoulder)
                {
                    ttl = -ttl;
                }
                double al = Atan2(center.Z - p1.Z, ttl);

                joints[1] = beta + al;

                double gama = Acos((l1 * l1 + l2 * l2 - l3 * l3) / (2 * l1 * l2));

                if (double.IsNaN(gama))
                {
                    gama          = PI;
                    isUnreachable = true;
                }
                if (elbow)
                {
                    gama *= -1;
                }

                joints[2] = gama - ad2 - PI / 2;

                double[] c = new double[3];
                double[] s = new double[3];
                for (int i = 0; i < 3; i++)
                {
                    c[i] = Cos(joints[i]);
                    s[i] = Sin(joints[i]);
                }

                var arr = new Transform();

                arr[0, 0] = c[0] * (c[1] * c[2] - s[1] * s[2]); arr[0, 1] = s[0]; arr[0, 2] = c[0] * (c[1] * s[2] + s[1] * c[2]); arr[0, 3] = c[0] * (a[2] * (c[1] * c[2] - s[1] * s[2]) + a[1] * c[1]) + a[0] * c[0];
                arr[1, 0] = s[0] * (c[1] * c[2] - s[1] * s[2]); arr[1, 1] = -c[0]; arr[1, 2] = s[0] * (c[1] * s[2] + s[1] * c[2]); arr[1, 3] = s[0] * (a[2] * (c[1] * c[2] - s[1] * s[2]) + a[1] * c[1]) + a[0] * s[0];
                arr[2, 0] = s[1] * c[2] + c[1] * s[2]; arr[2, 1] = 0; arr[2, 2] = s[1] * s[2] - c[1] * c[2]; arr[2, 3] = a[2] * (s[1] * c[2] + c[1] * s[2]) + a[1] * s[1] + d[0];
                arr[3, 0] = 0; arr[3, 1] = 0; arr[3, 2] = 0; arr[3, 3] = 1;

                arr.TryGetInverse(out var in123);

                var mr = Transform.Multiply(in123, transform);

                joints[3] = Atan2(mr[1, 2], mr[0, 2]);
                joints[4] = Acos(mr[2, 2]);
                joints[5] = Atan2(mr[2, 1], -mr[2, 0]);

                if (wrist)
                {
                    joints[3] += PI;
                    joints[4] *= -1;
                    joints[5] -= PI;
                }

                for (int i = 0; i < 6; i++)
                {
                    if (joints[i] > PI)
                    {
                        joints[i] -= 2 * PI;
                    }
                    if (joints[i] < -PI)
                    {
                        joints[i] += 2 * PI;
                    }
                }

                if (isUnreachable)
                {
                    errors.Add($"Target out of reach");
                }

                if (Abs(1 - mr[2, 2]) < 0.0001)
                {
                    errors.Add($"Near wrist singularity");
                }

                if (new Vector3d(center.X, center.Y, 0).Length < a[0] + SingularityTol)
                {
                    errors.Add($"Near overhead singularity");
                }

                for (int i = 0; i < 6; i++)
                {
                    if (double.IsNaN(joints[i]))
                    {
                        joints[i] = 0;
                    }
                }
                return(joints);
            }
예제 #7
0
 protected abstract double[] InverseKinematics(Transform transform, RobotConfigurations configuration, out List <string> errors);
예제 #8
0
    /// <summary>
    /// Inverse kinematics for a offset wrist 6 axis robot.
    /// Code adapted from https://github.com/ros-industrial/universal_robot/blob/indigo-devel/ur_kinematics/src/ur_kin.cpp
    /// </summary>
    /// <param name="target">Cartesian target</param>
    /// <returns>Returns the 6 rotation values in radians.</returns>
    protected override double[] InverseKinematics(Transform transform, RobotConfigurations configuration, out List <string> errors)
    {
        errors = new List <string>();

        bool shoulder = configuration.HasFlag(RobotConfigurations.Shoulder);
        bool elbow    = configuration.HasFlag(RobotConfigurations.Elbow);

        if (shoulder)
        {
            elbow = !elbow;
        }

        bool wrist = !configuration.HasFlag(RobotConfigurations.Wrist);

        if (shoulder)
        {
            wrist = !wrist;
        }

        double[] joints        = new double[6];
        bool     isUnreachable = false;

        transform *= Transform.Rotation(HalfPI, Point3d.Origin);

        var a = Vector6d.Map(_mechanism.Joints, j => j.A);
        var d = Vector6d.Map(_mechanism.Joints, j => j.D);

        // shoulder
        {
            double A = d[5] * transform.M12 - transform.M13;
            double B = d[5] * transform.M02 - transform.M03;
            double R = A * A + B * B;

            double arccos = Acos(d[3] / Sqrt(R));
            if (double.IsNaN(arccos))
            {
                errors.Add($"Overhead singularity.");
                arccos = 0;
            }

            double arctan = Atan2(-B, A);

            joints[0] = !shoulder ? arccos + arctan : -arccos + arctan;
        }

        // wrist 2
        {
            double numer = (transform.M03 * Sin(joints[0]) - transform.M13 * Cos(joints[0]) - d[3]);
            double div   = numer / d[5];

            double arccos = Acos(div);
            if (double.IsNaN(arccos))
            {
                errors.Add($"Overhead singularity 2.");
                arccos        = PI;
                isUnreachable = true;
            }

            joints[4] = !wrist ? arccos : 2.0 * PI - arccos;
        }

        // rest
        {
            double c1 = Cos(joints[0]);
            double s1 = Sin(joints[0]);
            double c5 = Cos(joints[4]);
            double s5 = Sin(joints[4]);

            joints[5] = Atan2(Sign(s5) * -(transform.M01 * s1 - transform.M11 * c1), Sign(s5) * (transform.M00 * s1 - transform.M10 * c1));

            double c6 = Cos(joints[5]), s6 = Sin(joints[5]);
            double x04x = -s5 * (transform.M02 * c1 + transform.M12 * s1) - c5 * (s6 * (transform.M01 * c1 + transform.M11 * s1) - c6 * (transform.M00 * c1 + transform.M10 * s1));
            double x04y = c5 * (transform.M20 * c6 - transform.M21 * s6) - transform.M22 * s5;
            double p13x = d[4] * (s6 * (transform.M00 * c1 + transform.M10 * s1) + c6 * (transform.M01 * c1 + transform.M11 * s1)) - d[5] * (transform.M02 * c1 + transform.M12 * s1) + transform.M03 * c1 + transform.M13 * s1;
            double p13y = transform.M23 - d[0] - d[5] * transform.M22 + d[4] * (transform.M21 * c6 + transform.M20 * s6);
            double c3   = (p13x * p13x + p13y * p13y - a[1] * a[1] - a[2] * a[2]) / (2.0 * a[1] * a[2]);

            double arccos = Acos(c3);
            if (double.IsNaN(arccos))
            {
                arccos        = 0;
                isUnreachable = true;
            }

            joints[2] = !elbow ? arccos : PI2 - arccos;

            double denom = a[1] * a[1] + a[2] * a[2] + 2 * a[1] * a[2] * c3;
            double s3    = Sin(arccos);
            double A     = (a[1] + a[2] * c3);
            double B     = a[2] * s3;

            joints[1] = !elbow
                ? Atan2((A *p13y - B *p13x) / denom, (A *p13x + B *p13y) / denom)
                : Atan2((A * p13y + B * p13x) / denom, (A * p13x - B * p13y) / denom);

            double c23_0 = Cos(joints[1] + joints[2]);
            double s23_0 = Sin(joints[1] + joints[2]);

            joints[3] = Atan2(c23_0 * x04y - s23_0 * x04x, x04x * c23_0 + x04y * s23_0);
        }

        if (isUnreachable)
        {
            errors.Add($"Target out of reach.");
        }

        //   joints[5] += PI / 2;

        for (int i = 0; i < 6; i++)
        {
            if (joints[i] > PI)
            {
                joints[i] -= PI2;
            }

            if (joints[i] < -PI)
            {
                joints[i] += PI2;
            }
        }

        return(joints);
    }
예제 #9
0
    /// <summary>
    /// Inverse kinematics for a spherical wrist 6 axis robot.
    /// Code adapted from https://github.com/whitegreen/KinematikJava
    /// </summary>
    /// <param name="target">Cartesian target</param>
    /// <returns>Returns the 6 rotation values in radians.</returns>
    protected override double[] InverseKinematics(Transform transform, RobotConfigurations configuration, out List <string> errors)
    {
        errors = new List <string>();

        bool shoulder = configuration.HasFlag(RobotConfigurations.Shoulder);
        bool elbow    = configuration.HasFlag(RobotConfigurations.Elbow);

        if (shoulder)
        {
            elbow = !elbow;
        }
        bool wrist = !configuration.HasFlag(RobotConfigurations.Wrist);

        bool isUnreachable = false;

        var a = Vector6d.Map(_mechanism.Joints, joint => joint.A);
        var d = Vector6d.Map(_mechanism.Joints, joint => joint.D);

        var flange = transform.ToPlane();

        double[] joints = new double[6];

        double  l2     = Sqrt(a[2] * a[2] + d[3] * d[3]);
        double  ad2    = Atan2(a[2], d[3]);
        Point3d center = flange.Origin - flange.Normal * d[5];

        joints[0] = Atan2(center.Y, center.X);
        double ll = Sqrt(center.X * center.X + center.Y * center.Y);
        var    p1 = new Point3d(a[0] * center.X / ll, a[0] * center.Y / ll, d[0]);

        if (shoulder)
        {
            joints[0] += PI;
            var rotate = Transform.Rotation(PI, new Point3d(0, 0, 0));
            center.Transform(rotate);
        }

        double l3   = (center - p1).Length;
        double l1   = a[1];
        double beta = Acos((l1 * l1 + l3 * l3 - l2 * l2) / (2 * l1 * l3));

        if (double.IsNaN(beta))
        {
            beta          = 0;
            isUnreachable = true;
        }

        if (elbow)
        {
            beta *= -1;
        }

        double ttl = new Vector3d(center.X - p1.X, center.Y - p1.Y, 0).Length;

        // if (p1.X * (center.X - p1.X) < 0)
        if (shoulder)
        {
            ttl = -ttl;
        }

        double al = Atan2(center.Z - p1.Z, ttl);

        joints[1] = beta + al;

        double gama = Acos((l1 * l1 + l2 * l2 - l3 * l3) / (2 * l1 * l2));

        if (double.IsNaN(gama))
        {
            gama          = PI;
            isUnreachable = true;
        }

        if (elbow)
        {
            gama *= -1;
        }

        joints[2] = gama - ad2 - PI / 2;

        Vector3d c = default;
        Vector3d s = default;

        for (int i = 0; i < 3; i++)
        {
            c[i] = Cos(joints[i]);
            s[i] = Sin(joints[i]);
        }

        Transform arr = default;

        arr.Set(
            c[0] * (c[1] * c[2] - s[1] * s[2]), s[0], c[0] * (c[1] * s[2] + s[1] * c[2]), c[0] * (a[2] * (c[1] * c[2] - s[1] * s[2]) + a[1] * c[1]) + a[0] * c[0],
            s[0] * (c[1] * c[2] - s[1] * s[2]), -c[0], s[0] * (c[1] * s[2] + s[1] * c[2]), s[0] * (a[2] * (c[1] * c[2] - s[1] * s[2]) + a[1] * c[1]) + a[0] * s[0],
            s[1] * c[2] + c[1] * s[2], 0, s[1] * s[2] - c[1] * c[2], a[2] * (s[1] * c[2] + c[1] * s[2]) + a[1] * s[1] + d[0]
            );

        arr.TryGetInverse(out var in123);
        var mr = in123 * transform;

        joints[3] = Atan2(mr.M12, mr.M02);
        joints[4] = Acos(mr.M22);
        joints[5] = Atan2(mr.M21, -mr.M20);

        if (wrist)
        {
            joints[3] += PI;
            joints[4] *= -1;
            joints[5] -= PI;
        }

        for (int i = 0; i < 6; i++)
        {
            if (joints[i] > PI)
            {
                joints[i] -= 2 * PI;
            }
            if (joints[i] < -PI)
            {
                joints[i] += 2 * PI;
            }
        }

        if (isUnreachable)
        {
            errors.Add($"Target out of reach");
        }

        if (Abs(1 - mr.M22) < 0.0001)
        {
            errors.Add($"Near wrist singularity");
        }

        if (new Vector3d(center.X, center.Y, 0).Length < a[0] + SingularityTol)
        {
            errors.Add($"Near overhead singularity");
        }

        for (int i = 0; i < 6; i++)
        {
            if (double.IsNaN(joints[i]))
            {
                joints[i] = 0;
            }
        }

        return(joints);
    }