Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transform testing and seed bugfix #74

Merged
merged 36 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
799237d
a first draft of a simple test
sfmig Jun 5, 2023
e236018
Started building out tests
harveymannering Sep 11, 2023
4f0e106
Added pytest pip installs to python install file
harveymannering Sep 11, 2023
a964ff0
Commenting tests
harveymannering Sep 12, 2023
f85fae9
Transforms and seed tests
harveymannering Sep 14, 2023
8bd2546
Fix linting
ruaridhg Sep 14, 2023
9a6e327
Fix linting
ruaridhg Sep 14, 2023
cef3bdd
Fix linting
ruaridhg Sep 14, 2023
763d426
Fix pytest bpy not found error
ruaridhg Sep 14, 2023
657db14
Pre-commit linting changes
harveymannering Sep 15, 2023
da738be
Fix pytest bpy not found error
ruaridhg Sep 15, 2023
c6a9b9e
Fix pytest bpy not found error
ruaridhg Sep 15, 2023
2b3ae9d
Trying to make github action work for pytest
harveymannering Sep 15, 2023
fdc2b2c
Testing removal of pytest fixture
harveymannering Sep 15, 2023
88d565a
Merge branch 'smg/basic-test' of https://github.com/UCL/Blender_Rando…
harveymannering Sep 15, 2023
2507fff
Linting
harveymannering Sep 15, 2023
019b0da
Trying out a different CI file
harveymannering Sep 15, 2023
f58ca42
Update test.yml
harveymannering Sep 15, 2023
49a7a6c
Update test.yml
harveymannering Sep 15, 2023
24cfc02
Create _blender-executable-path.txt
harveymannering Sep 15, 2023
26df644
Fixed linting issues
harveymannering Sep 15, 2023
295ba38
Update test.yml
harveymannering Sep 15, 2023
92e0aa0
Constructing a scene for the geometry testing
harveymannering Sep 18, 2023
e5d5a9b
Merge branch 'smg/basic-test' of https://github.com/UCL/Blender_Rando…
harveymannering Sep 18, 2023
b3f0230
Fixing up the test_randomiser_geometry test
harveymannering Sep 20, 2023
cfdf619
Removed unneeded seed code
harveymannering Sep 20, 2023
2acda15
Removed unnecessary pip installs
harveymannering Sep 20, 2023
1eb9c5d
Revert "Removed unneeded seed code"
harveymannering Sep 20, 2023
30b246a
Rolled back seed change and added geometry test
harveymannering Sep 21, 2023
f1b6375
Changed geometry code to work with testing
harveymannering Sep 21, 2023
9316e1a
Adding back setup code for geometry test
harveymannering Sep 21, 2023
491181b
Rolling back geometry testing
harveymannering Sep 21, 2023
99ab144
Reverting test.yml
harveymannering Sep 21, 2023
3e5329b
Removed _blender-executable-path.txt
harveymannering Sep 21, 2023
2d1add1
Removed debugging code
harveymannering Sep 21, 2023
50e9c65
Filling out the docstrings for tests
harveymannering Sep 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dev = [
"pre-commit",
"ruff",
"setuptools_scm",
"pytest-blender",
]

[build-system]
Expand All @@ -52,7 +53,10 @@ include = ["blender_randomiser*"]
exclude = ["tests*"]

[tool.pytest.ini_options]
addopts = "--cov=blender_randomiser"
pythonpath = "randomiser"
addopts = "--cov=randomiser"
pytest-blender-debug = true
blender-addons-cleaning = "uninstall"

[tool.black]
target-version = ['py310']
Expand Down
2 changes: 1 addition & 1 deletion randomiser/geometry/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ def invoke(self, context, event):
self.list_subpanel_gng_names = [
gng.name for gng in cs.socket_props_per_gng.collection
]

# for every GNG: save sockets to randomise
self.sockets_to_randomise_per_gng = {}
for gng_str in self.list_subpanel_gng_names:
Expand Down Expand Up @@ -150,6 +149,7 @@ def execute(self, context):
_type_
_description_
"""

cs = context.scene

# For every GNG with a subpanel
Expand Down
5 changes: 5 additions & 0 deletions randomiser/seed/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class PropertiesRandomSeed(bpy.types.PropertyGroup):
seed_prop = bpy.props.IntProperty(name="Seed", default=42)
seed: seed_prop # type: ignore

seed_previous_prop = bpy.props.IntProperty(
name="Seed Previous", default=42
)
seed_previous: seed_previous_prop # type: ignore


# ------------------------------------
# Register / unregister classes
Expand Down
11 changes: 9 additions & 2 deletions randomiser/transform/operators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from random import uniform
from random import seed, uniform

import bpy
import numpy as np
Expand Down Expand Up @@ -50,6 +50,7 @@ def execute(self, context):
_type_
_description_
"""

