@@ -1,8 +1,7 @@
""" The finger debugger ' s panel: picking, refusing, and what a row contains.
""" The finger debugger ' s panel: picking, refusing, and what a row contains.
The bundle is the real export on this machine. It is skipped rather than failed
Interaction tests use a small dome. Geometry and native-resolution checks keep
w hen absent, because the export lives in a sibling design repo: a machine without
t he real export and skip individually when that optional export is unavailable.
it has not broken AutoCal.
"""
"""
from __future__ import annotations
from __future__ import annotations
@@ -11,27 +10,26 @@ from pathlib import Path
import numpy as np
import numpy as np
import pytest
import pytest
from orisys_autocal . motion . kinematics import PlatformStack
from orisys_autocal . motion . kinematics import PlatformStack , rotation_about
from orisys_autocal . sensor import (
from orisys_autocal . sensor import (
DEFAULT_FLAT_SIZE ,
DEFAULT_FLAT_SIZE ,
load_bundle ,
load_bundle ,
rasterise ,
rasterise ,
to_sensor ,
to_sensor ,
)
)
from orisys_autocal . sensor . export_bundle import SensorBundle
from orisys_autocal . ui . sensor_panel import FingerPanel
from orisys_autocal . ui . sensor_panel import FingerPanel
from orisys_autocal . ui . pose_preview import PosePreview
from PySide6 . QtCore import QPointF
from PySide6 . QtCore import QPointF
BUNDLE = Path (
BUNDLE = Path (
r " D: \ orisys_ws \ orisys-sensor-design \ build \ export \ finger \ r01 \ compute "
r " D: \ orisys_ws \ orisys-sensor-design \ build \ export \ finger \ r01 \ compute "
)
)
pytestmark = pytest . mark . skipif (
not BUNDLE . is_dir ( ) , reason = f " no sensor export at { BUNDLE } "
)
@pytest.fixture ( scope = " module " )
@pytest.fixture ( scope = " module " )
def bundle ( ) :
def bundle ( ) :
if not BUNDLE . is_dir ( ) :
pytest . skip ( f " no sensor export at { BUNDLE } " )
return load_bundle ( BUNDLE )
return load_bundle ( BUNDLE )
@@ -40,11 +38,44 @@ def flat(bundle):
return rasterise ( bundle , size_hw = DEFAULT_FLAT_SIZE )
return rasterise ( bundle , size_hw = DEFAULT_FLAT_SIZE )
@pytest.fixture ( scope = " module " )
def small_surface ( ) :
""" A five-vertex dome with tilted normals and empty UV corners for UI tests. """
vertices = np . array ( [ [ - 5 , 8 , 8 ] , [ 5 , 8 , 8 ] , [ - 5 , 8 , 18 ] , [ 5 , 8 , 18 ] , [ 0 , 12 , 13 ] ] , dtype = float )
faces = np . array ( [ [ 0 , 4 , 1 ] , [ 1 , 4 , 3 ] , [ 3 , 4 , 2 ] , [ 2 , 4 , 0 ] ] )
uv = np . array ( [ [ .5 , 0 ] , [ 1 , .5 ] , [ 0 , .5 ] , [ .5 , 1 ] , [ .5 , .5 ] ] )
surface = SensorBundle ( path = Path ( " synthetic-finger " ) , vertices_mm = vertices , faces = faces ,
uv_data = uv [ faces ] . reshape ( - 1 , 2 ) , loop_to_vertex = faces . ravel ( ) )
return surface , rasterise ( surface , size_hw = ( 128 , 128 ) )
@pytest.fixture
@pytest.fixture
def panel ( qt_app , bundle , flat ) :
def panel ( qt_app , small_surface ) :
widget = FingerPanel ( stack = PlatformStack ( ) )
widget . set_bundle ( * small_surface )
yield widget
widget . preview . stop_simulation ( )
widget . close ( )
widget . deleteLater ( )
@pytest.fixture
def native_panel ( qt_app , bundle , flat ) :
""" Keep the production mesh and raster for native-resolution mapping checks. """
widget = FingerPanel ( stack = PlatformStack ( ) )
widget = FingerPanel ( stack = PlatformStack ( ) )
widget . set_bundle ( bundle , flat )
widget . set_bundle ( bundle , flat )
yield widget
yield widget
widget . preview . stop_simulation ( )
widget . close ( )
widget . deleteLater ( )
@pytest.fixture
def preview ( qt_app ) :
widget = PosePreview ( )
yield widget
widget . stop_simulation ( )
widget . close ( )
widget . deleteLater ( )
widget . deleteLater ( )
@@ -71,7 +102,7 @@ def _shrink_raster(monkeypatch, window_mod, size=(256, 256)):
monkeypatch . setattr ( window_mod , " rasterise " , small )
monkeypatch . setattr ( window_mod , " rasterise " , small )
def test_the_window_loads_the_remembered_bundle_at_startup ( qt_app , monkeypatch ) :
def test_the_window_loads_the_remembered_bundle_at_startup ( qt_app , bundle , monkeypatch ) :
""" A filled-in path over two empty views reads as a broken screen. """
""" A filled-in path over two empty views reads as a broken screen. """
import shutil
import shutil
@@ -139,7 +170,7 @@ def test_the_preview_legend_is_a_label_not_gl_text(qt_app):
# ------------------------------------------------- the initial view and axes
# ------------------------------------------------- the initial view and axes
def test_the_initial_view_is_the_commissioned_orientation ( qt_app , bundle ) :
def test_the_initial_view_is_the_commissioned_orientation ( bundle , preview ) :
""" Sensor +Y up the screen and sensor +Z toward the viewer, by construction.
""" Sensor +Y up the screen and sensor +Z toward the viewer, by construction.
The user asked for this state to be the configuration the screen opens in, so
The user asked for this state to be the configuration the screen opens in, so
@@ -149,10 +180,7 @@ def test_the_initial_view_is_the_commissioned_orientation(qt_app, bundle):
right, because getting it wrong draws the tool axis pointing down.
right, because getting it wrong draws the tool axis pointing down.
"""
"""
from orisys_autocal . sensor import to_sensor
from orisys_autocal . sensor import to_sensor
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
preview . resize ( 600 , 400 )
preview . resize ( 600 , 400 )
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
preview . initial_view ( )
preview . initial_view ( )
@@ -177,19 +205,11 @@ def test_the_initial_view_is_the_commissioned_orientation(qt_app, bundle):
assert eye [ 1 ] > centre [ 1 ]
assert eye [ 1 ] > centre [ 1 ]
assert eye [ 2 ] < centre [ 2 ]
assert eye [ 2 ] < centre [ 2 ]
assert forward [ 1 ] < - 0.5 , f " the view must look toward − Y, but forward is { forward } "
assert forward [ 1 ] < - 0.5 , f " the view must look toward − Y, but forward is { forward } "
finally :
preview . deleteLater ( )
def test_orbiting_and_then_resetting_returns_to_the_initial_view ( qt_app , bundle ) :
def test_orbiting_and_then_resetting_returns_to_the_initial_view ( bundle , preview ) :
from orisys_autocal . ui . pose_preview import (
from orisys_autocal . ui . pose_preview import INITIAL_PITCH_RAD , INITIAL_YAW_RAD
INITIAL_PITCH_RAD ,
INITIAL_YAW_RAD ,
PosePreview ,
)
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
preview . _yaw = 1.9 # noqa: SLF001 - pretend the operator dragged
preview . _yaw = 1.9 # noqa: SLF001 - pretend the operator dragged
preview . _pitch = - 0.4 # noqa: SLF001
preview . _pitch = - 0.4 # noqa: SLF001
@@ -200,31 +220,21 @@ def test_orbiting_and_then_resetting_returns_to_the_initial_view(qt_app, bundle)
assert preview . _yaw == pytest . approx ( INITIAL_YAW_RAD ) # noqa: SLF001
assert preview . _yaw == pytest . approx ( INITIAL_YAW_RAD ) # noqa: SLF001
assert preview . _pitch == pytest . approx ( INITIAL_PITCH_RAD ) # noqa: SLF001
assert preview . _pitch == pytest . approx ( INITIAL_PITCH_RAD ) # noqa: SLF001
assert preview . _distance_scale == pytest . approx ( 1.0 ) # noqa: SLF001
assert preview . _distance_scale == pytest . approx ( 1.0 ) # noqa: SLF001
finally :
preview . deleteLater ( )
def test_the_motion_axes_can_be_turned_off ( qt_app , bundle ) :
def test_the_motion_axes_can_be_turned_off ( bundle , preview ) :
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
assert preview . show_axes
assert preview . show_axes
preview . set_show_axes ( False )
preview . set_show_axes ( False )
assert not preview . show_axes
assert not preview . show_axes
preview . set_show_axes ( True )
preview . set_show_axes ( True )
assert preview . show_axes
assert preview . show_axes
finally :
preview . deleteLater ( )
def test_the_axis_labels_project_onto_their_arrows ( qt_app , bundle ) :
def test_the_axis_labels_project_onto_their_arrows ( bundle , preview ) :
""" The 2D label and the 3D arrow must agree, or the label names nothing. """
""" The 2D label and the 3D arrow must agree, or the label names nothing. """
from orisys_autocal . sensor import to_sensor
from orisys_autocal . sensor import to_sensor
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
preview . resize ( 600 , 400 )
preview . resize ( 600 , 400 )
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
stack = PlatformStack ( )
stack = PlatformStack ( )
@@ -257,20 +267,13 @@ def test_the_axis_labels_project_onto_their_arrows(qt_app, bundle):
for x , y in clamped :
for x , y in clamped :
assert 0 < = x < = preview . width ( )
assert 0 < = x < = preview . width ( )
assert 0 < = y < = preview . height ( )
assert 0 < = y < = preview . height ( )
finally :
preview . deleteLater ( )
def test_a_point_behind_the_camera_has_no_screen_position ( qt_app , bundle ) :
def test_a_point_behind_the_camera_has_no_screen_position ( bundle , preview ) :
""" Drawing a label for it would put a stray letter on the widget. """
""" Drawing a label for it would put a stray letter on the widget. """
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
assert preview . project ( bundle . centre_mm ) is not None
assert preview . project ( bundle . centre_mm ) is not None
finally :
preview . deleteLater ( )
def _frustum_pixel ( preview , point_mm ) - > tuple [ float , float ] :
def _frustum_pixel ( preview , point_mm ) - > tuple [ float , float ] :
@@ -313,12 +316,9 @@ def _frustum_pixel(preview, point_mm) -> tuple[float, float]:
return ( ( ndc_x + 1.0 ) / 2.0 * width , ( 1.0 - ndc_y ) / 2.0 * height )
return ( ( ndc_x + 1.0 ) / 2.0 * width , ( 1.0 - ndc_y ) / 2.0 * height )
def test_the_projection_matches_the_frustum_it_is_drawn_with ( qt_app , bundle ) :
def test_the_projection_matches_the_frustum_it_is_drawn_with ( bundle , preview ) :
""" The overlay is placed with this, so it has to be the renderer ' s projection. """
""" The overlay is placed with this, so it has to be the renderer ' s projection. """
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
preview . resize ( 900 , 640 )
preview . resize ( 900 , 640 )
stack = PlatformStack ( )
stack = PlatformStack ( )
@@ -339,13 +339,11 @@ def test_the_projection_matches_the_frustum_it_is_drawn_with(qt_app, bundle):
# must agree on — the check that survives any reformulation of either side.
# must agree on — the check that survives any reformulation of either side.
mid = preview . project ( preview . _focus ( ) ) # noqa: SLF001
mid = preview . project ( preview . _focus ( ) ) # noqa: SLF001
assert mid == pytest . approx ( ( 450.0 , 320.0 ) , abs = 1e-6 )
assert mid == pytest . approx ( ( 450.0 , 320.0 ) , abs = 1e-6 )
finally :
preview . deleteLater ( )
def test_the_flat_image_is_shown_whole_not_cropped ( panel ) :
def test_the_flat_image_is_shown_whole_not_cropped ( native_panel ) :
""" At native size the 1024 image was cropped to a corner of the UV space. """
""" At native size the 1024 image was cropped to a corner of the UV space. """
view = panel . view
view = native_panel . view
view . resize ( 240 , 300 )
view . resize ( 240 , 300 )
view . set_flat ( view . flat )
view . set_flat ( view . flat )
@@ -357,9 +355,9 @@ def test_the_flat_image_is_shown_whole_not_cropped(panel):
assert shown . width ( ) == pytest . approx ( shown . height ( ) , abs = 2 )
assert shown . width ( ) == pytest . approx ( shown . height ( ) , abs = 2 )
def test_a_click_on_the_scaled_image_maps_to_the_pixel_under_it ( panel ) :
def test_a_click_on_the_scaled_image_maps_to_the_pixel_under_it ( native_panel ) :
""" Scaling must not move the mapping; a click in the corner is the corner. """
""" Scaling must not move the mapping; a click in the corner is the corner. """
view = panel . view
view = native_panel . view
view . resize ( 240 , 300 )
view . resize ( 240 , 300 )
view . set_flat ( view . flat )
view . set_flat ( view . flat )
shown = view . pixmap ( )
shown = view . pixmap ( )
@@ -417,7 +415,7 @@ def _run_simulation(preview) -> None:
preview . _sim_tick ( ) # noqa: SLF001 - stepping the animation deterministically
preview . _sim_tick ( ) # noqa: SLF001 - stepping the animation deterministically
def test_playing_the_motion_puts_the_normal_on_the_tool_axis ( qt_app , bundle ) :
def test_playing_the_motion_puts_the_normal_on_the_tool_axis ( bundle , preview ) :
""" The two invariants the whole animation exists to show.
""" The two invariants the whole animation exists to show.
After playback the picked normal must lie along the tool axis, and the contact
After playback the picked normal must lie along the tool axis, and the contact
@@ -426,14 +424,11 @@ def test_playing_the_motion_puts_the_normal_on_the_tool_axis(qt_app, bundle):
and checking against it made the test agree with the drawing and with nothing
and checking against it made the test agree with the drawing and with nothing
on the bench.
on the bench.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
tool_local = to_sensor ( np . asarray ( stack . tool_direction , dtype = float ) , stack . axis_map )
tool_local = to_sensor ( np . asarray ( stack . tool_direction , dtype = float ) , stack . axis_map )
tool_local = tool_local / np . linalg . norm ( tool_local )
tool_local = tool_local / np . linalg . norm ( tool_local )
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
normals = bundle . vertex_normals ( )
normals = bundle . vertex_normals ( )
worst_angle = 0.0
worst_angle = 0.0
@@ -480,22 +475,16 @@ def test_playing_the_motion_puts_the_normal_on_the_tool_axis(qt_app, bundle):
assert worst_angle < 0.5 , (
assert worst_angle < 0.5 , (
f " after playing, a normal is still { worst_angle : .3f } deg off the tool axis "
f " after playing, a normal is still { worst_angle : .3f } deg off the tool axis "
)
)
finally :
preview . stop_simulation ( )
preview . deleteLater ( )
def test_the_simulation_rotates_first_and_then_translates ( qt_app , bundle ) :
def test_the_simulation_rotates_first_and_then_translates ( bundle , preview ) :
""" The bench contract: stages settle before the table moves. """
""" The bench contract: stages settle before the table moves. """
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
surface_normal = bundle . vertex_normals ( ) [ 0 ]
surface_normal = bundle . vertex_normals ( ) [ 0 ]
point = bundle . vertices_mm [ 0 ]
point = bundle . vertices_mm [ 0 ]
pose = stack . solve ( point , surface_normal )
pose = stack . solve ( point , surface_normal )
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
preview . set_point ( point , surface_normal )
preview . set_point ( point , surface_normal )
preview . set_pose ( pose )
preview . set_pose ( pose )
@@ -516,19 +505,13 @@ def test_the_simulation_rotates_first_and_then_translates(qt_app, bundle):
rotation_share , translation_share = preview . simulation_phases ( ) # noqa: SLF001
rotation_share , translation_share = preview . simulation_phases ( ) # noqa: SLF001
assert rotation_share == pytest . approx ( 1.0 )
assert rotation_share == pytest . approx ( 1.0 )
assert translation_share == pytest . approx ( 1.0 )
assert translation_share == pytest . approx ( 1.0 )
finally :
preview . stop_simulation ( )
preview . deleteLater ( )
def test_the_simulation_stops_and_can_be_replayed ( qt_app , bundle ) :
def test_the_simulation_stops_and_can_be_replayed ( bundle , preview ) :
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
point = bundle . vertices_mm [ 0 ]
point = bundle . vertices_mm [ 0 ]
pose = stack . solve ( point , bundle . vertex_normals ( ) [ 0 ] )
pose = stack . solve ( point , bundle . vertex_normals ( ) [ 0 ] )
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
preview . set_point ( point , bundle . vertex_normals ( ) [ 0 ] )
preview . set_point ( point , bundle . vertex_normals ( ) [ 0 ] )
preview . set_pose ( pose )
preview . set_pose ( pose )
@@ -545,23 +528,15 @@ def test_the_simulation_stops_and_can_be_replayed(qt_app, bundle):
assert not preview . simulating
assert not preview . simulating
preview . start_simulation ( )
preview . start_simulation ( )
assert preview . simulating
assert preview . simulating
finally :
preview . stop_simulation ( )
preview . deleteLater ( )
def test_the_simulation_cannot_start_without_a_pose ( qt_app , bundle ) :
def test_the_simulation_cannot_start_without_a_pose ( bundle , preview ) :
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
assert not preview . can_simulate
assert not preview . can_simulate
preview . start_simulation ( )
preview . start_simulation ( )
assert not preview . simulating
assert not preview . simulating
assert preview . _sim_transform ( ) is None # noqa: SLF001
assert preview . _sim_transform ( ) is None # noqa: SLF001
finally :
preview . deleteLater ( )
# ------------------------------------------------------------- the point table
# ------------------------------------------------------------- the point table
@@ -720,7 +695,7 @@ def test_clearing_the_points_removes_the_markers(panel):
# ------------------------------------------------------- the motion table frame
# ------------------------------------------------------- the motion table frame
def test_the_two_rotation_axes_cross_at_one_origin ( qt_app , bundle ) :
def test_the_two_rotation_axes_cross_at_one_origin ( bundle , preview ) :
""" The bench statement: yaw and pitch share an origin.
""" The bench statement: yaw and pitch share an origin.
Both axes are drawn through the frame ' s single ``origin`` point, so if this
Both axes are drawn through the frame ' s single ``origin`` point, so if this
@@ -728,10 +703,7 @@ def test_the_two_rotation_axes_cross_at_one_origin(qt_app, bundle):
asserted on the returned frame rather than the drawing, because a picture
asserted on the returned frame rather than the drawing, because a picture
cannot be checked in a test and a wrong frame would draw perfectly happily.
cannot be checked in a test and a wrong frame would draw perfectly happily.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
frame = preview . table_frame ( )
frame = preview . table_frame ( )
origin = frame [ " origin " ]
origin = frame [ " origin " ]
@@ -745,21 +717,16 @@ def test_the_two_rotation_axes_cross_at_one_origin(qt_app, bundle):
assert np . allclose ( offset , 0.0 )
assert np . allclose ( offset , 0.0 )
# And the two axes are distinct, not accidentally parallel.
# And the two axes are distinct, not accidentally parallel.
assert abs ( float ( frame [ " yaw_direction " ] @ frame [ " pitch_direction " ] ) ) < 1e-6
assert abs ( float ( frame [ " yaw_direction " ] @ frame [ " pitch_direction " ] ) ) < 1e-6
finally :
preview . deleteLater ( )
def test_the_sensor_base_sits_above_the_origin_by_the_setup_z ( qt_app , bundle ) :
def test_the_sensor_base_sits_above_the_origin_by_the_setup_z ( bundle , preview ) :
""" The other half of the statement, and the reason for the highlighted plane.
""" The other half of the statement, and the reason for the highlighted plane.
The mounting height — the plate, and any shim with it — is measured along the
The mounting height — the plate, and any shim with it — is measured along the
platform ' s tool axis, which is the table origin plane ' s normal, so the offset
platform ' s tool axis, which is the table origin plane ' s normal, so the offset
must be exactly that much along the normal and nothing at all in the plane.
must be exactly that much along the normal and nothing at all in the plane.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
for plate in ( 0.0 , 2.5 , 6.0 ) :
for plate in ( 0.0 , 2.5 , 6.0 ) :
preview . set_setup ( ( 0.0 , 0.0 , plate ) , 0.0 )
preview . set_setup ( ( 0.0 , 0.0 , plate ) , 0.0 )
@@ -771,16 +738,11 @@ def test_the_sensor_base_sits_above_the_origin_by_the_setup_z(qt_app, bundle):
# No sideways component, so the base plane is parallel to the origin
# No sideways component, so the base plane is parallel to the origin
# plane rather than merely offset from it.
# plane rather than merely offset from it.
assert float ( np . linalg . norm ( gap - ( gap @ normal ) * normal ) ) == pytest . approx ( 0.0 )
assert float ( np . linalg . norm ( gap - ( gap @ normal ) * normal ) ) == pytest . approx ( 0.0 )
finally :
preview . deleteLater ( )
def test_the_table_frame_normal_is_the_tool_axis ( qt_app , bundle ) :
def test_the_table_frame_normal_is_the_tool_axis ( bundle , preview ) :
""" The origin plane ' s normal is the direction the sensor base is measured along. """
""" The origin plane ' s normal is the direction the sensor base is measured along. """
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
frame = preview . table_frame ( )
frame = preview . table_frame ( )
# In the mesh's frame, so compare against the tool axis mapped the same way.
# In the mesh's frame, so compare against the tool axis mapped the same way.
@@ -789,20 +751,15 @@ def test_the_table_frame_normal_is_the_tool_axis(qt_app, bundle):
)
)
expected = expected / np . linalg . norm ( expected )
expected = expected / np . linalg . norm ( expected )
assert np . allclose ( frame [ " plane_normal " ] , expected , atol = 1e-12 )
assert np . allclose ( frame [ " plane_normal " ] , expected , atol = 1e-12 )
finally :
preview . deleteLater ( )
def test_the_table_frame_does_not_move_with_the_simulation ( qt_app , bundle ) :
def test_the_table_frame_does_not_move_with_the_simulation ( bundle , preview ) :
""" It is the machine ' s frame: it must stay put while the sensor is animated.
""" It is the machine ' s frame: it must stay put while the sensor is animated.
If it moved, the picture would show the sensor standing still relative to a
If it moved, the picture would show the sensor standing still relative to a
moving table, which is the opposite of what makes an alignment checkable.
moving table, which is the opposite of what makes an alignment checkable.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
before = { key : np . array ( value ) for key , value in preview . table_frame ( ) . items ( ) }
before = { key : np . array ( value ) for key , value in preview . table_frame ( ) . items ( ) }
point = bundle . vertices_mm [ 0 ]
point = bundle . vertices_mm [ 0 ]
@@ -813,23 +770,15 @@ def test_the_table_frame_does_not_move_with_the_simulation(qt_app, bundle):
after = preview . table_frame ( )
after = preview . table_frame ( )
for key , value in before . items ( ) :
for key , value in before . items ( ) :
assert np . allclose ( after [ key ] , value ) , f " { key } moved with the simulation "
assert np . allclose ( after [ key ] , value ) , f " { key } moved with the simulation "
finally :
preview . stop_simulation ( )
preview . deleteLater ( )
def test_the_table_frame_can_be_hidden ( qt_app , bundle ) :
def test_the_table_frame_can_be_hidden ( bundle , preview ) :
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
assert preview . show_table
assert preview . show_table
preview . set_show_table ( False )
preview . set_show_table ( False )
assert not preview . show_table
assert not preview . show_table
preview . set_show_table ( True )
preview . set_show_table ( True )
assert preview . show_table
assert preview . show_table
finally :
preview . deleteLater ( )
def test_the_panel_drives_the_base_plane_from_the_sensor_z_field ( panel ) :
def test_the_panel_drives_the_base_plane_from_the_sensor_z_field ( panel ) :
@@ -845,21 +794,18 @@ def test_the_panel_drives_the_base_plane_from_the_sensor_z_field(panel):
# ------------------------------------------------------------- the tool path
# ------------------------------------------------------------- the tool path
def test_the_planned_path_starts_at_the_contact_and_ends_on_the_tool ( qt_app , bundle ) :
def test_the_planned_path_starts_at_the_contact_and_ends_on_the_tool ( bundle , preview ) :
""" The two ends of the trajectory are the two things it claims.
""" The two ends of the trajectory are the two things it claims.
A path that does not start where the point is, or does not finish on the tool
A path that does not start where the point is, or does not finish on the tool
axis, is showing a motion the machine would not perform — which is worse than
axis, is showing a motion the machine would not perform — which is worse than
showing nothing, because it looks checkable.
showing nothing, because it looks checkable.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
axis = to_sensor ( stack . tool_axis_direction , stack . axis_map )
axis = to_sensor ( stack . tool_axis_direction , stack . axis_map )
axis = axis / np . linalg . norm ( axis )
axis = axis / np . linalg . norm ( axis )
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
for index in ( 0 , 7 , 40 , 200 ) :
for index in ( 0 , 7 , 40 , 200 ) :
point , normal = bundle . vertices_mm [ index ] , bundle . vertex_normals ( ) [ index ]
point , normal = bundle . vertices_mm [ index ] , bundle . vertex_normals ( ) [ index ]
@@ -878,11 +824,9 @@ def test_the_planned_path_starts_at_the_contact_and_ends_on_the_tool(qt_app, bun
# Continuous: no step larger than the body, or it is not a path.
# Continuous: no step larger than the body, or it is not a path.
steps = np . linalg . norm ( np . diff ( path , axis = 0 ) , axis = 1 )
steps = np . linalg . norm ( np . diff ( path , axis = 0 ) , axis = 1 )
assert float ( steps . max ( ) ) < float ( np . linalg . norm ( bundle . extent_mm ) )
assert float ( steps . max ( ) ) < float ( np . linalg . norm ( bundle . extent_mm ) )
finally :
preview . deleteLater ( )
def test_the_path_ends_where_the_pose_says_the_table_moves_to ( qt_app , bundle ) :
def test_the_path_ends_where_the_pose_says_the_table_moves_to ( bundle , preview ) :
""" The path, the animation and the reported move are one claim, checked once.
""" The path, the animation and the reported move are one claim, checked once.
The contact ' s last drawn position must be the tool, which is the tool ' s own XY —
The contact ' s last drawn position must be the tool, which is the tool ' s own XY —
@@ -890,15 +834,12 @@ def test_the_path_ends_where_the_pose_says_the_table_moves_to(qt_app, bundle):
somewhere near it. This is the check that a wrong tool position or a missing
somewhere near it. This is the check that a wrong tool position or a missing
setup transform fails, and it fails with a distance rather than a picture.
setup transform fails, and it fails with a distance rather than a picture.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack (
stack = PlatformStack (
tool_xy_mm = ( 6.0 , - 3.0 ) ,
tool_xy_mm = ( 6.0 , - 3.0 ) ,
sensor_offset_mm = ( 1.5 , - 2.0 , 4.0 ) ,
sensor_offset_mm = ( 1.5 , - 2.0 , 4.0 ) ,
sensor_yaw_deg = 7.5 ,
sensor_yaw_deg = 7.5 ,
)
)
preview = PosePreview ( )
try :
preview . set_stack ( stack )
preview . set_stack ( stack )
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
preview . set_setup ( stack . sensor_offset_mm , stack . sensor_yaw_deg )
preview . set_setup ( stack . sensor_offset_mm , stack . sensor_yaw_deg )
@@ -921,23 +862,17 @@ def test_the_path_ends_where_the_pose_says_the_table_moves_to(qt_app, bundle):
moved = stack . mount @ np . asarray ( centre , dtype = float )
moved = stack . mount @ np . asarray ( centre , dtype = float )
assert moved [ 0 ] == pytest . approx ( pose . table_xy_mm [ 0 ] , abs = 1e-6 )
assert moved [ 0 ] == pytest . approx ( pose . table_xy_mm [ 0 ] , abs = 1e-6 )
assert moved [ 1 ] == pytest . approx ( pose . table_xy_mm [ 1 ] , abs = 1e-6 )
assert moved [ 1 ] == pytest . approx ( pose . table_xy_mm [ 1 ] , abs = 1e-6 )
finally :
preview . stop_simulation ( )
preview . deleteLater ( )
def test_the_table_marker_shows_the_move_against_the_fixed_grid ( qt_app , bundle ) :
def test_the_table_marker_shows_the_move_against_the_fixed_grid ( bundle , preview ) :
""" Item three of the fidelity request: what the machine did, versus where it started.
""" Item three of the fidelity request: what the machine did, versus where it started.
The grid is the initial state and never moves. The marker is the table ' s own
The grid is the initial state and never moves. The marker is the table ' s own
frame: at the start of the animation it lies exactly on the grid, and by the end
frame: at the start of the animation it lies exactly on the grid, and by the end
it stands at the solved rotation and the solved XY.
it stands at the solved rotation and the solved XY.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
preview = PosePreview ( )
try :
preview . set_stack ( stack )
preview . set_stack ( stack )
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
point , normal = bundle . vertices_mm [ 40 ] , bundle . vertex_normals ( ) [ 40 ]
point , normal = bundle . vertices_mm [ 40 ] , bundle . vertex_normals ( ) [ 40 ]
@@ -962,17 +897,12 @@ def test_the_table_marker_shows_the_move_against_the_fixed_grid(qt_app, bundle):
moved = stack . mount @ np . asarray ( translation , dtype = float )
moved = stack . mount @ np . asarray ( translation , dtype = float )
assert moved [ 0 ] == pytest . approx ( pose . table_xy_mm [ 0 ] , abs = 1e-9 )
assert moved [ 0 ] == pytest . approx ( pose . table_xy_mm [ 0 ] , abs = 1e-9 )
assert moved [ 1 ] == pytest . approx ( pose . table_xy_mm [ 1 ] , abs = 1e-9 )
assert moved [ 1 ] == pytest . approx ( pose . table_xy_mm [ 1 ] , abs = 1e-9 )
finally :
preview . deleteLater ( )
def test_the_path_is_a_rotation_arc_then_a_straight_move ( qt_app , bundle ) :
def test_the_path_is_a_rotation_arc_then_a_straight_move ( bundle , preview ) :
""" Two phases, matching the two things the machine has. """
""" Two phases, matching the two things the machine has. """
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
point , normal = bundle . vertices_mm [ 40 ] , bundle . vertex_normals ( ) [ 40 ]
point , normal = bundle . vertices_mm [ 40 ] , bundle . vertex_normals ( ) [ 40 ]
preview . set_point ( point , normal )
preview . set_point ( point , normal )
@@ -994,17 +924,12 @@ def test_the_path_is_a_rotation_arc_then_a_straight_move(qt_app, bundle):
directions = segments [ keep ] / lengths [ keep ] [ : , None ]
directions = segments [ keep ] / lengths [ keep ] [ : , None ]
for first , second in zip ( directions , directions [ 1 : ] ) :
for first , second in zip ( directions , directions [ 1 : ] ) :
assert float ( first @ second ) > 0.99 , " the table move is not straight "
assert float ( first @ second ) > 0.99 , " the table move is not straight "
finally :
preview . deleteLater ( )
def test_the_path_summary_reports_what_judges_validity ( qt_app , bundle ) :
def test_the_path_summary_reports_what_judges_validity ( bundle , preview ) :
""" The numbers, not just the line: reach and arc radius. """
""" The numbers, not just the line: reach and arc radius. """
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
assert preview . trajectory_summary ( ) == { }
assert preview . trajectory_summary ( ) == { }
point , normal = bundle . vertices_mm [ 0 ] , bundle . vertex_normals ( ) [ 0 ]
point , normal = bundle . vertices_mm [ 0 ] , bundle . vertex_normals ( ) [ 0 ]
@@ -1018,33 +943,23 @@ def test_the_path_summary_reports_what_judges_validity(qt_app, bundle):
float ( np . linalg . norm ( point - origin ) )
float ( np . linalg . norm ( point - origin ) )
)
)
assert summary [ " final_offset_mm " ] == pytest . approx ( 0.0 , abs = 1e-6 )
assert summary [ " final_offset_mm " ] == pytest . approx ( 0.0 , abs = 1e-6 )
finally :
preview . deleteLater ( )
def test_the_path_can_be_hidden ( qt_app , bundle ) :
def test_the_path_can_be_hidden ( bundle , preview ) :
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
assert preview . show_trajectory
assert preview . show_trajectory
preview . set_show_trajectory ( False )
preview . set_show_trajectory ( False )
assert not preview . show_trajectory
assert not preview . show_trajectory
finally :
preview . deleteLater ( )
def test_the_origin_plane_and_the_base_plane_are_separate_surfaces ( qt_app , bundle ) :
def test_the_origin_plane_and_the_base_plane_are_separate_surfaces ( bundle , preview ) :
""" They coincide only when the mounting height is zero; the frame must say so.
""" They coincide only when the mounting height is zero; the frame must say so.
With nothing under the sensor there is one surface, and drawing it twice is what
With nothing under the sensor there is one surface, and drawing it twice is what
made the view look cluttered; lifted, they are genuinely two, separated along
made the view look cluttered; lifted, they are genuinely two, separated along
the normal.
the normal.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
preview . set_setup ( ( 0.0 , 0.0 , 0.0 ) , 0.0 )
preview . set_setup ( ( 0.0 , 0.0 , 0.0 ) , 0.0 )
frame = preview . table_frame ( )
frame = preview . table_frame ( )
@@ -1055,16 +970,11 @@ def test_the_origin_plane_and_the_base_plane_are_separate_surfaces(qt_app, bundl
gap = frame [ " base_point " ] - frame [ " origin " ]
gap = frame [ " base_point " ] - frame [ " origin " ]
assert float ( gap @ frame [ " plane_normal " ] ) == pytest . approx ( 3.0 )
assert float ( gap @ frame [ " plane_normal " ] ) == pytest . approx ( 3.0 )
assert not np . allclose ( frame [ " origin " ] , frame [ " base_point " ] )
assert not np . allclose ( frame [ " origin " ] , frame [ " base_point " ] )
finally :
preview . deleteLater ( )
def test_the_table_triad_basis_is_well_defined ( qt_app , bundle ) :
def test_the_table_triad_basis_is_well_defined ( bundle , preview ) :
""" A degenerate cross product would orient the plane arbitrarily. """
""" A degenerate cross product would orient the plane arbitrarily. """
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
frame = preview . table_frame ( )
frame = preview . table_frame ( )
normal , first , second = frame [ " plane_normal " ] , frame [ " first " ] , frame [ " second " ]
normal , first , second = frame [ " plane_normal " ] , frame [ " first " ] , frame [ " second " ]
@@ -1073,8 +983,6 @@ def test_the_table_triad_basis_is_well_defined(qt_app, bundle):
assert float ( first @ normal ) == pytest . approx ( 0.0 , abs = 1e-12 )
assert float ( first @ normal ) == pytest . approx ( 0.0 , abs = 1e-12 )
assert float ( second @ normal ) == pytest . approx ( 0.0 , abs = 1e-12 )
assert float ( second @ normal ) == pytest . approx ( 0.0 , abs = 1e-12 )
assert float ( first @ second ) == pytest . approx ( 0.0 , abs = 1e-12 )
assert float ( first @ second ) == pytest . approx ( 0.0 , abs = 1e-12 )
finally :
preview . deleteLater ( )
def test_a_pick_returns_the_row_it_also_shows ( panel ) :
def test_a_pick_returns_the_row_it_also_shows ( panel ) :
@@ -1184,13 +1092,14 @@ def test_the_sensor_z_offset_reaches_the_solve(panel):
assert thick_row [ " Y " ] == pytest . approx ( thin_row [ " Y " ] )
assert thick_row [ " Y " ] == pytest . approx ( thin_row [ " Y " ] )
def test_the_mounting_pose_moves_the_point_the_way_the_setup_says ( panel ) :
def test_the_mounting_pose_moves_the_point_the_way_the_setup_says ( native_panel ) :
""" x, y and yaw are a mounting pose, so they move the contact, not the reading.
""" x, y and yaw are a mounting pose, so they move the contact, not the reading.
A sensor that is not square on the stack puts the same sensor-frame point at a
A sensor that is not square on the stack puts the same sensor-frame point at a
different place on the table — which is exactly what has to be taken out before
different place on the table — which is exactly what has to be taken out before
the reported coordinates mean anything on the bench.
the reported coordinates mean anything on the bench.
"""
"""
panel = native_panel
flat = panel . view . flat
flat = panel . view . flat
x , y = _first_surface_pixel ( flat )
x , y = _first_surface_pixel ( flat )
point , normal , _face = flat . sample ( x , y )
point , normal , _face = flat . sample ( x , y )
@@ -1217,7 +1126,15 @@ def test_the_mounting_pose_moves_the_point_the_way_the_setup_says(panel):
assert float ( np . hypot ( * turned . target_xy_mm ) ) == pytest . approx ( radius , rel = 1e-9 )
assert float ( np . hypot ( * turned . target_xy_mm ) ) == pytest . approx ( radius , rel = 1e-9 )
assert turned . contact_z_mm == pytest . approx ( base . contact_z_mm )
assert turned . contact_z_mm == pytest . approx ( base . contact_z_mm )
assert turned . yaw_deg != pytest . approx ( base . yaw_deg )
assert turned . yaw_deg != pytest . approx ( base . yaw_deg )
assert turned . pitch_deg == pytest . approx ( base . pitch_deg )
# The shorter equivalent branch can change pitch's sign. Compare the full
# rotation, including the changed mounting yaw, against the original pose.
relative = ( panel . stack . platform_rotation ( turned . yaw_deg , turned . pitch_deg )
@ rotation_about ( " z " , 30.0 )
@ panel . stack . platform_rotation ( base . yaw_deg , base . pitch_deg ) . T )
assert ( np . allclose ( relative , np . eye ( 3 ) , atol = 1e-9 )
or np . allclose ( relative , rotation_about ( " z " , 180.0 ) , atol = 1e-9 ) )
assert np . allclose ( panel . stack . platform_rotation ( turned . yaw_deg , turned . pitch_deg )
@ panel . stack . setup_normal ( normal ) , panel . stack . tool_direction , atol = 1e-9 )
def test_the_tool_position_sets_the_move_and_not_the_reading ( panel ) :
def test_the_tool_position_sets_the_move_and_not_the_reading ( panel ) :
@@ -1306,11 +1223,8 @@ def test_the_press_readout_says_why_it_is_held_and_then_names_the_target(panel):
assert targets [ " z " ] == pytest . approx ( 2.0 - height )
assert targets [ " z " ] == pytest . approx ( 2.0 - height )
def test_the_preview_refuses_to_draw_without_a_context ( qt_app ) :
def test_the_preview_refuses_to_draw_without_a_context ( preview ) :
from orisys_autocal . ui . pose_preview import PosePreview
preview = PosePreview ( )
try :
assert not preview . has_context ( )
assert not preview . has_context ( )
# Rendering must be a no-op, not a crash: Qt calls this during teardown
# Rendering must be a no-op, not a crash: Qt calls this during teardown
# on some platforms.
# on some platforms.
@@ -1319,8 +1233,6 @@ def test_the_preview_refuses_to_draw_without_a_context(qt_app):
# leaving a black rectangle unexplained.
# leaving a black rectangle unexplained.
preview . set_bundle ( None )
preview . set_bundle ( None )
assert preview . problem
assert preview . problem
finally :
preview . deleteLater ( )
def test_the_panel_shows_why_the_preview_is_blank ( qt_app ) :
def test_the_panel_shows_why_the_preview_is_blank ( qt_app ) :
@@ -1378,7 +1290,7 @@ def test_every_palette_colour_can_be_set_on_a_strict_gl(qt_app):
# --------------------------------------------- the camera frames the whole scene
# --------------------------------------------- the camera frames the whole scene
def test_the_whole_scene_fits_the_widget_at_the_default_zoom ( qt_app , bundle ) :
def test_the_whole_scene_fits_the_widget_at_the_default_zoom ( bundle , preview ) :
""" Every point the framing counts on must land inside the pane.
""" Every point the framing counts on must land inside the pane.
Framed on the mesh alone — half its bounding-box diagonal, times three — the
Framed on the mesh alone — half its bounding-box diagonal, times three — the
@@ -1386,11 +1298,8 @@ def test_the_whole_scene_fits_the_widget_at_the_default_zoom(qt_app, bundle):
two things that say whether a pose is reachable. A pose whose parts leave the
two things that say whether a pose is reachable. A pose whose parts leave the
picture looks like a pose that failed.
picture looks like a pose that failed.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
width , height = 900 , 640
width , height = 900 , 640
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
preview . resize ( width , height )
preview . resize ( width , height )
points = preview . scene_points ( )
points = preview . scene_points ( )
@@ -1400,11 +1309,9 @@ def test_the_whole_scene_fits_the_widget_at_the_default_zoom(qt_app, bundle):
assert screen is not None , " a scene point projected behind the camera "
assert screen is not None , " a scene point projected behind the camera "
assert - 1.0 < = screen [ 0 ] < = width + 1.0 , f " x= { screen [ 0 ] : .1f } outside 0.. { width } "
assert - 1.0 < = screen [ 0 ] < = width + 1.0 , f " x= { screen [ 0 ] : .1f } outside 0.. { width } "
assert - 1.0 < = screen [ 1 ] < = height + 1.0 , f " y= { screen [ 1 ] : .1f } outside 0.. { height } "
assert - 1.0 < = screen [ 1 ] < = height + 1.0 , f " y= { screen [ 1 ] : .1f } outside 0.. { height } "
finally :
preview . deleteLater ( )
def test_the_resting_view_shows_the_solved_end_state ( qt_app , bundle ) :
def test_the_resting_view_shows_the_solved_end_state ( bundle , preview ) :
""" At rest the screen answers " where does it end up " without an animation.
""" At rest the screen answers " where does it end up " without an animation.
This is a solve view: the operator picks a point and needs to see whether the
This is a solve view: the operator picks a point and needs to see whether the
@@ -1412,11 +1319,8 @@ def test_the_resting_view_shows_the_solved_end_state(qt_app, bundle):
the table marker all stand at the end of the move before anything is played, and
the table marker all stand at the end of the move before anything is played, and
the fixed grid is the frame they started in.
the fixed grid is the frame they started in.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( tool_xy_mm = ( 5.0 , - 2.0 ) )
stack = PlatformStack ( tool_xy_mm = ( 5.0 , - 2.0 ) )
preview = PosePreview ( )
try :
preview . set_stack ( stack )
preview . set_stack ( stack )
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
preview . set_tool_xy ( * stack . tool_xy_mm )
preview . set_tool_xy ( * stack . tool_xy_mm )
@@ -1439,17 +1343,12 @@ def test_the_resting_view_shows_the_solved_end_state(qt_app, bundle):
start = preview . initial_marker_points ( )
start = preview . initial_marker_points ( )
assert np . allclose ( start . mean ( axis = 0 ) , [ 0.0 , 0.0 , 0.0 ] , atol = 1e-9 )
assert np . allclose ( start . mean ( axis = 0 ) , [ 0.0 , 0.0 , 0.0 ] , atol = 1e-9 )
assert not np . allclose ( start , preview . table_marker_points ( ) , atol = 1e-6 )
assert not np . allclose ( start , preview . table_marker_points ( ) , atol = 1e-6 )
finally :
preview . deleteLater ( )
def test_playing_from_the_resting_state_moves_the_body ( qt_app , bundle ) :
def test_playing_from_the_resting_state_moves_the_body ( bundle , preview ) :
""" Play runs the move the resting picture already shows the end of. """
""" Play runs the move the resting picture already shows the end of. """
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
index = 40
index = 40
point , normal = bundle . vertices_mm [ index ] , bundle . vertex_normals ( ) [ index ]
point , normal = bundle . vertices_mm [ index ] , bundle . vertex_normals ( ) [ index ]
@@ -1465,27 +1364,21 @@ def test_playing_from_the_resting_state_moves_the_body(qt_app, bundle):
for _ in range ( 200 ) :
for _ in range ( 200 ) :
preview . _sim_tick ( ) # noqa: SLF001
preview . _sim_tick ( ) # noqa: SLF001
assert np . allclose ( preview . _placed_contact ( point ) , resting , atol = 1e-6 ) # noqa: SLF001
assert np . allclose ( preview . _placed_contact ( point ) , resting , atol = 1e-6 ) # noqa: SLF001
finally :
preview . stop_simulation ( )
preview . deleteLater ( )
def test_the_landing_check_agrees_with_the_tool ( qt_app , bundle ) :
def test_the_landing_check_agrees_with_the_tool ( bundle , preview ) :
""" The x-y claim, measured rather than judged off a picture.
""" The x-y claim, measured rather than judged off a picture.
``landing_xy_mm`` pushes the contact through the *drawing ' s* transform; the tool ' s
``landing_xy_mm`` pushes the contact through the *drawing ' s* transform; the tool ' s
X/Y is a setup number. They are computed two different ways and must agree — this
X/Y is a setup number. They are computed two different ways and must agree — this
is the check the bench asks for when it says " x and y are not aligning " .
is the check the bench asks for when it says " x and y are not aligning " .
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
setup = PlatformStack (
setup = PlatformStack (
sensor_offset_mm = ( 6.0 , - 3.0 , 5.0 ) ,
sensor_offset_mm = ( 6.0 , - 3.0 , 5.0 ) ,
sensor_yaw_deg = 12.0 ,
sensor_yaw_deg = 12.0 ,
tool_xy_mm = ( 7.0 , 4.0 ) ,
tool_xy_mm = ( 7.0 , 4.0 ) ,
)
)
preview = PosePreview ( )
try :
preview . set_stack ( setup )
preview . set_stack ( setup )
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
preview . set_setup ( setup . sensor_offset_mm , setup . sensor_yaw_deg )
preview . set_setup ( setup . sensor_offset_mm , setup . sensor_yaw_deg )
@@ -1502,8 +1395,6 @@ def test_the_landing_check_agrees_with_the_tool(qt_app, bundle):
assert landing is not None
assert landing is not None
worst = max ( worst , float ( np . hypot ( landing [ 0 ] - 7.0 , landing [ 1 ] - 4.0 ) ) )
worst = max ( worst , float ( np . hypot ( landing [ 0 ] - 7.0 , landing [ 1 ] - 4.0 ) ) )
assert worst < 1e-9 , f " the drawing lands { worst * 1000 : .3f } um off the tool "
assert worst < 1e-9 , f " the drawing lands { worst * 1000 : .3f } um off the tool "
finally :
preview . deleteLater ( )
def test_the_landing_line_reports_the_check ( panel ) :
def test_the_landing_line_reports_the_check ( panel ) :
@@ -1528,7 +1419,7 @@ def test_the_landing_line_says_so_when_they_disagree(panel):
assert " 0.250 " in panel . path_label . text ( )
assert " 0.250 " in panel . path_label . text ( )
def test_the_framing_follows_the_planned_path ( qt_app , bundle ) :
def test_the_framing_follows_the_planned_path ( bundle , preview ) :
""" A long move has to fit in the picture, or the move leaves the frame.
""" A long move has to fit in the picture, or the move leaves the frame.
The body at rest stands at the *end* of the move, so the far end of the picture
The body at rest stands at the *end* of the move, so the far end of the picture
@@ -1536,11 +1427,8 @@ def test_the_framing_follows_the_planned_path(qt_app, bundle):
points, which is what this checks. (It used to be enough to compare the radius
points, which is what this checks. (It used to be enough to compare the radius
with and without a pose, because the body itself was at the far end.)
with and without a pose, because the body itself was at the far end.)
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
preview . resize ( 900 , 640 )
preview . resize ( 900 , 640 )
@@ -1559,25 +1447,20 @@ def test_the_framing_follows_the_planned_path(qt_app, bundle):
start = np . linalg . norm ( path [ 0 ] - preview . _focus ( ) ) # noqa: SLF001
start = np . linalg . norm ( path [ 0 ] - preview . _focus ( ) ) # noqa: SLF001
assert radius > = start - 1e-9
assert radius > = start - 1e-9
assert start > 0.0
assert start > 0.0
finally :
preview . deleteLater ( )
# ------------------------------------ the animation and the drawn path agree
# ------------------------------------ the animation and the drawn path agree
def test_the_drawn_path_ends_where_the_animation_puts_the_contact ( qt_app , bundle ) :
def test_the_drawn_path_ends_where_the_animation_puts_the_contact ( bundle , preview ) :
""" Two descriptions of one move, so they cannot disagree.
""" Two descriptions of one move, so they cannot disagree.
The path was sampled up to ``SIM_ROTATION_SHARE`` of the solved rotation — the
The path was sampled up to ``SIM_ROTATION_SHARE`` of the solved rotation — the
*time* that phase takes, not how far it turns — so the drawn arc stopped 45 %
*time* that phase takes, not how far it turns — so the drawn arc stopped 45 %
short and the straight move began from a point the machine never passes through.
short and the straight move began from a point the machine never passes through.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
index = 7
index = 7
point , normal = bundle . vertices_mm [ index ] , bundle . vertex_normals ( ) [ index ]
point , normal = bundle . vertices_mm [ index ] , bundle . vertex_normals ( ) [ index ]
@@ -1590,22 +1473,17 @@ def test_the_drawn_path_ends_where_the_animation_puts_the_contact(qt_app, bundle
_run_simulation ( preview )
_run_simulation ( preview )
landed = preview . _placed_contact ( point ) # noqa: SLF001
landed = preview . _placed_contact ( point ) # noqa: SLF001
assert np . allclose ( path [ - 1 ] , landed , atol = 1e-6 )
assert np . allclose ( path [ - 1 ] , landed , atol = 1e-6 )
finally :
preview . deleteLater ( )
def test_the_placement_covers_a_point_and_a_whole_vertex_array ( qt_app , bundle ) :
def test_the_placement_covers_a_point_and_a_whole_vertex_array ( bundle , preview ) :
""" ``_placed`` is handed both, and left-multiplying only worked for one.
""" ``_placed`` is handed both, and left-multiplying only worked for one.
The camera framing walks the whole mesh through the simulation every frame.
The camera framing walks the whole mesh through the simulation every frame.
``rotation @ vertices`` raised for an (N, 3) array, which — inside the paint
``rotation @ vertices`` raised for an (N, 3) array, which — inside the paint
handler — blanked the view the moment playback started.
handler — blanked the view the moment playback started.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
index = 40
index = 40
point , normal = bundle . vertices_mm [ index ] , bundle . vertex_normals ( ) [ index ]
point , normal = bundle . vertices_mm [ index ] , bundle . vertex_normals ( ) [ index ]
@@ -1618,11 +1496,9 @@ def test_the_placement_covers_a_point_and_a_whole_vertex_array(qt_app, bundle):
for index in ( 0 , 5 , 200 , bundle . vertices_mm . shape [ 0 ] - 1 ) :
for index in ( 0 , 5 , 200 , bundle . vertices_mm . shape [ 0 ] - 1 ) :
single = preview . _placed ( bundle . vertices_mm [ index ] ) # noqa: SLF001
single = preview . _placed ( bundle . vertices_mm [ index ] ) # noqa: SLF001
assert np . allclose ( placed [ index ] , single )
assert np . allclose ( placed [ index ] , single )
finally :
preview . deleteLater ( )
def test_the_surface_keeps_its_residual_colours_while_the_machine_moves ( qt_app , bundle ) :
def test_the_surface_keeps_its_residual_colours_while_the_machine_moves ( bundle , preview ) :
""" The shading is the measurement, so it stays on through playback.
""" The shading is the measurement, so it stays on through playback.
It used to switch to a flat " moving body " grey, which threw away the one thing
It used to switch to a flat " moving body " grey, which threw away the one thing
@@ -1630,11 +1506,8 @@ def test_the_surface_keeps_its_residual_colours_while_the_machine_moves(qt_app,
tool axis. The body itself *does* move — from the initial state when playback
tool axis. The body itself *does* move — from the initial state when playback
starts to the solved state it rests at — and that is checked here too.
starts to the solved state it rests at — and that is checked here too.
"""
"""
from orisys_autocal . ui . pose_preview import PosePreview
stack = PlatformStack ( )
stack = PlatformStack ( )
preview = PosePreview ( )
try :
preview . set_bundle ( bundle )
preview . set_bundle ( bundle )
index = 40
index = 40
point , normal = bundle . vertices_mm [ index ] , bundle . vertex_normals ( ) [ index ]
point , normal = bundle . vertices_mm [ index ] , bundle . vertex_normals ( ) [ index ]
@@ -1670,8 +1543,6 @@ def test_the_surface_keeps_its_residual_colours_while_the_machine_moves(qt_app,
from orisys_autocal . ui . pose_preview import residual_colour
from orisys_autocal . ui . pose_preview import residual_colour
assert np . allclose ( moved [ index ] , residual_colour ( preview . _residuals [ index ] ) ) # noqa: SLF001
assert np . allclose ( moved [ index ] , residual_colour ( preview . _residuals [ index ] ) ) # noqa: SLF001
finally :
preview . deleteLater ( )
def test_reapplying_the_current_theme_does_not_repolish_all_windows ( make_window , qt_app , monkeypatch ) :
def test_reapplying_the_current_theme_does_not_repolish_all_windows ( make_window , qt_app , monkeypatch ) :
window = make_window ( )
window = make_window ( )
calls = [ ]
calls = [ ]