Merge branch 'main' of ssh://892887cvmk08.aicp.io:24928/orisys-tech/orisys-app
This commit is contained in:
@@ -48,14 +48,14 @@
|
|||||||
},
|
},
|
||||||
"move": {
|
"move": {
|
||||||
"execute": "Execute",
|
"execute": "Execute",
|
||||||
"execute_hint": "Send the selected point's solved pose to the platform: yaw, pitch and table X/Y move together. The press axis Z is not moved.",
|
"execute_hint": "Send the selected point's solved pose to the platform: yaw, pitch, table X/Y and press axis Z move together.",
|
||||||
"stop": "Stop",
|
"stop": "Stop",
|
||||||
"stop_hint": "Stop every axis now (the same command as 全部停止 in the strip).",
|
"stop_hint": "Stop every axis now (the same command as 全部停止 in the strip).",
|
||||||
"return_initial": "Return to zero",
|
"return_initial": "Return to zero",
|
||||||
"return_hint": "Every axis back to the initial state, press axis Z first.",
|
"return_hint": "Yaw, pitch, table X/Y and press axis Z return to zero together.",
|
||||||
"confirm_title": "Confirm motion",
|
"confirm_title": "Confirm motion",
|
||||||
"confirm_execute": "Will move these axes together:\n\n{steps}\n\nCheck this against the machine before continuing.",
|
"confirm_execute": "Will move these axes together:\n\n{steps}\n\nCheck this against the machine before continuing.",
|
||||||
"confirm_return": "Will move every axis back to zero (press axis Z first):\n\n{steps}",
|
"confirm_return": "Will move these axes back to zero together:\n\n{steps}",
|
||||||
"hold_note": "Not moved: {roles} (the press axis is P7's business — contact detection and press depth)",
|
"hold_note": "Not moved: {roles} (the press axis is P7's business — contact detection and press depth)",
|
||||||
"cancelled": "Cancelled; nothing was commanded",
|
"cancelled": "Cancelled; nothing was commanded",
|
||||||
"confirm_check": "Confirm before moving",
|
"confirm_check": "Confirm before moving",
|
||||||
|
|||||||
@@ -48,14 +48,14 @@
|
|||||||
},
|
},
|
||||||
"move": {
|
"move": {
|
||||||
"execute": "执行动作",
|
"execute": "执行动作",
|
||||||
"execute_hint": "把选中点解算出的位姿送到平台:回转、俯仰、工作台 X/Y 同时移动。压轴 Z 不动作。",
|
"execute_hint": "把选中点解算出的位姿送到平台:回转、俯仰、工作台 X/Y、压轴 Z 同时移动。",
|
||||||
"stop": "停止",
|
"stop": "停止",
|
||||||
"stop_hint": "立即停止所有轴(与顶栏「全部停止」相同)。",
|
"stop_hint": "立即停止所有轴(与顶栏「全部停止」相同)。",
|
||||||
"return_initial": "回初始位",
|
"return_initial": "回初始位",
|
||||||
"return_hint": "五个轴全部回到零点,先退压轴 Z。",
|
"return_hint": "回转、俯仰、工作台 X/Y、压轴 Z 一起回到零点。",
|
||||||
"confirm_title": "确认运动",
|
"confirm_title": "确认运动",
|
||||||
"confirm_execute": "将同时移动:\n\n{steps}\n\n请确认仿真与机床一致。",
|
"confirm_execute": "将同时移动:\n\n{steps}\n\n请确认仿真与机床一致。",
|
||||||
"confirm_return": "将把五个轴全部移回零点(先退压轴 Z):\n\n{steps}",
|
"confirm_return": "将同时移回零点:\n\n{steps}\n\n请确认机床上没有干涉。",
|
||||||
"hold_note": "不动作:{roles}(压轴由 P7 的接触检测与压深另外管)",
|
"hold_note": "不动作:{roles}(压轴由 P7 的接触检测与压深另外管)",
|
||||||
"cancelled": "已取消,未发出任何运动",
|
"cancelled": "已取消,未发出任何运动",
|
||||||
"confirm_check": "移动前确认",
|
"confirm_check": "移动前确认",
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ matters:
|
|||||||
:class:`~orisys_autocal.motion.poller.MotionPoller` is built, and it is why a stop
|
:class:`~orisys_autocal.motion.poller.MotionPoller` is built, and it is why a stop
|
||||||
is possible at all: a mover that sat in a loop inside one command could not be
|
is possible at all: a mover that sat in a loop inside one command could not be
|
||||||
interrupted, because the stop would have to wait for the loop it is meant to abort.
|
interrupted, because the stop would have to wait for the loop it is meant to abort.
|
||||||
* **This module** owns the waiting. An execute plan starts yaw, pitch and table X/Y
|
* **This module** owns the waiting. Execute and return-to-zero both start yaw, pitch,
|
||||||
in one command so they move together; a return plan still retracts one axis at a
|
table X/Y and the press axis in one command so the five axes move together. The
|
||||||
time. It watches the snapshots the poller already produces and gives up on its
|
mover watches the snapshots the poller already produces and gives up on its own
|
||||||
own if an axis stops short of its target.
|
if an axis stops short of its target.
|
||||||
|
|
||||||
That shape is the whole reason for the class: ``stop`` is submitted as an ordinary
|
That shape is the whole reason for the class: ``stop`` is submitted as an ordinary
|
||||||
command, so it runs at the next tick and stops the axis that is moving *now*.
|
command, so it runs at the next tick and stops the axis that is moving *now*.
|
||||||
@@ -51,11 +51,8 @@ STALL_GRACE_S = 2.0
|
|||||||
#: The order a role move is issued in, for the reason in the module docstring.
|
#: The order a role move is issued in, for the reason in the module docstring.
|
||||||
MOVE_ORDER: tuple[str, ...] = ("yaw", "pitch", "x", "y", "z")
|
MOVE_ORDER: tuple[str, ...] = ("yaw", "pitch", "x", "y", "z")
|
||||||
|
|
||||||
#: The order "return to the initial state" retracts in: the press axis first, so the
|
#: Axes a pose execute and a return-to-zero command together, including the press axis.
|
||||||
#: tool is clear of the sensor before anything rotates, then the stages, then the
|
ALIGN_ORDER: tuple[str, ...] = ("yaw", "pitch", "x", "y", "z")
|
||||||
#: table. Returning in one coordinated swoop would move five axes through poses
|
|
||||||
#: nobody has simulated.
|
|
||||||
RETURN_ORDER: tuple[str, ...] = ("z", "pitch", "yaw", "x", "y")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -82,7 +79,6 @@ class MovePlan:
|
|||||||
#: information the operator needs, not an omission.
|
#: information the operator needs, not an omission.
|
||||||
held: tuple[str, ...] = ()
|
held: tuple[str, ...] = ()
|
||||||
#: When set, every step is commanded in one call and they travel together.
|
#: When set, every step is commanded in one call and they travel together.
|
||||||
#: Return-to-zero leaves this off so the press axis clears before anything turns.
|
|
||||||
together: bool = False
|
together: bool = False
|
||||||
|
|
||||||
def describe(self) -> str:
|
def describe(self) -> str:
|
||||||
@@ -153,11 +149,12 @@ def plan_pose_move(
|
|||||||
|
|
||||||
|
|
||||||
def plan_return_to_initial(service: MotionService) -> MovePlan:
|
def plan_return_to_initial(service: MotionService) -> MovePlan:
|
||||||
"""Every axis to zero, press axis first, in :data:`RETURN_ORDER`."""
|
"""Yaw, pitch, table X/Y and the press axis back to zero together."""
|
||||||
return plan_pose_move(
|
return plan_pose_move(
|
||||||
service,
|
service,
|
||||||
{role: 0.0 for role in RETURN_ORDER},
|
{role: 0.0 for role in ALIGN_ORDER},
|
||||||
order=RETURN_ORDER,
|
order=ALIGN_ORDER,
|
||||||
|
together=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -242,7 +239,7 @@ class PoseMover(QObject):
|
|||||||
self.stepStarted.emit(step)
|
self.stepStarted.emit(step)
|
||||||
|
|
||||||
def _issue_together(self, plan: MovePlan) -> None:
|
def _issue_together(self, plan: MovePlan) -> None:
|
||||||
"""Command every step in one worker call. Z is absent from the plan."""
|
"""Command every step in one worker call."""
|
||||||
moves: list[tuple[int, float]] = []
|
moves: list[tuple[int, float]] = []
|
||||||
for step in plan.steps:
|
for step in plan.steps:
|
||||||
if not step.units == step.units:
|
if not step.units == step.units:
|
||||||
@@ -317,7 +314,7 @@ class PoseMover(QObject):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _on_snapshot_together(self, snapshot: Snapshot) -> None:
|
def _on_snapshot_together(self, snapshot: Snapshot) -> None:
|
||||||
"""Finish when every commanded axis has arrived. Z is not in the plan."""
|
"""Finish when every commanded axis has arrived."""
|
||||||
plan = self.state.plan
|
plan = self.state.plan
|
||||||
if plan is None:
|
if plan is None:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -750,13 +750,8 @@ class FingerPanel(QGroupBox):
|
|||||||
|
|
||||||
The two numbers that matter are the *table's* move and the stage angles: the
|
The two numbers that matter are the *table's* move and the stage angles: the
|
||||||
table translation is what the stages are resolved against, and it is what the
|
table translation is what the stages are resolved against, and it is what the
|
||||||
preview drew as the cyan path's end.
|
preview drew as the cyan path's end. Z is the contact height, commanded with
|
||||||
|
the other four axes in the same move.
|
||||||
z is returned as *held*: executing a simulated pose moves the stages and the
|
|
||||||
table, it does not press. Pressing is P7 and has its own guards — contact
|
|
||||||
detection, press depth, the sensor's second opinion — none of which exist
|
|
||||||
here, so a move that reached the contact height would be a press with none of
|
|
||||||
them.
|
|
||||||
"""
|
"""
|
||||||
pose = self._last_pose
|
pose = self._last_pose
|
||||||
if pose is None:
|
if pose is None:
|
||||||
@@ -767,8 +762,9 @@ class FingerPanel(QGroupBox):
|
|||||||
"y": float(table_y),
|
"y": float(table_y),
|
||||||
"yaw": float(pose.yaw_deg),
|
"yaw": float(pose.yaw_deg),
|
||||||
"pitch": float(pose.pitch_deg),
|
"pitch": float(pose.pitch_deg),
|
||||||
|
"z": float(pose.contact_z_mm),
|
||||||
}
|
}
|
||||||
return targets, ("z",)
|
return targets, ()
|
||||||
|
|
||||||
def _sync_sim_button(self) -> None:
|
def _sync_sim_button(self) -> None:
|
||||||
self.sim_button.setText(
|
self.sim_button.setText(
|
||||||
|
|||||||
@@ -1045,10 +1045,9 @@ class AutoCalMainWindow(QMainWindow):
|
|||||||
def _on_execute_pose(self) -> None:
|
def _on_execute_pose(self) -> None:
|
||||||
"""Move the platform to the solved pose of the finger screen's selection.
|
"""Move the platform to the solved pose of the finger screen's selection.
|
||||||
|
|
||||||
Yaw, pitch and table X/Y start together. The press axis is deliberately
|
Yaw, pitch, table X/Y and the press axis start together. The Z target is
|
||||||
not commanded, so this is workflow P6's dry run for one point — it cannot
|
the solved contact height. The numbers in the confirmation are the ones
|
||||||
reach the sensor. The numbers shown in the confirmation are the ones the
|
the preview drew.
|
||||||
preview drew, so the operator checks the simulation against the machine.
|
|
||||||
"""
|
"""
|
||||||
solve = self.finger_panel.solved_pose()
|
solve = self.finger_panel.solved_pose()
|
||||||
if solve is None:
|
if solve is None:
|
||||||
@@ -1065,7 +1064,7 @@ class AutoCalMainWindow(QMainWindow):
|
|||||||
self._start_move(plan, t("move.execute"))
|
self._start_move(plan, t("move.execute"))
|
||||||
|
|
||||||
def _on_return_to_initial(self) -> None:
|
def _on_return_to_initial(self) -> None:
|
||||||
"""Every drive back to zero, press axis first."""
|
"""Yaw, pitch, table X/Y and the press axis back to zero together."""
|
||||||
plan = plan_return_to_initial(self.poller.service)
|
plan = plan_return_to_initial(self.poller.service)
|
||||||
self._start_move(plan, t("move.return_initial"))
|
self._start_move(plan, t("move.return_initial"))
|
||||||
|
|
||||||
|
|||||||
@@ -202,8 +202,14 @@ def test_accepting_the_prompt_starts_the_move(make_window, monkeypatch):
|
|||||||
)
|
)
|
||||||
window._on_return_to_initial() # noqa: SLF001
|
window._on_return_to_initial() # noqa: SLF001
|
||||||
assert window.mover.running, "accepting the prompt did not start the move"
|
assert window.mover.running, "accepting the prompt did not start the move"
|
||||||
assert [step.role for step in window.mover.state.plan.steps][0] == "z"
|
assert [step.role for step in window.mover.state.plan.steps] == [
|
||||||
# The status line carries the plan, so the operator sees what is moving.
|
"yaw",
|
||||||
|
"pitch",
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
]
|
||||||
|
assert window.mover.state.plan.together
|
||||||
assert "z" in window.finger_panel.move_label.text()
|
assert "z" in window.finger_panel.move_label.text()
|
||||||
finally:
|
finally:
|
||||||
window.mover.cancel("test")
|
window.mover.cancel("test")
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ from orisys_autocal.motion import (
|
|||||||
from orisys_autocal.motion.models import AxisState, AxisStatusBits, BusStatus
|
from orisys_autocal.motion.models import AxisState, AxisStatusBits, BusStatus
|
||||||
from orisys_autocal.motion.pose_move import (
|
from orisys_autocal.motion.pose_move import (
|
||||||
MOVE_ORDER,
|
MOVE_ORDER,
|
||||||
RETURN_ORDER,
|
|
||||||
PoseMover,
|
PoseMover,
|
||||||
SETTLE_DEG,
|
SETTLE_DEG,
|
||||||
SETTLE_MM,
|
SETTLE_MM,
|
||||||
@@ -128,11 +127,12 @@ def test_the_plan_holds_back_what_it_is_not_given():
|
|||||||
assert "z" in plan.describe()
|
assert "z" in plan.describe()
|
||||||
|
|
||||||
|
|
||||||
def test_returning_to_zero_retracts_the_press_axis_first():
|
def test_returning_to_zero_moves_the_five_axes_together():
|
||||||
service = make_service()
|
service = make_service()
|
||||||
plan = plan_return_to_initial(service)
|
plan = plan_return_to_initial(service)
|
||||||
assert [step.role for step in plan.steps] == list(RETURN_ORDER)
|
assert plan.together
|
||||||
assert plan.steps[0].role == "z"
|
assert [step.role for step in plan.steps] == ["yaw", "pitch", "x", "y", "z"]
|
||||||
|
assert plan.held == ()
|
||||||
assert all(step.target == 0.0 for step in plan.steps)
|
assert all(step.target == 0.0 for step in plan.steps)
|
||||||
|
|
||||||
|
|
||||||
@@ -262,15 +262,23 @@ def test_an_execute_plan_moves_the_four_axes_together_and_leaves_z():
|
|||||||
assert mover.state.finished
|
assert mover.state.finished
|
||||||
|
|
||||||
|
|
||||||
def test_return_to_zero_stays_one_axis_at_a_time():
|
def test_return_to_zero_is_one_command_for_the_five_axes():
|
||||||
service = make_service()
|
service = make_service()
|
||||||
|
called: list[list[tuple[int, float]]] = []
|
||||||
|
|
||||||
|
def record(moves):
|
||||||
|
called.append(list(moves))
|
||||||
|
return MotionResult.success()
|
||||||
|
|
||||||
|
service.move_abs_many = record # type: ignore[method-assign]
|
||||||
poller = FakePoller(service)
|
poller = FakePoller(service)
|
||||||
mover = PoseMover(service, poller.submit)
|
mover = PoseMover(service, poller.submit)
|
||||||
plan = plan_return_to_initial(service)
|
plan = plan_return_to_initial(service)
|
||||||
assert not plan.together
|
|
||||||
assert mover.start(plan).ok
|
assert mover.start(plan).ok
|
||||||
assert len(poller.commands) == 1
|
assert len(poller.commands) == 1
|
||||||
assert mover.state.step.role == "z"
|
poller.commands[0](service)
|
||||||
|
assert [axis for axis, _value in called[0]] == [3, 2, 0, 1, 4]
|
||||||
|
assert mover.progress_text().startswith("同时")
|
||||||
|
|
||||||
|
|
||||||
def test_the_mover_commands_one_axis_and_waits_for_it_to_arrive():
|
def test_the_mover_commands_one_axis_and_waits_for_it_to_arrive():
|
||||||
|
|||||||
@@ -539,13 +539,16 @@ def main() -> int:
|
|||||||
if solve is not None:
|
if solve is not None:
|
||||||
targets, held = solve
|
targets, held = solve
|
||||||
checker.that(
|
checker.that(
|
||||||
"z" in held and "z" not in targets,
|
"z" in targets and not held,
|
||||||
f"execute does not hold the press axis back: "
|
f"execute does not command the press axis: "
|
||||||
f"{sorted(targets)} held={held}",
|
f"{sorted(targets)} held={held}",
|
||||||
)
|
)
|
||||||
plan = plan_pose_move(window.poller.service, targets, hold=held)
|
plan = plan_pose_move(
|
||||||
|
window.poller.service, targets, hold=held, together=True
|
||||||
|
)
|
||||||
checker.that(
|
checker.that(
|
||||||
[step.role for step in plan.steps] == ["yaw", "pitch", "x", "y"],
|
[step.role for step in plan.steps]
|
||||||
|
== ["yaw", "pitch", "x", "y", "z"],
|
||||||
f"the move order is {[step.role for step in plan.steps]}",
|
f"the move order is {[step.role for step in plan.steps]}",
|
||||||
)
|
)
|
||||||
checker.that(
|
checker.that(
|
||||||
@@ -581,11 +584,11 @@ def main() -> int:
|
|||||||
+ " reached"
|
+ " reached"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Returning to the initial state, press axis first.
|
# All five axes back to zero together, including the press axis.
|
||||||
back = plan_return_to_initial(window.poller.service)
|
back = plan_return_to_initial(window.poller.service)
|
||||||
checker.that(
|
checker.that(
|
||||||
[step.role for step in back.steps]
|
[step.role for step in back.steps]
|
||||||
== ["z", "pitch", "yaw", "x", "y"],
|
== ["yaw", "pitch", "x", "y", "z"],
|
||||||
f"the return order is {[step.role for step in back.steps]}",
|
f"the return order is {[step.role for step in back.steps]}",
|
||||||
)
|
)
|
||||||
result = window.mover.start(back)
|
result = window.mover.start(back)
|
||||||
|
|||||||
Reference in New Issue
Block a user