loc = context.scene.randomise_camera_props.camera_pos
rot = context.scene.randomise_camera_props.camera_rot

Expand Down Expand Up @@ -86,6 +87,13 @@ def execute(self, context):
rand_roty = context.scene.randomise_camera_props.bool_rand_roty
rand_rotz = context.scene.randomise_camera_props.bool_rand_rotz

previous_seed = context.scene.seed_properties.seed_previous
current_seed = context.scene.seed_properties.seed
seed_enabled = context.scene.seed_properties.seed_toggle
if (previous_seed != current_seed) and (seed_enabled is True):
seed(current_seed)
context.scene.seed_properties.seed_previous = current_seed

randomise_selected(
context,
loc,
Expand All @@ -111,7 +119,6 @@ def execute(self, context):
@persistent
def randomise_camera_transform_per_frame(dummy):
bpy.ops.camera.apply_random_transform("INVOKE_DEFAULT")

return


Expand Down
266 changes: 266 additions & 0 deletions tests/test_integration/test_installing_and_enabling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
# This repo seems very useful for testing
# with pytest: https://github.com/mondeja/pytest-blender#pytest-blender
import subprocess
from pathlib import Path

import numpy as np
import pytest

try:
import bpy
except ImportError:
pytest.skip("bpy not available", allow_module_level=True)


# TODO:
# - make "randomiser" a fixture?
# - make sample.blend a fixture? keep it in test_data/?
def test_install_and_enable_1(
blender_executable, # pytest-blender fixture
blender_addons_dir, # pytest-blender fixture
):
list_return_codes = []
list_stderr = []

# zip randomiser folder
zip_result = subprocess.run(
["zip", "randomiser.zip", "-FS", "-r", "randomiser/"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
list_return_codes.append(zip_result.returncode)
list_stderr.append(zip_result.stderr)

# ----------------
# Option 2:
# launch blender with factory settings and sample blendfile
blender_result = subprocess.run(
[
blender_executable,
"--background",
"--factory-startup",
]
)
list_return_codes.append(blender_result.returncode)
list_stderr.append(blender_result.stderr)

bpy.ops.wm.open_mainfile(filepath="sample.blend")

# install and enable randomiser addon
bpy.ops.preferences.addon_install(filepath="./randomiser.zip")
bpy.ops.preferences.addon_enable(module=Path("randomiser.zip").stem)
# ----------------

# check addon is in expected folder
directory_exists = Path.exists(Path(blender_addons_dir) / "randomiser")

# TODO: maybe better practice to have all these in separate tests?
assert (
(all([x == 0 for x in list_return_codes]))
and (all([x in ["", None] for x in list_stderr]))
and (directory_exists)
)


def test_blend_file_loads():
"""
Test if the sample.blend file included in the project has been
properly loaded. A blank blender scene will contain 3 object by default.
"""
assert len(bpy.data.objects) == 4


#####################
## TRANSFORMS ###
#####################


def test_randomiser_position():
"""
Testing whether our randomizers generate poisitions
within a specified range (between 1 and 3).
"""

# Define range of values we randomise over
lower_bound = 1.0
upper_bound = 3.0

# set range for randomise in blender properties
bpy.data.scenes["Scene"].randomise_camera_props.camera_pos_x_max[
0
] = upper_bound
bpy.data.scenes["Scene"].randomise_camera_props.camera_pos_x_min[
0
] = lower_bound
bpy.data.scenes["Scene"].randomise_camera_props.camera_pos_y_max[
0
] = upper_bound
bpy.data.scenes["Scene"].randomise_camera_props.camera_pos_y_min[
0
] = lower_bound
bpy.data.scenes["Scene"].randomise_camera_props.camera_pos_z_max[
0
] = upper_bound
bpy.data.scenes["Scene"].randomise_camera_props.camera_pos_z_min[
0
] = lower_bound

# run a large number of randomisation and
# check they fall with the predefined range
total_random_test = 1000
for _ in range(total_random_test):
bpy.ops.camera.apply_random_transform("INVOKE_DEFAULT")
assert (
(bpy.data.objects["Camera"].location[0] >= lower_bound)
and (bpy.data.objects["Camera"].location[0] <= upper_bound)
and (bpy.data.objects["Camera"].location[1] >= lower_bound)
and (bpy.data.objects["Camera"].location[1] <= upper_bound)
and (bpy.data.objects["Camera"].location[2] >= lower_bound)
and (bpy.data.objects["Camera"].location[2] <= upper_bound)
)


def test_randomiser_rotation():
"""
Test our randomiser generates rotation angles
between a specified range (between 10° and 90°).
"""

# Define range of values we randomise over
lower_bound = 10.0
upper_bound = 90.0

# convert to radians
deg2rad = np.pi / 180
lower_bound_rad = lower_bound * deg2rad
upper_bound_rad = upper_bound * deg2rad

# set range for randomise in blender properties
bpy.data.scenes["Scene"].randomise_camera_props.camera_rot_x_max[
0
] = upper_bound
bpy.data.scenes["Scene"].randomise_camera_props.camera_rot_x_min[
0
] = lower_bound
bpy.data.scenes["Scene"].randomise_camera_props.camera_rot_y_max[
0
] = upper_bound
bpy.data.scenes["Scene"].randomise_camera_props.camera_rot_y_min[
0
] = lower_bound
bpy.data.scenes["Scene"].randomise_camera_props.camera_rot_z_max[
0
] = upper_bound
bpy.data.scenes["Scene"].randomise_camera_props.camera_rot_z_min[
0
] = lower_bound

# run a large number of randomisation
# and check they fall with the predefined range
total_random_test = 1000
for _ in range(total_random_test):
bpy.ops.camera.apply_random_transform("INVOKE_DEFAULT")
assert (
(bpy.data.objects["Camera"].rotation_euler[0] >= lower_bound_rad)
and (
bpy.data.objects["Camera"].rotation_euler[0] <= upper_bound_rad
)
and (
bpy.data.objects["Camera"].rotation_euler[1] >= lower_bound_rad
)
and (
bpy.data.objects["Camera"].rotation_euler[1] <= upper_bound_rad
)
and (
bpy.data.objects["Camera"].rotation_euler[2] >= lower_bound_rad
)
and (
bpy.data.objects["Camera"].rotation_euler[2] <= upper_bound_rad
)
)


def test_random_seed():
"""
Test whether changing the seed works by checking
random numbers are the same after setting the same seed.
"""

# Run randomisation 5 times and save some numbers
bpy.data.scenes["Scene"].seed_properties.seed_toggle = True
bpy.data.scenes["Scene"].seed_properties.seed = 1
sequence_length = 5
first_run = []
for _ in range(sequence_length):
bpy.ops.camera.apply_random_transform("INVOKE_DEFAULT")
first_run.append(bpy.data.objects["Camera"].location[0])

# Change the sure and ensure the randomised numbers are different
bpy.data.scenes["Scene"].seed_properties.seed = 2
for idx in range(sequence_length):
bpy.ops.camera.apply_random_transform("INVOKE_DEFAULT")
assert bpy.data.objects["Camera"].location[0] != first_run[idx]

# Check that this randomisation outputs
# the same numbers are the first for loop
bpy.data.scenes["Scene"].seed_properties.seed = 1
for idx in range(sequence_length):
bpy.ops.camera.apply_random_transform("INVOKE_DEFAULT")
assert bpy.data.objects["Camera"].location[0] == first_run[idx]


def test_bool_delta_position():
pass


def test_bool_delta_rotation():
pass


def test_per_frame():
"""
Test if we can replicate a sequence of
random numbers using the same seed when running an animation.
"""

# Record the first few x positions
# (randomly generated) in a sequence of frames
bpy.data.scenes["Scene"].seed_properties.seed_toggle = True
bpy.data.scenes["Scene"].seed_properties.seed = 3
bpy.data.scenes["Scene"].frame_current = 0
sequence_length = 5
first_run = []
for idx in range(sequence_length):
bpy.app.handlers.frame_change_pre[0]("dummy")
bpy.data.scenes["Scene"].frame_current = idx
first_run.append(bpy.data.objects["Camera"].location[0])

# Repeat the same sequence with a different seed,
# then ensure the numbers generated are different
bpy.data.scenes["Scene"].seed_properties.seed = 4
bpy.data.scenes["Scene"].frame_current = 0
for idx in range(sequence_length):
bpy.app.handlers.frame_change_pre[0]("dummy")
bpy.data.scenes["Scene"].frame_current = idx
assert first_run[idx] != bpy.data.objects["Camera"].location[0]

# Repeat sequence with original seed
# and check if the numbers generated are the same
bpy.data.scenes["Scene"].seed_properties.seed = 3
bpy.data.scenes["Scene"].frame_current = 0
for idx in range(sequence_length):
bpy.app.handlers.frame_change_pre[0]("dummy")
bpy.data.scenes["Scene"].frame_current = idx
assert first_run[idx] == bpy.data.objects["Camera"].location[0]


# modified from the pytest-blender docs
def test_install_and_enable_2(install_addons_from_dir, uninstall_addons):
# install and enable addons from directory
addons_ids = install_addons_from_dir(
".", addons_ids=["randomiser"], save_userpref=False
)

# uninstall them
uninstall_addons(addons_ids)
Loading