added motion simulator feature

This commit is contained in:
ykcheungab
2026-09-21 16:11:45 +08:00
parent ccedb9ddd9
commit f1bec62ca4
21 changed files with 5025 additions and 12 deletions
+206 -5
View File
@@ -101,7 +101,7 @@ STRINGS = {
"nodes": "节点数量",
"axes_count": "轴数量",
"busdata_group": "总线数据 (SDO / RTEX) ▶",
"tabs": ["轴调试", "测试序列", "传感器", "表面分布", "标定点"],
"tabs": ["轴调试", "测试序列", "手指调试", "传感器", "表面分布", "标定点"],
"readout_title": "轴状态",
"enabled": "已使能",
"disabled": "未使能",
@@ -151,6 +151,38 @@ STRINGS = {
["3", "4.000", "0.000", "12.400", "待测", "—", "—"],
],
"points_hint": "坐标由网格生成并逐点校验,不接受手工输入:手工输入的坐标没有任何检查,一个错数字会毁掉整条序列。",
"finger_bundle_label": "导出包体",
"finger_bundle_value": "D:\\orisys_ws\\orisys-sensor-design\\build\\export\\finger\\r01",
"finger_load": "载入包体…",
"finger_initial_view": "初始视角",
"finger_reset": "复位视角",
"finger_textured_note": "UV 贴图:patch_texture_compute.png",
"finger_axes_note": "黄色为工具轴 · X/Y 为工作台,Z 为工具行程 · 绿=对齐,红=不可对齐",
"finger_bundle_summary": "1032 顶点 · 1974 三角面 · 15.00 × 7.50 × 15.81 mm · 0.018388 mm/单位",
"finger_flat_title": "展平图像(点击取点)",
"finger_pick_hint": "点击传感面上的像素即可取点;落在 UV 缝隙上的像素会被拒绝。",
"finger_readout_title": "读数",
"finger_readout_rows": [
["传感器 (mm)", "2.841, 3.512, 0.940", None],
["法向", "-0.18, -0.88, 0.43", None],
["平台 (mm)", "2.841, 0.940, -3.512", None],
["X / Y (mm)", "2.841, 0.940", None],
["yaw / pitch", "-78.42° / 23.10°", None],
["对轴残差", "0.03° 可对轴", "#12b76a"],
["坐标系映射", "x→x, y→z, z→-y", None],
],
"finger_plate_label": "安装板厚 (mm)",
"finger_plate_value": "0.000",
"finger_preview_title": "三维预览",
"finger_preview_legend": "曲面颜色 = 对轴残差(绿为对正,红为对不上) · 黄色为 Z 轴",
"finger_point_cols": ["#", "像素 u, v", "sensor x", "sensor y", "sensor z", "nx", "ny", "nz", "X / Y", "Z", "yaw", "pitch"],
"finger_point_rows": [
["1", "0.35, 0.40", "2.841", "3.512", "0.940", "-0.18", "-0.88", "0.43", "2.841 / 0.940", "7.958", "-78.42", "23.10"],
["2", "0.62, 0.55", "5.113", "2.204", "-1.870", "0.21", "0.55", "-0.81", "5.113 / -1.870", "7.958", "12.05", "8.44"],
["3", "0.50, 0.72", "3.902", "6.118", "2.405", "0.55", "0.19", "0.81", "3.902 / 2.405", "7.958", "-90.00", "51.66"],
],
"finger_bad_word": "撞 yaw 行程限位",
"finger_point_count": "3 个取点 · 展平 1024 × 1024 · 有效像素 72.9%",
"sensor_note": "设备与信号源、实时图像、力曲线",
"sensor_curve": "力曲线 (FN / FX / FY)",
"surface_note": "多点标定灵敏度 → 三维表面色图",
@@ -196,7 +228,7 @@ STRINGS = {
"nodes": "Nodes",
"axes_count": "Axes",
"busdata_group": "Bus data (SDO / RTEX) ▶",
"tabs": ["Axis debug", "Test sequence", "Sensor", "Surface map", "Calibration points"],
"tabs": ["Axis debug", "Test sequence", "Finger debug", "Sensor", "Surface map", "Calibration points"],
"readout_title": "Axis state",
"enabled": "Enabled",
"disabled": "Disabled",
@@ -246,6 +278,38 @@ STRINGS = {
["3", "4.000", "0.000", "12.400", "Pending", "—", "—"],
],
"points_hint": "Coordinates are generated and checked per point; they are not typed. A typed coordinate is one nothing has checked.",
"finger_bundle_label": "Export bundle",
"finger_bundle_value": "D:\\orisys_ws\\orisys-sensor-design\\build\\export\\finger\\r01",
"finger_load": "Load bundle…",
"finger_initial_view": "Initial view",
"finger_reset": "Reset view",
"finger_textured_note": "UV texture: patch_texture_compute.png",
"finger_axes_note": "Yellow is the tool axis · X/Y are the table, Z the tool travel · green aligned, red not",
"finger_bundle_summary": "1032 vertices · 1974 triangles · 15.00 × 7.50 × 15.81 mm · 0.018388 mm per unit",
"finger_flat_title": "Flat image (click to pick)",
"finger_pick_hint": "Click a pixel on the sensing surface to pick a point; pixels in a UV gap are refused.",
"finger_readout_title": "Readouts",
"finger_readout_rows": [
["Sensor (mm)", "2.841, 3.512, 0.940", None],
["Normal", "-0.18, -0.88, 0.43", None],
["Platform (mm)", "2.841, 0.940, -3.512", None],
["X / Y (mm)", "2.841, 0.940", None],
["yaw / pitch", "-78.42° / 23.10°", None],
["Residual", "0.03° can align", "#12b76a"],
["Frame map", "x→x, y→z, z→-y", None],
],
"finger_plate_label": "Mounting plate (mm)",
"finger_plate_value": "0.000",
"finger_preview_title": "3D preview",
"finger_preview_legend": "Surface colour = alignment residual (green aligned, red not) · yellow is the Z axis",
"finger_point_cols": ["#", "pixel u, v", "sensor x", "sensor y", "sensor z", "nx", "ny", "nz", "X / Y", "Z", "yaw", "pitch"],
"finger_point_rows": [
["1", "0.35, 0.40", "2.841", "3.512", "0.940", "-0.18", "-0.88", "0.43", "2.841 / 0.940", "7.958", "-78.42", "23.10"],
["2", "0.62, 0.55", "5.113", "2.204", "-1.870", "0.21", "0.55", "-0.81", "5.113 / -1.870", "7.958", "12.05", "8.44"],
["3", "0.50, 0.72", "3.902", "6.118", "2.405", "0.55", "0.19", "0.81", "3.902 / 2.405", "7.958", "-90.00", "51.66"],
],
"finger_bad_word": "hits yaw travel limit",
"finger_point_count": "3 picked points · flat 1024 × 1024 · 72.9% valid pixels",
"sensor_note": "Device and source, live image, force curves",
"sensor_curve": "Force curves (FN / FX / FY)",
"surface_note": "Multi-point calibration sensitivity → 3D surface colour map",
@@ -875,16 +939,151 @@ def draw_points_tab(painter: QPainter, palette: Palette, s: dict, rect: QRect) -
_button(painter, QRect(inner.x() + 250, y, 100, 28), "删除" if s["axis_label"] == "轴号" else "Remove", palette)
def draw_finger_tab(painter: QPainter, palette: Palette, s: dict, rect: QRect) -> None:
"""The finger debugger: pick on the flat image, read the pose, check the 3D preview."""
inner = rect.adjusted(10, 8, -10, -8)
zh = s["axis_label"] == "轴号"
y = inner.y()
_text(painter, QRect(inner.x(), y, 90, 24), s["finger_bundle_label"], palette, size=10, muted=True)
_field(painter, QRect(inner.x() + 92, y, 300, 24), s["finger_bundle_value"], palette)
_button(painter, QRect(inner.x() + 400, y, 100, 24), s["finger_load"], palette)
_button(painter, QRect(inner.x() + 508, y, 100, 24), s["finger_initial_view"], palette)
_button(painter, QRect(inner.x() + 616, y, 100, 24), s["finger_reset"], palette)
y += 34
_text(painter, QRect(inner.x(), y, inner.width(), 18), s["finger_bundle_summary"], palette, size=9, muted=True)
y += 24
body_h = int((inner.height() - (y - inner.y())) * 0.62)
table_h = inner.bottom() - (y + body_h) - 8
gap = 10
usable = inner.width() - 2 * gap
picker_w = int(usable * 28 / 100)
readout_w = int(usable * 32 / 100)
preview_w = usable - picker_w - readout_w
picker = QRect(inner.x(), y, picker_w, body_h)
readouts = QRect(picker.right() + gap, y, readout_w, body_h)
preview = QRect(readouts.right() + gap, y, preview_w, body_h)
# Flat unrolled image.
inner_picker = _panel(painter, picker, palette)
_text(painter, QRect(inner_picker.x(), inner_picker.y(), inner_picker.width(), 18), s["finger_flat_title"], palette, size=10, bold=True)
flat = QRect(inner_picker.x(), inner_picker.y() + 22, inner_picker.width(), inner_picker.height() - 22 - 40)
# The textured UV space: black outside the island, artwork inside it.
painter.fillRect(flat, QColor("#000000"))
island = flat.adjusted(int(flat.width() * 0.06), int(flat.height() * 0.16), -int(flat.width() * 0.06), -int(flat.height() * 0.16))
painter.setPen(QPen(QColor("#000000"), 0))
columns, rows = 22, 14
for row in range(rows):
for column in range(columns):
cell = QRect(
island.x() + column * island.width() // columns,
island.y() + row * island.height() // rows,
island.width() // columns + 1,
island.height() // rows + 1,
)
shade = ((row * 7 + column * 13) % 5) / 4.0
painter.fillRect(
cell,
QColor(int(60 + 150 * shade), int(40 + 120 * ((row + column) % 3) / 2), int(90 + 120 * (1 - shade))),
)
for index, (fx, fy) in enumerate(((0.35, 0.4), (0.62, 0.55), (0.5, 0.72))):
mark = QPoint(int(flat.x() + fx * flat.width()), int(flat.y() + fy * flat.height()))
painter.setPen(QPen(QColor("#ffffff"), 2))
painter.setBrush(QColor("#d92d20") if index == 2 else QColor("#12b76a"))
painter.drawEllipse(mark, 4, 4)
# Leave no brush behind: a later panel fills its background, and a leaked
# brush turns that fill into the marker colour.
painter.setBrush(Qt.BrushStyle.NoBrush)
_text(painter, QRect(inner_picker.x(), flat.bottom() + 2, inner_picker.width(), 18), s["finger_textured_note"], palette, size=9, muted=True)
_text(painter, QRect(inner_picker.x(), flat.bottom() + 20, inner_picker.width(), 18), s["finger_pick_hint"], palette, size=9, muted=True)
# Readouts.
inner_read = _panel(painter, readouts, palette)
_text(painter, QRect(inner_read.x(), inner_read.y(), inner_read.width(), 18), s["finger_readout_title"], palette, size=10, bold=True)
row_y = inner_read.y() + 24
label_w = int(inner_read.width() * 0.42)
for label, value, colour in s["finger_readout_rows"]:
_text(painter, QRect(inner_read.x(), row_y, label_w, 20), label, palette, size=9, muted=True)
_text(painter, QRect(inner_read.x() + label_w, row_y, inner_read.width() - label_w, 20), value, palette, size=9, colour=QColor(colour) if colour else None)
row_y += 20
_text(painter, QRect(inner_read.x(), inner_read.bottom() - 36, inner_read.width(), 18), s["finger_plate_label"], palette, size=9, muted=True)
_field(painter, QRect(inner_read.x(), inner_read.bottom() - 20, inner_read.width(), 20), s["finger_plate_value"], palette)
# 3D preview: the tool axis and a normal with its residual.
inner_preview = _panel(painter, preview, palette)
_text(painter, QRect(inner_preview.x(), inner_preview.y(), inner_preview.width(), 18), s["finger_preview_title"], palette, size=10, bold=True)
view = QRect(inner_preview.x(), inner_preview.y() + 22, inner_preview.width(), inner_preview.height() - 22 - 34)
painter.fillRect(view, QColor("#12161c"))
painter.setPen(QPen(QColor("#2a313c"), 1))
for i in range(1, 4):
painter.drawLine(view.x() + i * view.width() // 4, view.y(), view.x() + i * view.width() // 4, view.bottom())
cx, cy = view.center().x(), view.center().y() + 40
axis = QPoint(cx, view.y() + 24)
painter.setPen(QPen(QColor("#f2c94c"), 3))
painter.drawLine(axis, QPoint(cx, cy))
painter.setBrush(QColor("#f2c94c"))
painter.drawEllipse(QPoint(cx, cy), 3, 3)
# The motion axes: the platform frame, drawn in the sensor frame.
for colour, end in (
("#e65454", QPoint(cx - 96, cy + 18)),
("#61cc61", QPoint(cx + 30, cy + 92)),
("#5c8cf2", QPoint(cx + 88, cy - 26)),
):
painter.setPen(QPen(QColor(colour), 2))
painter.drawLine(QPoint(cx, cy), end)
painter.setPen(QPen(QColor("#12b76a"), 3))
painter.drawLine(QPoint(cx, cy), QPoint(cx + 26, cy - 96))
painter.setPen(QPen(QColor("#d92d20"), 3))
painter.drawLine(QPoint(cx, cy), QPoint(cx - 74, cy - 78))
painter.setBrush(Qt.BrushStyle.NoBrush)
painter.setPen(QPen(QColor("#4a5568"), 1))
painter.drawLine(QPoint(cx - 90, cy), QPoint(cx + 90, cy))
painter.setPen(QPen(QColor("#e65454"), 1))
painter.drawText(QPoint(cx - 108, cy + 24), "X")
painter.setPen(QPen(QColor("#61cc61"), 1))
painter.drawText(QPoint(cx + 34, cy + 100), "Y")
painter.setPen(QPen(QColor("#5c8cf2"), 1))
painter.drawText(QPoint(cx + 92, cy - 28), "Z")
_text(painter, QRect(inner_preview.x(), view.bottom() + 2, inner_preview.width(), 30), s["finger_axes_note"], palette, size=8, muted=True)
# Picked points.
table = QRect(inner.x(), y + body_h + 8, inner.width(), max(60, table_h))
painter.fillRect(table, palette.window)
painter.setPen(QPen(palette.border, 1))
painter.drawRect(table.adjusted(0, 0, -1, -1))
header_h = 24
painter.fillRect(QRect(table.x(), table.y(), table.width(), header_h), palette.panel)
columns = s["finger_point_cols"]
widths = [int(table.width() * share) for share in (0.06, 0.09, 0.1, 0.09, 0.09, 0.07, 0.07, 0.07, 0.09, 0.09, 0.09, 0.09)]
x = table.x()
for name, width in zip(columns, widths):
_text(painter, QRect(x, table.y(), width, header_h), name, palette, size=9, muted=True, align=Qt.AlignmentFlag.AlignCenter)
x += width
for index, row in enumerate(s["finger_point_rows"]):
row_y = table.y() + header_h + index * 26
if index % 2 == 0:
painter.fillRect(QRect(table.x() + 1, row_y, table.width() - 2, 25), palette.panel)
x = table.x()
for value, width in zip(row, widths):
colour = palette.danger if value == s["finger_bad_word"] else None
_text(painter, QRect(x, row_y, width, 26), value, palette, size=10, align=Qt.AlignmentFlag.AlignCenter, colour=colour)
x += width
_text(painter, QRect(table.x() + 8, table.bottom() + 2, table.width(), 18), s["finger_point_count"], palette, size=9, muted=True)
TAB_PAINTERS = {
"axes": draw_axis_tab,
"sequence": draw_sequence_tab,
"finger": draw_finger_tab,
"sensor": draw_sensor_tab,
"surface": draw_surface_tab,
"points": draw_points_tab,
}
#: Tab index -> painter key, for the tab strip labels.
TAB_KEYS = ("axes", "sequence", "sensor", "surface", "points")
TAB_KEYS = ("axes", "sequence", "finger", "sensor", "surface", "points")
def draw_page(
@@ -983,8 +1182,10 @@ def main() -> int:
parser.add_argument("--height", type=int, default=PAGE_H)
parser.add_argument(
"--page",
default="axes",
choices=("axes", "sequence", "sensor", "surface", "points", "all"),
default=TAB_KEYS[0],
# Derived, not listed: a literal here silently rejects any tab added to
# TAB_KEYS, which is how the finger page first went missing.
choices=(*TAB_KEYS, "all"),
help="which page to draw; 'all' writes one full-size image per page",
)
args = parser.parse_args()