From 3241e253ddc377735fe71f15241b12db98810fb2 Mon Sep 17 00:00:00 2001 From: Peter Vaiko Date: Sat, 10 May 2025 05:49:27 -0400 Subject: [PATCH 01/10] fix: harvester headland turn --- config/VehicleConfigurations.xml | 2 +- scripts/ai/turns/AITurn.lua | 111 ++++++++---------------------- scripts/ai/turns/TurnManeuver.lua | 19 ++++- 3 files changed, 45 insertions(+), 87 deletions(-) diff --git a/config/VehicleConfigurations.xml b/config/VehicleConfigurations.xml index b594851fc..4d8c741c1 100644 --- a/config/VehicleConfigurations.xml +++ b/config/VehicleConfigurations.xml @@ -80,7 +80,7 @@ You can define the following custom settings: - lowerEarly: boolean Similar to the above, the default behavior when starting to work is lower the implement when the back of the work area reaches to row start/field edge. Setting this to true will make Courseplay lower the implement as - soon as the the front reaches the row start/field edge, avoiding unworked patches with irregular implement + soon as the front reaches the row start/field edge, avoiding unworked patches with irregular implement work areas such as a plow. - useVehicleSizeForMarkers: boolean diff --git a/scripts/ai/turns/AITurn.lua b/scripts/ai/turns/AITurn.lua index 5b291ae7e..79eecf83b 100644 --- a/scripts/ai/turns/AITurn.lua +++ b/scripts/ai/turns/AITurn.lua @@ -378,7 +378,6 @@ function KTurn:onWaypointPassed(ix, course) end function KTurn:startTurn() - AITurn.startTurn(self) self.state = self.states.FORWARD end @@ -438,87 +437,6 @@ function KTurn:turn(dt) return gx, gz, moveForwards, maxSpeed end ---[[ - Headland turn for combines: - 1. drive forward to the field edge or the headland path edge - 2. start turning forward - 3. reverse straight and then align with the direction after the - corner while reversing - 4. forward to the turn start to continue on headland -]] ----@class CombineHeadlandTurn : AITurn -CombineHeadlandTurn = CpObject(AITurn) - ----@param driveStrategy AIDriveStrategyFieldWorkCourse ----@param turnContext TurnContext -function CombineHeadlandTurn:init(vehicle, driveStrategy, ppc, proximityController, turnContext) - AITurn.init(self, vehicle, driveStrategy, ppc, proximityController, turnContext, 'CombineHeadlandTurn') - self:addState('FORWARD') - self:addState('REVERSE_STRAIGHT') - self:addState('REVERSE_ARC') - self.turningRadius = AIUtil.getTurningRadius(self.vehicle) - self.cornerAngleToTurn = turnContext:getCornerAngleToTurn() - -- half the turn angle but not less than 45 - self.angleToTurnInReverse = math.max(math.pi / 4, math.abs(self.cornerAngleToTurn / 2)) - self.dxToStartReverseTurn = self.turningRadius - math.abs(self.turningRadius - self.turningRadius * math.cos(self.cornerAngleToTurn)) -end - -function CombineHeadlandTurn:startTurn() - self.state = self.states.FORWARD - self:debug('Starting combine headland turn') -end - -function CombineHeadlandTurn:onWaypointChange(ix, course) - -- nothing to do -end - -function CombineHeadlandTurn:onWaypointPassed(ix, course) - -- nothing to do, especially because the row finishing course is still active in the PPC and we may - -- pass the last waypoint which causes the turn to end and return to field work -end - -function CombineHeadlandTurn:turn(dt) - local gx, gz, moveForwards, maxSpeed = AITurn.turn(self) - local dx, _, dz = self.turnContext:getLocalPositionFromTurnEnd(self.vehicle:getAIDirectionNode()) - local angleToTurnEnd = math.abs(self.turnContext:getAngleToTurnEndDirection(self.vehicle:getAIDirectionNode())) - if self.state == self.states.FORWARD then - maxSpeed = self:getForwardSpeed() - moveForwards = true - if angleToTurnEnd > self.angleToTurnInReverse then - --and not self.turnContext:isLateralDistanceLess(dx, self.dxToStartReverseTurn) then - -- full turn towards the turn end direction - gx, gz = self:getGoalPointForTurn(moveForwards, self.turnContext:isLeftTurn()) - else - -- reverse until we can make turn to the turn end point - self.state = self.states.REVERSE_STRAIGHT - self:debug('Combine headland turn start reversing straight') - end - - elseif self.state == self.states.REVERSE_STRAIGHT then - maxSpeed = self:getReverseSpeed() - moveForwards = false - gx, gz = self:getGoalPointForTurn(moveForwards, nil) - if math.abs(dx) < 0.2 then - self.state = self.states.REVERSE_ARC - self:debug('Combine headland turn start reversing arc') - end - - elseif self.state == self.states.REVERSE_ARC then - maxSpeed = self:getReverseSpeed() - moveForwards = false - gx, gz = self:getGoalPointForTurn(moveForwards, not self.turnContext:isLeftTurn()) - if angleToTurnEnd < math.rad(20) then - self.state = self.states.ENDING_TURN - self:debug('Combine headland turn forwarding again') - -- lower implements here unconditionally (regardless of the direction, self:endTurn() would wait until we - -- are pointing to the turn target direction) - self.driveStrategy:lowerImplements() - self:resumeFieldworkAfterTurn(self.turnContext.turnEndWpIx) - end - end - return gx, gz, moveForwards, maxSpeed -end - --[[ A turn maneuver following a course (waypoints created by turn.lua) ]] @@ -586,7 +504,6 @@ end -- = if turn on field setting is off, use pathfinder turns if enabled in settings, calculated turns otherwise -- function CourseTurn:startTurn() - AITurn.startTurn(self) local canTurnOnField = AITurn.canTurnOnField(self.turnContext, self.vehicle, self.workWidth, self.turningRadius) if self.turnContext:isHeadlandCorner() then self:debug('Starting a headland corner turn') @@ -750,7 +667,7 @@ function CourseTurn:generateCalculatedTurn() self.enableTightTurnOffset = true else turnManeuver = HeadlandCornerTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(), - self.turningRadius, self.workWidth, self.reversingImplement, self.steeringLength) + self.turningRadius, self.workWidth, self.reversingImplement, self.steeringLength) -- adjust turn course for tight turns only for headland corners by default self.forceTightTurnOffset = self.steeringLength > 0 end @@ -826,6 +743,32 @@ function CourseTurn:drawDebug() end end +--[[ + Headland turn for combines: + 1. drive forward to the field edge or the headland path edge + 2. start turning forward + 3. reverse straight and then align with the direction after the + corner while reversing + 4. forward to the turn start to continue on headland +]] +---@class CombineHeadlandTurn : CourseTurn +CombineHeadlandTurn = CpObject(CourseTurn) + +function CombineHeadlandTurn:init(vehicle, driveStrategy, ppc, proximityController, turnContext, fieldWorkCourse, + workWidth, name) + CourseTurn.init(self, vehicle, driveStrategy, ppc, proximityController, turnContext, fieldWorkCourse, + workWidth, name or 'CombineHeadlandTurn') +end + +function CombineHeadlandTurn:startTurn() + self:debug('Starting a combine headland turn') + self.turnCourse = ReedsSheppHeadlandTurn(self.vehicle, self.turnContext, + self.turnContext.vehicleAtTurnStartNode, self.turningRadius):getCourse() + self.state = self.states.TURNING + self.ppc:setCourse(self.turnCourse) + self.ppc:initialize(1) +end + --- A turn maneuver to recover when the vehicle is blocked by an object (tree, fence, etc) during the turn --- This should only be initiated when the state is TURNING, so after the row is finished and before starting --- the new row. diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index c05876cff..dd6f6e6ae 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -434,7 +434,7 @@ function AnalyticTurnManeuver:getDistanceToMoveBack(course, workWidth, distanceT -- the field, not only the center local headlandAngle = self.turnContext:getHeadlandAngle() distanceToFieldEdge = distanceToFieldEdge - - -- exclude very sharp headland angles to prevent moving back ridiculously far + -- exclude very sharp headland angles to prevent moving back ridiculously far ((headlandAngle > math.deg(10) and headlandAngle < math.deg(170)) and (workWidth / 2 / math.abs(math.tan(headlandAngle))) or 0) self:debug('dzMax=%.1f, workWidth=%.1f, spaceNeeded=%.1f, turnEndForwardOffset=%.1f, headlandAngle=%.1f, distanceToFieldEdge=%.1f', dzMax, workWidth, @@ -465,7 +465,7 @@ end ---@class LoopTurnManeuver : TurnManeuver LoopTurnManeuver = CpObject(DubinsTurnManeuver) function LoopTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turningRadius, - workWidth, steeringLength) + workWidth, steeringLength) self.debugPrefix = '(LoopTurn): ' TurnManeuver.init(self, vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength) @@ -552,6 +552,21 @@ function ReedsSheppTurnManeuver:findAnalyticPath(vehicleDirectionNode, startXOff return course end +---@class ReedsSheppHeadlandTurn : TurnManeuver +ReedsSheppHeadlandTurn = CpObject(TurnManeuver) + +--- This is a headland turn (~90 degrees) for non-towed harvesters with cutter on the front. Expected to be called +--- just after the cutter finished the corner, that is, the harvester should drive forward in the original direction +--- until there is no fruit left. It'll then do a quick 90 degree 3 point turn to align with the new direction. +function ReedsSheppHeadlandTurn:init(vehicle, turnContext, vehicleDirectionNode, turningRadius) + local solver = ReedsSheppSolver() + -- use lateWorkStartNode since we covered the corner in the inbound direction already + local path = PathfinderUtil.findAnalyticPath(solver, vehicleDirectionNode, 0, 0, + turnContext.lateWorkStartNode, 0, -turnContext.backMarkerDistance, turningRadius) + self.course = Course.createFromAnalyticPath(vehicle, path, true) + self.course:adjustForReversing(2) +end + ---@class TurnEndingManeuver : TurnManeuver TurnEndingManeuver = CpObject(TurnManeuver) From 36e027f25eccb4d2b63f8cf660ce3a72f4fa9098 Mon Sep 17 00:00:00 2001 From: Peter Vaiko Date: Sat, 10 May 2025 06:40:48 -0400 Subject: [PATCH 02/10] fix: Reeds-Shepp headland turn Use a Reeds-Shepp turn instead of the old combine headland turn which used angles and relative distances to steer the vehicle. #786 --- scripts/Course.lua | 4 ++-- scripts/CpUtil.lua | 2 +- scripts/ai/turns/AITurn.lua | 2 +- scripts/ai/turns/TurnManeuver.lua | 4 ++++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/scripts/Course.lua b/scripts/Course.lua index 0e5a178ea..5a28481d6 100644 --- a/scripts/Course.lua +++ b/scripts/Course.lua @@ -915,13 +915,13 @@ function Course:extend(length, dx, dz) for i = first, last, step do local x = lastWp.x + dx * i local z = lastWp.z + dz * i - self:appendWaypoint({ x = x, z = z }) + self:appendWaypoint({ x = x, z = z, rev = lastWp.rev }) end if length % step > 0 then -- add the remainder to make sure we extend all the way up to length local x = lastWp.x + dx * length local z = lastWp.z + dz * length - self:appendWaypoint({ x = x, z = z }) + self:appendWaypoint({ x = x, z = z, rev = lastWp.rev }) end -- enrich the waypoints we added self:enrichWaypointData(nWaypoints) diff --git a/scripts/CpUtil.lua b/scripts/CpUtil.lua index 85a433d90..7e0047408 100644 --- a/scripts/CpUtil.lua +++ b/scripts/CpUtil.lua @@ -466,7 +466,7 @@ function CpUtil.getAllRootVegetables() local preparedGrowthState = fruitTypeData.preparedGrowthState local name = fruitTypeData.name - -- check if fruit is needs herb removement to be harvested + -- check if fruit is needs herb removal to be harvested if minPreparingGrowthState ~= -1 and preparedGrowthState ~= -1 and name ~= "SUGARCANE" then local fruitType = g_fruitTypeManager:getFruitTypeByName(name) if fruitType ~= nil then diff --git a/scripts/ai/turns/AITurn.lua b/scripts/ai/turns/AITurn.lua index 79eecf83b..4fe59a8f4 100644 --- a/scripts/ai/turns/AITurn.lua +++ b/scripts/ai/turns/AITurn.lua @@ -763,7 +763,7 @@ end function CombineHeadlandTurn:startTurn() self:debug('Starting a combine headland turn') self.turnCourse = ReedsSheppHeadlandTurn(self.vehicle, self.turnContext, - self.turnContext.vehicleAtTurnStartNode, self.turningRadius):getCourse() + self.vehicle:getAIDirectionNode(), self.turningRadius):getCourse() self.state = self.states.TURNING self.ppc:setCourse(self.turnCourse) self.ppc:initialize(1) diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index dd6f6e6ae..d3931912d 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -565,6 +565,10 @@ function ReedsSheppHeadlandTurn:init(vehicle, turnContext, vehicleDirectionNode, turnContext.lateWorkStartNode, 0, -turnContext.backMarkerDistance, turningRadius) self.course = Course.createFromAnalyticPath(vehicle, path, true) self.course:adjustForReversing(2) + -- add a little straight section to the end so we have a little buffer and don't end the turn right at + -- the work start + self.course:extend(5) + TurnManeuver.setLowerImplements(self.course, 5, true) end ---@class TurnEndingManeuver : TurnManeuver From 900adcdd96ee02b008c5953789bba202184b9169 Mon Sep 17 00:00:00 2001 From: Peter Vaiko Date: Sat, 10 May 2025 06:45:02 -0400 Subject: [PATCH 03/10] fix: no special handling for root veggie harvesters #786 --- scripts/ai/controllers/CombineController.lua | 14 -------------- .../ai/strategies/AIDriveStrategyCombineCourse.lua | 7 ------- 2 files changed, 21 deletions(-) diff --git a/scripts/ai/controllers/CombineController.lua b/scripts/ai/controllers/CombineController.lua index 77e9820fc..4aa3eec51 100644 --- a/scripts/ai/controllers/CombineController.lua +++ b/scripts/ai/controllers/CombineController.lua @@ -128,20 +128,6 @@ function CombineController:isDroppingStrawSwath() return self.combineSpec.strawPSenabled end -function CombineController:isRootVegetableHarvester() - for _, fruitTypeIndex in pairs(CpUtil.getAllRootVegetables()) do - local fillUnitIndex = g_fruitTypeManager:getFillTypeIndexByFruitTypeIndex(fruitTypeIndex) - self:debug("check if fruitType %s is supported", g_fillTypeManager:getFillTypeNameByIndex(fillUnitIndex)) - for i, _ in ipairs(self.implement:getFillUnits()) do - if self.implement:getFillUnitSupportsFillType(i, fillUnitIndex) then - self:debug('This is a root vegetable harvester.') - return true - end - end - end - return false -end - --- Is this a towed harvester? We don't want these to make combine headland turns (or make pockets?) function CombineController:isTowed() return self.isWheeledImplement diff --git a/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua index cc6b730ea..8f3f72830 100644 --- a/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua @@ -1412,13 +1412,6 @@ function AIDriveStrategyCombineCourse:startTurn(ix) if self.combineController:isTowed() then self:debug('Headland turn but this is a towed harvester using normal turn maneuvers.') AIDriveStrategyFieldWorkCourse.startTurn(self, ix) - -- The type of fruit being harvested isn't really the indicator if we can make a headland turn - -- TODO: either make disabling combine headland turns configurable, or - -- TODO: decide automatically based on the vehicle's properties, like turn radius, work width, etc. - -- and disable when such a turn does not make sense for the vehicle. - elseif self.combineController:isRootVegetableHarvester() then - self:debug('Headland turn but this harvester uses normal turn maneuvers.') - AIDriveStrategyFieldWorkCourse.startTurn(self, ix) elseif self.course:isOnConnectingPath(ix) then self:debug('Headland turn but this a connecting track, use normal turn maneuvers.') AIDriveStrategyFieldWorkCourse.startTurn(self, ix) From febf885c99651642222306f1279cbae90ee584ba Mon Sep 17 00:00:00 2001 From: Peter Vaiko Date: Sat, 10 May 2025 07:30:27 -0400 Subject: [PATCH 04/10] fix: no pocket headland turn for root veggie harvesters #786 --- scripts/ai/controllers/CombineController.lua | 14 ++++++++++++++ .../ai/strategies/AIDriveStrategyCombineCourse.lua | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/ai/controllers/CombineController.lua b/scripts/ai/controllers/CombineController.lua index 4aa3eec51..77e9820fc 100644 --- a/scripts/ai/controllers/CombineController.lua +++ b/scripts/ai/controllers/CombineController.lua @@ -128,6 +128,20 @@ function CombineController:isDroppingStrawSwath() return self.combineSpec.strawPSenabled end +function CombineController:isRootVegetableHarvester() + for _, fruitTypeIndex in pairs(CpUtil.getAllRootVegetables()) do + local fillUnitIndex = g_fruitTypeManager:getFillTypeIndexByFruitTypeIndex(fruitTypeIndex) + self:debug("check if fruitType %s is supported", g_fillTypeManager:getFillTypeNameByIndex(fillUnitIndex)) + for i, _ in ipairs(self.implement:getFillUnits()) do + if self.implement:getFillUnitSupportsFillType(i, fillUnitIndex) then + self:debug('This is a root vegetable harvester.') + return true + end + end + end + return false +end + --- Is this a towed harvester? We don't want these to make combine headland turns (or make pockets?) function CombineController:isTowed() return self.isWheeledImplement diff --git a/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua index 8f3f72830..113a33dc4 100644 --- a/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua @@ -1415,7 +1415,8 @@ function AIDriveStrategyCombineCourse:startTurn(ix) elseif self.course:isOnConnectingPath(ix) then self:debug('Headland turn but this a connecting track, use normal turn maneuvers.') AIDriveStrategyFieldWorkCourse.startTurn(self, ix) - elseif self.course:isOnOutermostHeadland(ix) and self:isTurnOnFieldActive() then + elseif self.course:isOnOutermostHeadland(ix) and self:isTurnOnFieldActive() and + not self.combineController:isRootVegetableHarvester()then self:debug('Creating a pocket in the corner so the combine stays on the field during the turn') self.aiTurn = CombinePocketHeadlandTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext, self.course, self:getWorkWidth()) From 98c630a6e0f87b431f51a2708d1d201a3760bc32 Mon Sep 17 00:00:00 2001 From: Peter Vaiko Date: Sat, 10 May 2025 07:45:59 -0400 Subject: [PATCH 05/10] fix: include the reverser node offset #786 --- scripts/ai/turns/AITurn.lua | 2 +- scripts/ai/turns/TurnManeuver.lua | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/ai/turns/AITurn.lua b/scripts/ai/turns/AITurn.lua index 4fe59a8f4..6889cc9ee 100644 --- a/scripts/ai/turns/AITurn.lua +++ b/scripts/ai/turns/AITurn.lua @@ -762,7 +762,7 @@ end function CombineHeadlandTurn:startTurn() self:debug('Starting a combine headland turn') - self.turnCourse = ReedsSheppHeadlandTurn(self.vehicle, self.turnContext, + self.turnCourse = ReedsSheppHeadlandTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(), self.turningRadius):getCourse() self.state = self.states.TURNING self.ppc:setCourse(self.turnCourse) diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index d3931912d..8967c245f 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -552,13 +552,13 @@ function ReedsSheppTurnManeuver:findAnalyticPath(vehicleDirectionNode, startXOff return course end ----@class ReedsSheppHeadlandTurn : TurnManeuver -ReedsSheppHeadlandTurn = CpObject(TurnManeuver) +---@class ReedsSheppHeadlandTurnManeuver : TurnManeuver +ReedsSheppHeadlandTurnManeuver = CpObject(TurnManeuver) --- This is a headland turn (~90 degrees) for non-towed harvesters with cutter on the front. Expected to be called --- just after the cutter finished the corner, that is, the harvester should drive forward in the original direction --- until there is no fruit left. It'll then do a quick 90 degree 3 point turn to align with the new direction. -function ReedsSheppHeadlandTurn:init(vehicle, turnContext, vehicleDirectionNode, turningRadius) +function ReedsSheppHeadlandTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turningRadius) local solver = ReedsSheppSolver() -- use lateWorkStartNode since we covered the corner in the inbound direction already local path = PathfinderUtil.findAnalyticPath(solver, vehicleDirectionNode, 0, 0, @@ -567,7 +567,7 @@ function ReedsSheppHeadlandTurn:init(vehicle, turnContext, vehicleDirectionNode, self.course:adjustForReversing(2) -- add a little straight section to the end so we have a little buffer and don't end the turn right at -- the work start - self.course:extend(5) + self.course:extend(self:getReversingOffset() + 1) TurnManeuver.setLowerImplements(self.course, 5, true) end From becb603646fd496b3f85ac214b0fd210e48240f9 Mon Sep 17 00:00:00 2001 From: Peter Vaiko Date: Sat, 10 May 2025 07:55:36 -0400 Subject: [PATCH 06/10] fix: callstack #786 --- scripts/ai/turns/TurnManeuver.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index 8967c245f..8d9106956 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -247,14 +247,14 @@ end --- Get the distance between the direction node of the vehicle and the reverser node (if there is one). This --- is to make sure that when the course changes to reverse and there is a reverse node, the first reverse --- waypoint is behind the reverser node. Otherwise we'll just keep backing up until the emergency brake is triggered. -function TurnManeuver:getReversingOffset() - local reverserNode, debugText = AIUtil.getReverserNode(self.vehicle) +---@return number|nil distance in meters to the reverser node, or nil if there is no reverser node +function TurnManeuver:getReversingOffset(vehicle, vehicleDirectionNode) + local reverserNode, debugText = AIUtil.getReverserNode(vehicle) if reverserNode then - local _, _, dz = localToLocal(reverserNode, self.vehicleDirectionNode, 0, 0, 0) + local _, _, dz = localToLocal(reverserNode, vehicleDirectionNode, 0, 0, 0) self:debug('Using reverser node (%s) distance %.1f', debugText, dz) return math.abs(dz) end - return self.steeringLength end --- Set implement lowering control for the end of the turn @@ -274,7 +274,7 @@ end function TurnManeuver:adjustCourseToFitField(course, dBack, ixBeforeEndingTurnSection) self:debug('moving course back: d=%.1f', dBack) local endingTurnLength - local reversingOffset = self:getReversingOffset() + local reversingOffset = self:getReversingOffset(self.vehicle, self.vehicleDirectionNode) or self.steeringLength -- generate a straight reverse section first (less than 1 m step should make sure we always end up with -- at least two waypoints local courseWithReversing = Course.createFromNode(self.vehicle, self.vehicle:getAIDirectionNode(), @@ -567,7 +567,7 @@ function ReedsSheppHeadlandTurnManeuver:init(vehicle, turnContext, vehicleDirect self.course:adjustForReversing(2) -- add a little straight section to the end so we have a little buffer and don't end the turn right at -- the work start - self.course:extend(self:getReversingOffset() + 1) + self.course:extend((self:getReversingOffset(vehicle, vehicleDirectionNode) or 4) + 1) TurnManeuver.setLowerImplements(self.course, 5, true) end From 307c51059cc469e08361a31bb5e54ad2fd63caef Mon Sep 17 00:00:00 2001 From: Peter Vaiko Date: Sun, 11 May 2025 07:13:36 -0400 Subject: [PATCH 07/10] fix: fine tuning #786 --- scripts/Course.lua | 4 ++++ scripts/ai/turns/TurnContext.lua | 13 ------------- scripts/ai/turns/TurnManeuver.lua | 16 +++++++++++----- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/scripts/Course.lua b/scripts/Course.lua index 5a28481d6..5acc50447 100644 --- a/scripts/Course.lua +++ b/scripts/Course.lua @@ -648,6 +648,10 @@ function Course:isLastWaypointIx(ix) return #self.waypoints == ix end +function Course:endsInReverse() + return self:isReverseAt(self:getNumberOfWaypoints()) +end + function Course:print() for i = 1, #self.waypoints do local p = self.waypoints[i] diff --git a/scripts/ai/turns/TurnContext.lua b/scripts/ai/turns/TurnContext.lua index ffc1628d2..a233a097e 100644 --- a/scripts/ai/turns/TurnContext.lua +++ b/scripts/ai/turns/TurnContext.lua @@ -367,19 +367,6 @@ function TurnContext:createCorner(vehicle, r) vehicle:getCpSettings().toolOffsetX:getValue()) end ---- Course to reverse before starting a turn to make sure the turn is completely on the field ---- @param vehicle table ---- @param reverseDistance number distance to reverse in meters -function TurnContext:createReverseWaypointsBeforeStartingTurn(vehicle, reverseDistance) - local reverserNode = AIUtil.getReverserNode(vehicle) - local _, _, dStart = localToLocal(reverserNode or vehicle:getAIDirectionNode(), self.workEndNode, 0, 0, 0) - local waypoints = {} - for d = dStart, dStart - reverseDistance - 1, -1 do - local x, y, z = localToWorld(self.workEndNode, 0, 0, d) - table.insert(waypoints, {x = x, y = y, z = z, rev = true}) - end - return waypoints -end --- Course to end a pathfinder turn, a straight line from where pathfinder ended, into to next row, --- making sure it is long enough so the vehicle reaches the point to lower the implements on this course diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index 8d9106956..f10d1344a 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -559,16 +559,22 @@ ReedsSheppHeadlandTurnManeuver = CpObject(TurnManeuver) --- just after the cutter finished the corner, that is, the harvester should drive forward in the original direction --- until there is no fruit left. It'll then do a quick 90 degree 3 point turn to align with the new direction. function ReedsSheppHeadlandTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turningRadius) + self.vehicle = vehicle local solver = ReedsSheppSolver() -- use lateWorkStartNode since we covered the corner in the inbound direction already local path = PathfinderUtil.findAnalyticPath(solver, vehicleDirectionNode, 0, 0, turnContext.lateWorkStartNode, 0, -turnContext.backMarkerDistance, turningRadius) self.course = Course.createFromAnalyticPath(vehicle, path, true) - self.course:adjustForReversing(2) - -- add a little straight section to the end so we have a little buffer and don't end the turn right at - -- the work start - self.course:extend((self:getReversingOffset(vehicle, vehicleDirectionNode) or 4) + 1) - TurnManeuver.setLowerImplements(self.course, 5, true) + self.course:adjustForTowedImplements(2) + if self.course:endsInReverse() then + -- add a little straight section to the end so we have a little buffer and don't end the turn right at + -- the work start + local reversingOffset = (self:getReversingOffset(vehicle, vehicleDirectionNode) or 4) + self:debug('Extending course by %.1f m', reversingOffset) + self.course:extend( reversingOffset + 2, -turnContext.turnEndWp.dx, -turnContext.turnEndWp.dz) + end + local endingTurnLength = turnContext:appendEndingTurnCourse(self.course, 0) + TurnManeuver.setLowerImplements(self.course, endingTurnLength, true) end ---@class TurnEndingManeuver : TurnManeuver From 61b0031053f981f0680ddabe92f72e18a798340d Mon Sep 17 00:00:00 2001 From: Peter Vaiko Date: Sun, 11 May 2025 12:24:42 -0400 Subject: [PATCH 08/10] fix: disable pocket config #786 --- config/VehicleConfigurations.xml | 7 +++++++ scripts/ai/strategies/AIDriveStrategyCombineCourse.lua | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/config/VehicleConfigurations.xml b/config/VehicleConfigurations.xml index 4d8c741c1..b48bb6782 100644 --- a/config/VehicleConfigurations.xml +++ b/config/VehicleConfigurations.xml @@ -163,6 +163,13 @@ You can define the following custom settings: Forced tip side index for unloading. For now only for an auger wagon. As an example the Hawe SUW 5000 has two tipside, but only the one with the pipe is needed. +- disablePocket: boolean + Disables creating a pocket for headland turns. Some harvesters, like mostly potato and other root vegetable + harvesters, are not good at making a pocket at the headland corner as they are very long but have a small + working width. We automatically try to disable pocket for most of the root vegetable harvesters, but some, + like the Oxbo, are not detected correctly. This setting can be used to disable the pocket creation for these + harvesters. + --> diff --git a/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua index 113a33dc4..841d1a566 100644 --- a/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua @@ -1416,7 +1416,8 @@ function AIDriveStrategyCombineCourse:startTurn(ix) self:debug('Headland turn but this a connecting track, use normal turn maneuvers.') AIDriveStrategyFieldWorkCourse.startTurn(self, ix) elseif self.course:isOnOutermostHeadland(ix) and self:isTurnOnFieldActive() and - not self.combineController:isRootVegetableHarvester()then + not self.combineController:isRootVegetableHarvester() and + not g_vehicleConfigurations:getRecursively(self.vehicle, 'disablePocket') then self:debug('Creating a pocket in the corner so the combine stays on the field during the turn') self.aiTurn = CombinePocketHeadlandTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext, self.course, self:getWorkWidth()) From 8c9db8e00d9155d6879b251ee92fb742472d32eb Mon Sep 17 00:00:00 2001 From: Tensuko Date: Sun, 11 May 2025 18:34:25 +0200 Subject: [PATCH 09/10] no pocket for oxbo --- config/VehicleConfigurations.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/VehicleConfigurations.xml b/config/VehicleConfigurations.xml index b48bb6782..78e1c7150 100644 --- a/config/VehicleConfigurations.xml +++ b/config/VehicleConfigurations.xml @@ -237,12 +237,15 @@ You can define the following custom settings: From 6eb1afe2c0ad09563673bb55e2becdb481da3b49 Mon Sep 17 00:00:00 2001 From: Peter Vaiko Date: Sun, 11 May 2025 12:38:02 -0400 Subject: [PATCH 10/10] fix: disable pocket config #786 --- config/VehicleConfigurations.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/VehicleConfigurations.xml b/config/VehicleConfigurations.xml index 78e1c7150..175c69ff4 100644 --- a/config/VehicleConfigurations.xml +++ b/config/VehicleConfigurations.xml @@ -202,6 +202,7 @@ You can define the following custom settings: openPipeEarly closePipeAfterUnload tipSideIndex + disablePocket