调整传感器位置
This commit is contained in:
@@ -52,7 +52,7 @@
|
|||||||
"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": "Yaw, pitch, table X/Y and press axis Z return to zero together.",
|
"return_hint": "Yaw, pitch and press axis Z return to 0. The table parks at its initial position: X at -1.0 mm, Y 15.0 mm forward.",
|
||||||
"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 these axes back to zero together:\n\n{steps}",
|
"confirm_return": "Will move these axes back to zero together:\n\n{steps}",
|
||||||
@@ -405,10 +405,10 @@
|
|||||||
"row_residual": "Alignment residual",
|
"row_residual": "Alignment residual",
|
||||||
"row_frames": "Frame mapping",
|
"row_frames": "Frame mapping",
|
||||||
"tool_xy": "Tool X / Y",
|
"tool_xy": "Tool X / Y",
|
||||||
"tool_hint": "Where the tool is in the table frame. With Y at 3 mm, every point's table Y is the solved move plus 3 mm, so the sensor shell tail stays on the platform centre axis.",
|
"tool_hint": "Where the tool is in the table frame. Each point's table move is measured from this position.",
|
||||||
"sensor_xy": "Sensor X / Y",
|
"sensor_xy": "Sensor X / Y",
|
||||||
"sensor_zy": "Sensor Z / yaw",
|
"sensor_zy": "Sensor Z / yaw",
|
||||||
"setup_hint": "How the sensor actually sits on the stack with the machine at zero: X/Y/Z translation and a yaw about Z. Z is the mounting plate, shims included — there is no separate plate field. Measure it on the real setup.",
|
"setup_hint": "How the sensor actually sits on the stack with the machine at zero: X/Y/Z translation and a yaw about Z. Z is the mounting-plate thickness. The plate on this bench is 8 mm.",
|
||||||
"clear_points": "Clear points",
|
"clear_points": "Clear points",
|
||||||
"remove_selected": "Remove selected",
|
"remove_selected": "Remove selected",
|
||||||
"move_up": "Move up",
|
"move_up": "Move up",
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
"stop": "停止",
|
"stop": "停止",
|
||||||
"stop_hint": "立即停止所有轴(与顶栏「全部停止」相同)。",
|
"stop_hint": "立即停止所有轴(与顶栏「全部停止」相同)。",
|
||||||
"return_initial": "回初始位",
|
"return_initial": "回初始位",
|
||||||
"return_hint": "回转、俯仰、工作台 X/Y、压轴 Z 一起回到零点。",
|
"return_hint": "回转、俯仰、压轴 Z 回到 0。工作台停在初始位置:X 为 -1.0 mm,Y 为向前 15.0 mm。",
|
||||||
"confirm_title": "确认运动",
|
"confirm_title": "确认运动",
|
||||||
"confirm_execute": "将同时移动:\n\n{steps}\n\n请确认仿真与机床一致。",
|
"confirm_execute": "将同时移动:\n\n{steps}\n\n请确认仿真与机床一致。",
|
||||||
"confirm_return": "将同时移回零点:\n\n{steps}\n\n请确认机床上没有干涉。",
|
"confirm_return": "将同时移回零点:\n\n{steps}\n\n请确认机床上没有干涉。",
|
||||||
@@ -405,10 +405,10 @@
|
|||||||
"row_residual": "对齐残差",
|
"row_residual": "对齐残差",
|
||||||
"row_frames": "坐标映射",
|
"row_frames": "坐标映射",
|
||||||
"tool_xy": "刀具 X / Y",
|
"tool_xy": "刀具 X / Y",
|
||||||
"tool_hint": "刀具在工作台坐标系中的位置。Y 为 3 mm 时,每个测点的工作台 Y 都会在解算结果上再加 3 mm,使传感器壳体尾部与平台原中心同轴。",
|
"tool_hint": "刀具在工作台坐标系中的位置。每个测点的工作台移动都相对这个位置计算。",
|
||||||
"sensor_xy": "传感器 X / Y",
|
"sensor_xy": "传感器 X / Y",
|
||||||
"sensor_zy": "传感器 Z / yaw",
|
"sensor_zy": "传感器 Z / yaw",
|
||||||
"setup_hint": "传感器在转台零位时相对工作台原点的实际安装位姿:X/Y/Z 平移与绕 Z 的偏摆。Z 即安装板厚度(含垫片),无需另填。请按实际安装测量填写。",
|
"setup_hint": "传感器在转台零位时相对工作台原点的实际安装位姿:X/Y/Z 平移与绕 Z 的偏摆。Z 为安装板厚度,当前安装台是 8 mm。",
|
||||||
"clear_points": "清空标定点",
|
"clear_points": "清空标定点",
|
||||||
"remove_selected": "删除所选",
|
"remove_selected": "删除所选",
|
||||||
"move_up": "上移",
|
"move_up": "上移",
|
||||||
|
|||||||
@@ -148,11 +148,16 @@ def plan_pose_move(
|
|||||||
return MovePlan(steps=tuple(steps), title=title, held=tuple(hold), together=together)
|
return MovePlan(steps=tuple(steps), title=title, held=tuple(hold), together=together)
|
||||||
|
|
||||||
|
|
||||||
def plan_return_to_initial(service: MotionService) -> MovePlan:
|
def plan_return_to_initial(
|
||||||
"""Yaw, pitch, table X/Y and the press axis back to zero together."""
|
service: MotionService, *, x_mm: float = 0.0, y_mm: float = 0.0
|
||||||
|
) -> MovePlan:
|
||||||
|
"""Yaw, pitch and the press axis back to zero. Table X/Y park at ``x_mm`` and ``y_mm``."""
|
||||||
|
targets = {role: 0.0 for role in ALIGN_ORDER}
|
||||||
|
targets["x"] = float(x_mm)
|
||||||
|
targets["y"] = float(y_mm)
|
||||||
return plan_pose_move(
|
return plan_pose_move(
|
||||||
service,
|
service,
|
||||||
{role: 0.0 for role in ALIGN_ORDER},
|
targets,
|
||||||
order=ALIGN_ORDER,
|
order=ALIGN_ORDER,
|
||||||
together=True,
|
together=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -64,16 +64,16 @@ AXIS_ROLES: tuple[str, ...] = ("x", "y", "z", "yaw", "pitch")
|
|||||||
LINEAR_ROLES: tuple[str, ...] = ("x", "y", "z")
|
LINEAR_ROLES: tuple[str, ...] = ("x", "y", "z")
|
||||||
ROTARY_ROLES: tuple[str, ...] = ("yaw", "pitch")
|
ROTARY_ROLES: tuple[str, ...] = ("yaw", "pitch")
|
||||||
|
|
||||||
#: Default mapping, from the bench: drives 0-4 are x, y, pitch, yaw, z.
|
#: Default mapping, from the bench: drives 0-4 are x, y, yaw, pitch, z.
|
||||||
#:
|
#:
|
||||||
#: Deliberately *not* the same as the model's own order. It is written the way the
|
#: Pitch and yaw were first wired the other way around. Jogging the machine
|
||||||
#: machine is wired, so reading it against the terminal strip is a check rather than
|
#: showed drive 2 is yaw (回转) and drive 3 is pitch (俯仰). The degree scales
|
||||||
#: a translation.
|
#: stay on the drive numbers in ``units.py``.
|
||||||
DEFAULT_AXIS_ROLES: dict[str, int] = {
|
DEFAULT_AXIS_ROLES: dict[str, int] = {
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 1,
|
"y": 1,
|
||||||
"pitch": 2,
|
"pitch": 3,
|
||||||
"yaw": 3,
|
"yaw": 2,
|
||||||
"z": 4,
|
"z": 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -905,10 +905,11 @@ class MotionService:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
sent = float(value)
|
||||||
if absolute:
|
if absolute:
|
||||||
self._transport.move_abs(axis, float(value))
|
self._transport.move_abs(axis, sent)
|
||||||
else:
|
else:
|
||||||
self._transport.move_rel(axis, float(value))
|
self._transport.move_rel(axis, sent)
|
||||||
except (ZAuxError, OSError) as exc:
|
except (ZAuxError, OSError) as exc:
|
||||||
code = getattr(exc, "code", 0)
|
code = getattr(exc, "code", 0)
|
||||||
return self._logged(
|
return self._logged(
|
||||||
|
|||||||
@@ -99,11 +99,10 @@ def _default_safety_limits() -> dict[str, float]:
|
|||||||
#: How far the axis may descend looking for contact before the run is
|
#: How far the axis may descend looking for contact before the run is
|
||||||
#: aborted (workflow P7, no-contact abort).
|
#: aborted (workflow P7, no-contact abort).
|
||||||
"contact_search_mm": 3.0,
|
"contact_search_mm": 3.0,
|
||||||
#: Stage envelope, from the confirmed bench values: the yaw stage turns a
|
#: Stage envelope. Yaw turns ±180. Pitch is limited to ±90 on this bench:
|
||||||
#: half-turn either way, the pitch stage a full turn. These are the limits a
|
#: past that the fixture interferes, so a pose move is refused.
|
||||||
#: pose move is checked against before any axis is commanded.
|
|
||||||
"max_yaw_deg": 180.0,
|
"max_yaw_deg": 180.0,
|
||||||
"max_pitch_deg": 360.0,
|
"max_pitch_deg": 90.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -220,21 +219,20 @@ def default_preferences() -> dict[str, Any]:
|
|||||||
#: x/y/z translation along the table's axes and a yaw about its z. **The z
|
#: x/y/z translation along the table's axes and a yaw about its z. **The z
|
||||||
#: is the mounting plate**, so the plate is not a setting of its own — it
|
#: is the mounting plate**, so the plate is not a setting of its own — it
|
||||||
#: lifts the sensing face and does nothing else, and two fields for one
|
#: lifts the sensing face and does nothing else, and two fields for one
|
||||||
#: number drift apart.
|
#: number drift apart. The bench plate under the finger is 8 mm thick.
|
||||||
"finger_sensor_offset_mm": [0.0, 0.0, 0.0],
|
"finger_sensor_offset_mm": [0.0, 0.0, 8.0],
|
||||||
"finger_sensor_yaw_deg": 0.0,
|
"finger_sensor_yaw_deg": 0.0,
|
||||||
#: Where the tool is, in the table frame, mm. Straight above the table
|
#: Where the tool is, in the table frame, mm. A tool-tip offset belongs
|
||||||
#: origin unless the real machine says otherwise; every XY move is measured
|
#: here. The bench X shift is the initial position, not this number.
|
||||||
#: against it, so it is what makes the reported move match the bench. A tool
|
|
||||||
#: tip offset belongs here — it used to be a separate field, and having two
|
|
||||||
#: of them is what let the picture and the numbers disagree.
|
|
||||||
"finger_tool_xy_mm": [0.0, 3.0],
|
"finger_tool_xy_mm": [0.0, 3.0],
|
||||||
#: Stage travel, degrees. Confirmed at the bench: the vertical stage turns
|
#: Where 「回初始位」 parks the table, mm. X is 1 mm toward the negative
|
||||||
#: a full half-turn either way (±180), and the horizontal stage turns
|
#: direction. Y is 15.0 mm forward of the old zero.
|
||||||
#: continuously. An earlier value of 90 was wrong and made half the patch
|
"table_initial_x_mm": -1.0,
|
||||||
#: look unreachable when it is not.
|
"table_initial_y_mm": 15.0,
|
||||||
|
#: Stage travel, degrees. Yaw turns ±180. Pitch is held to ±90 because the
|
||||||
|
#: fixture interferes outside that.
|
||||||
"finger_yaw_limit_deg": 180.0,
|
"finger_yaw_limit_deg": 180.0,
|
||||||
"finger_pitch_limit_deg": 360.0,
|
"finger_pitch_limit_deg": 90.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -277,6 +275,10 @@ def load_preferences() -> dict[str, Any]:
|
|||||||
offset.append(0.0)
|
offset.append(0.0)
|
||||||
if plate and not offset[2]:
|
if plate and not offset[2]:
|
||||||
offset[2] = plate
|
offset[2] = plate
|
||||||
|
# A session started before the plate was entered saves Z as 0 and would put
|
||||||
|
# that 0 back over the 8 mm bench plate. Keep a stored non-zero thickness.
|
||||||
|
if not offset[2]:
|
||||||
|
offset[2] = float(defaults["finger_sensor_offset_mm"][2])
|
||||||
prefs["finger_sensor_offset_mm"] = offset
|
prefs["finger_sensor_offset_mm"] = offset
|
||||||
|
|
||||||
# The tool tip offset used to be a vector added to the reported coordinates
|
# The tool tip offset used to be a vector added to the reported coordinates
|
||||||
@@ -299,6 +301,27 @@ def load_preferences() -> dict[str, Any]:
|
|||||||
if value and not tool[index]:
|
if value and not tool[index]:
|
||||||
tool[index] = value
|
tool[index] = value
|
||||||
prefs["finger_tool_xy_mm"] = tool
|
prefs["finger_tool_xy_mm"] = tool
|
||||||
|
# Closing AutoCal writes the whole in-memory file back. A session that started
|
||||||
|
# on the previous initial position would otherwise put that old Y over this one.
|
||||||
|
prefs["table_initial_x_mm"] = defaults["table_initial_x_mm"]
|
||||||
|
prefs["table_initial_y_mm"] = defaults["table_initial_y_mm"]
|
||||||
|
# 360° was the old pitch envelope. This fixture interferes outside ±90, and a
|
||||||
|
# session started on 360 would write that envelope back on close.
|
||||||
|
limits = prefs.get("safety_limits")
|
||||||
|
if isinstance(limits, dict):
|
||||||
|
try:
|
||||||
|
if float(limits.get("max_pitch_deg")) == 360.0:
|
||||||
|
limits["max_pitch_deg"] = 90.0
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
limits["max_pitch_deg"] = 90.0
|
||||||
|
try:
|
||||||
|
if float(prefs.get("finger_pitch_limit_deg")) == 360.0:
|
||||||
|
prefs["finger_pitch_limit_deg"] = 90.0
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
prefs["finger_pitch_limit_deg"] = 90.0
|
||||||
|
# Yaw travel is ±180. The same-sign pitch solution sits near ±150° of yaw,
|
||||||
|
# and a stored ±90 drops it, leaving the opposite pitch sign on one side.
|
||||||
|
prefs["finger_yaw_limit_deg"] = float(defaults["finger_yaw_limit_deg"])
|
||||||
# A hand-edited task number must not reach the controller as typed.
|
# A hand-edited task number must not reach the controller as typed.
|
||||||
prefs["bas_task"] = clamp_bas_task(prefs.get("bas_task"))
|
prefs["bas_task"] = clamp_bas_task(prefs.get("bas_task"))
|
||||||
prefs = _migrate_axis_roles(prefs)
|
prefs = _migrate_axis_roles(prefs)
|
||||||
@@ -333,6 +356,11 @@ def _migrate_axis_roles(prefs: dict[str, Any]) -> dict[str, Any]:
|
|||||||
merged[role] = int(value)
|
merged[role] = int(value)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
continue
|
continue
|
||||||
|
# The first bench note had these two drives swapped. A session that still
|
||||||
|
# has that pair would otherwise put it back on close.
|
||||||
|
if merged.get("pitch") == 2 and merged.get("yaw") == 3:
|
||||||
|
merged["pitch"] = 3
|
||||||
|
merged["yaw"] = 2
|
||||||
prefs["axis_roles"] = merged
|
prefs["axis_roles"] = merged
|
||||||
return prefs
|
return prefs
|
||||||
|
|
||||||
|
|||||||
@@ -278,8 +278,8 @@ class AutoCalMainWindow(QMainWindow):
|
|||||||
# platform connected. Its stack carries the bench's stage travel limits,
|
# platform connected. Its stack carries the bench's stage travel limits,
|
||||||
# because a pose outside them is not a pose.
|
# because a pose outside them is not a pose.
|
||||||
self.finger_stack = PlatformStack(
|
self.finger_stack = PlatformStack(
|
||||||
yaw_limit_deg=float(self.prefs.get("finger_yaw_limit_deg") or 90.0),
|
yaw_limit_deg=float(self.prefs.get("finger_yaw_limit_deg") or 180.0),
|
||||||
pitch_limit_deg=float(self.prefs.get("finger_pitch_limit_deg") or 360.0),
|
pitch_limit_deg=float(self.prefs.get("finger_pitch_limit_deg") or 90.0),
|
||||||
)
|
)
|
||||||
self.finger_panel = FingerPanel(
|
self.finger_panel = FingerPanel(
|
||||||
self.finger_stack,
|
self.finger_stack,
|
||||||
@@ -1104,8 +1104,19 @@ class AutoCalMainWindow(QMainWindow):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _on_return_to_initial(self) -> None:
|
def _on_return_to_initial(self) -> None:
|
||||||
"""Yaw, pitch, table X/Y and the press axis back to zero together."""
|
"""Yaw, pitch and Z back to zero. Table X/Y park at the stored initial."""
|
||||||
plan = plan_return_to_initial(self.poller.service)
|
|
||||||
|
def initial(key: str) -> float:
|
||||||
|
try:
|
||||||
|
return float(self.prefs.get(key) or 0.0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
plan = plan_return_to_initial(
|
||||||
|
self.poller.service,
|
||||||
|
x_mm=initial("table_initial_x_mm"),
|
||||||
|
y_mm=initial("table_initial_y_mm"),
|
||||||
|
)
|
||||||
self._start_move(plan, t("move.return_initial"))
|
self._start_move(plan, t("move.return_initial"))
|
||||||
|
|
||||||
def _start_move(self, plan, title: str, *, report=None) -> None:
|
def _start_move(self, plan, title: str, *, report=None) -> None:
|
||||||
|
|||||||
@@ -136,6 +136,31 @@ def test_returning_to_zero_moves_the_five_axes_together():
|
|||||||
assert all(step.target == 0.0 for step in plan.steps)
|
assert all(step.target == 0.0 for step in plan.steps)
|
||||||
|
|
||||||
|
|
||||||
|
def test_return_to_initial_can_park_y_forward():
|
||||||
|
service = make_service()
|
||||||
|
plan = plan_return_to_initial(service, x_mm=-1.0, y_mm=15.0)
|
||||||
|
targets = {step.role: step.target for step in plan.steps}
|
||||||
|
assert targets == {"yaw": 0.0, "pitch": 0.0, "x": -1.0, "y": 15.0, "z": 0.0}
|
||||||
|
|
||||||
|
|
||||||
|
def test_pitch_positive_direction_matches_the_drive():
|
||||||
|
service = make_service()
|
||||||
|
pitch = ROLES["pitch"]
|
||||||
|
yaw = ROLES["yaw"]
|
||||||
|
seen: list[bool] = []
|
||||||
|
service.transport.jog = lambda axis, forward: seen.append((axis, forward)) # type: ignore[method-assign]
|
||||||
|
|
||||||
|
assert service.jog(pitch, True).ok
|
||||||
|
assert seen == [(pitch, True)]
|
||||||
|
assert service.move_rel(pitch, 90.0).ok
|
||||||
|
assert service.move_rel(yaw, 90.0).ok
|
||||||
|
assert service.transport.get_dpos(pitch) == pytest.approx(90.0)
|
||||||
|
assert service.transport.get_dpos(yaw) == pytest.approx(90.0)
|
||||||
|
snapshot = service.poll()
|
||||||
|
assert snapshot.axis(pitch).dpos == pytest.approx(90.0)
|
||||||
|
assert snapshot.axis(yaw).dpos == pytest.approx(90.0)
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------- guards
|
# ----------------------------------------------------------------------- guards
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user