diff --git a/config/default.ini b/config/default.ini index f0aed6d4..997fefce 100755 --- a/config/default.ini +++ b/config/default.ini @@ -4,7 +4,7 @@ name: Scratch2Catrobat Converter short_name: S2CC version: 0.10.0 build_name: Aegean cat -build_number: 1023 +build_number: 1028 build_type: S2CC ;------------------------------------------------------------------------------- diff --git a/src/scratchtocatrobat/converter/converter.py b/src/scratchtocatrobat/converter/converter.py index e1643816..412e7a89 100644 --- a/src/scratchtocatrobat/converter/converter.py +++ b/src/scratchtocatrobat/converter/converter.py @@ -693,10 +693,7 @@ def add_cursor_sprite_to(self, catrobat_scene, upcoming_sprites): if self.scratch_project._has_mouse_position_script: position_script = catbase.StartScript() - forever_brick = catbricks.ForeverBrick() - forever_end = catbricks.LoopEndBrick(forever_brick) - forever_brick.setLoopEndBrick(forever_end) var_x_name = scratch.S2CC_POSITION_X_VARIABLE_NAME_PREFIX + MOUSE_SPRITE_NAME pos_x_uservariable = catformula.UserVariable(var_x_name) @@ -710,12 +707,12 @@ def add_cursor_sprite_to(self, catrobat_scene, upcoming_sprites): set_y_formula = catformula.Formula(catformula.FormulaElement(catElementType.SENSOR, "OBJECT_Y", None)) set_y_brick = catbricks.SetVariableBrick(set_y_formula, pos_y_uservariable) - catrobat_scene.getProject().projectVariables.add(pos_x_uservariable) - catrobat_scene.getProject().projectVariables.add(pos_y_uservariable) + catrobat_scene.getProject().userVariables.add(pos_x_uservariable) + catrobat_scene.getProject().userVariables.add(pos_y_uservariable) wait_brick = catbricks.WaitBrick(int(scratch.UPDATE_HELPER_VARIABLE_TIMEOUT * 1000)) - - position_script.brickList.addAll([forever_brick, set_x_brick, set_y_brick, wait_brick, forever_end]) + forever_brick.loopBricks.addAll([set_x_brick, set_y_brick, wait_brick]) + position_script.brickList.add(forever_brick) sprite.addScript(position_script) move_script = catbase.BroadcastScript("_mouse_move_") @@ -1404,7 +1401,6 @@ def write_program_source(catrobat_program, context): if sprite.name == MOUSE_SPRITE_NAME: mouse_img_path = _mouse_image_path() shutil.copyfile(mouse_img_path, os.path.join(images_path, _get_mouse_filename())) - break def download_automatic_screenshot_if_available(output_dir, scratch_project): if scratch_project.automatic_screenshot_image_url is None: diff --git a/src/scratchtocatrobat/converter/test_converter.py b/src/scratchtocatrobat/converter/test_converter.py index 4c8428bc..91c6023e 100644 --- a/src/scratchtocatrobat/converter/test_converter.py +++ b/src/scratchtocatrobat/converter/test_converter.py @@ -32,12 +32,9 @@ import org.catrobat.catroid.formulaeditor.FormulaElement.ElementType as catElementType import xml.etree.cElementTree as ET -from scratchtocatrobat.converter import catrobat -from scratchtocatrobat.tools import common -from scratchtocatrobat.tools import common_testing -from scratchtocatrobat.tools import svgtopng -from scratchtocatrobat.scratch import scratch -from scratchtocatrobat.converter import converter +from scratchtocatrobat.converter import catrobat, converter +from scratchtocatrobat.tools import common, common_testing, svgtopng +from scratchtocatrobat.scratch import scratch, scratch3 BACKGROUND_LOCALIZED_GERMAN_NAME = "Hintergrund" BACKGROUND_ORIGINAL_NAME = "Stage" @@ -2606,6 +2603,9 @@ def _load_test_scratch_project(self, project_name, force_download=False): is_local_project = True scratch_project_dir = common_testing.get_test_project_path(project_name) + isScratch3Project = scratch3.is_scratch3_project(scratch_project_dir) + if isScratch3Project: + scratch3.convert_to_scratch2_data(scratch_project_dir, 0) scratch_project = scratch.Project(scratch_project_dir, name=project_name, project_id=common_testing.PROJECT_DUMMY_ID, is_local_project=is_local_project) @@ -2620,6 +2620,62 @@ def _test_project(self, project_name): unused_scratch_resources=scratch_project.unused_resource_names) return converted_project.catrobat_program + def _test_mouse_pointer_tracking_workaround(self, catrobat_program): + scene = catrobat_program.getDefaultScene() + sprite_list = scene.getSpriteList() + + # check if the 'distanceTo'- or 'glideTo'-brick with the mouse-pointer as target + # was converted correctly -> workaround for this brick is realized during + # conversion and needs a scratch.Project() constructor to be tested -> testing this + # in a earlier phase not possible + mouse_sprite = sprite_list[-1] + assert mouse_sprite.name == "_mouse_" + + mouse_sprite_script_list = mouse_sprite.scriptList + assert len(mouse_sprite_script_list) == 4 + + position_tracking_script = mouse_sprite_script_list[0] + go_to_touch_position_script = mouse_sprite_script_list[1] + touch_screen_detection_script = mouse_sprite_script_list[2] + create_clone_at_touch_position_script = mouse_sprite_script_list[3] + + assert isinstance(position_tracking_script, catbase.StartScript) + assert isinstance(go_to_touch_position_script, catbase.BroadcastScript) + assert isinstance(touch_screen_detection_script, catbase.StartScript) + assert isinstance(create_clone_at_touch_position_script, catbase.WhenClonedScript) + + assert len(position_tracking_script.brickList) == 1 + assert len(go_to_touch_position_script.brickList) == 1 + assert len(touch_screen_detection_script.brickList) == 2 + assert len(create_clone_at_touch_position_script.brickList) == 3 + + forever_brick = position_tracking_script.brickList[0] + assert isinstance(forever_brick, catbricks.ForeverBrick) + assert len(forever_brick.loopBricks) == 3 + assert isinstance(forever_brick.loopBricks[0], catbricks.SetVariableBrick) + assert isinstance(forever_brick.loopBricks[1], catbricks.SetVariableBrick) + assert isinstance(forever_brick.loopBricks[2], catbricks.WaitBrick) + + go_to_brick = go_to_touch_position_script.brickList[0] + assert isinstance(go_to_brick, catbricks.GoToBrick) + assert go_to_brick.spinnerSelection == catcommon.BrickValues.GO_TO_TOUCH_POSITION + + set_transparency_brick = touch_screen_detection_script.brickList[0] + forever_brick = touch_screen_detection_script.brickList[1] + assert isinstance(set_transparency_brick, catbricks.SetTransparencyBrick) + assert isinstance(forever_brick, catbricks.ForeverBrick) + + go_to_brick = create_clone_at_touch_position_script.brickList[0] + if_then_logic_brick = create_clone_at_touch_position_script.brickList[1] + delete_this_clone_brick = create_clone_at_touch_position_script.brickList[2] + assert isinstance(go_to_brick, catbricks.GoToBrick) + assert go_to_brick.spinnerSelection == catcommon.BrickValues.GO_TO_TOUCH_POSITION + assert isinstance(if_then_logic_brick, catbricks.IfThenLogicBeginBrick) + assert len(if_then_logic_brick.ifBranchBricks) == 1 + broadcast_brick = if_then_logic_brick.ifBranchBricks[0] + assert isinstance(broadcast_brick, catbricks.BroadcastBrick) + assert isinstance(delete_this_clone_brick, catbricks.DeleteThisCloneBrick) + # Checks if the visible global or local variables in the scratch program are converted into show test bricks in the converted project def test_can_convert_visible_variables(self): scratch_project = self._load_test_scratch_project("visible_variables") @@ -2647,6 +2703,18 @@ def test_can_convert_visible_variables(self): if found_show_var: break assert found_show_var + # workaround for distanceTo-brick with the mouse-pointer as target + def test_can_convert_distance_to_brick_with_mouse_pointer_as_target(self): + catrobat_program = self._test_project("distance_to_mouse_pointer") + assert catrobat_program is not None + self._test_mouse_pointer_tracking_workaround(catrobat_program) + + # glideTo-brick workaround with mouse-pointer as target + def test_can_convert_glide_to_brick_with_mouse_pointer_as_target(self): + catrobat_program = self._test_project("glide_to_mouse_pointer") + assert catrobat_program is not None + self._test_mouse_pointer_tracking_workaround(catrobat_program) + # full_test_no_var def test_can_convert_project_without_variables(self): self._test_project("full_test_no_var") diff --git a/src/scratchtocatrobat/scratch/scratch.py b/src/scratchtocatrobat/scratch/scratch.py index 3a80eb4b..3fd503dd 100644 --- a/src/scratchtocatrobat/scratch/scratch.py +++ b/src/scratchtocatrobat/scratch/scratch.py @@ -187,7 +187,7 @@ def replace_key_pressed_blocks(block_list): return new_block_list for script in self.scripts: - if has_key_pressed_block(script.blocks): + if has_key_pressed_block(script.blocks): script.blocks = replace_key_pressed_blocks(script.blocks) workaround_info[ADD_KEY_PRESSED_SCRIPT_KEY] = key_pressed_keys # rebuild ScriptElement tree @@ -199,8 +199,8 @@ def replace_key_pressed_blocks(block_list): def has_distance_to_object_block(block_list, all_sprite_names): for block in block_list: if isinstance(block, list) \ - and ((block[0] == 'distanceTo:' and block[1] in (all_sprite_names) + ['_mouse_']) \ - or has_distance_to_object_block(block, all_sprite_names)): + and ((block[0] == 'distanceTo:' and block[1] in (all_sprite_names) + ['_mouse_']) \ + or has_distance_to_object_block(block, all_sprite_names)): return True return False @@ -213,14 +213,14 @@ def replace_distance_to_object_blocks(block_list, positions_needed_for_sprite_na # between both sprite objects new_block_list += [ ["computeFunction:of:", "sqrt", ["+", - ["*", - ["()", ["-", ["xpos"], ["readVariable", S2CC_POSITION_X_VARIABLE_NAME_PREFIX + block[1]]]], - ["()", ["-", ["xpos"], ["readVariable", S2CC_POSITION_X_VARIABLE_NAME_PREFIX + block[1]]]] - ], ["*", - ["()", ["-", ["ypos"], ["readVariable", S2CC_POSITION_Y_VARIABLE_NAME_PREFIX + block[1]]]], - ["()", ["-", ["ypos"], ["readVariable", S2CC_POSITION_Y_VARIABLE_NAME_PREFIX + block[1]]]] - ] - ]] + ["*", + ["()", ["-", ["xpos"], ["readVariable", S2CC_POSITION_X_VARIABLE_NAME_PREFIX + block[1]]]], + ["()", ["-", ["xpos"], ["readVariable", S2CC_POSITION_X_VARIABLE_NAME_PREFIX + block[1]]]] + ], ["*", + ["()", ["-", ["ypos"], ["readVariable", S2CC_POSITION_Y_VARIABLE_NAME_PREFIX + block[1]]]], + ["()", ["-", ["ypos"], ["readVariable", S2CC_POSITION_Y_VARIABLE_NAME_PREFIX + block[1]]]] + ] + ]] ] positions_needed_for_sprite_names.add(block[1]) if block[1] == "_mouse_": @@ -238,6 +238,63 @@ def replace_distance_to_object_blocks(block_list, positions_needed_for_sprite_na script.blocks = replace_distance_to_object_blocks(script.blocks, positions_needed_for_sprite_names) # parse again ScriptElement tree script.script_element = ScriptElement.from_raw_block(script.blocks) + + ############################################################################################ + # glide to sprite/mouse-pointer/random-position workaround + ############################################################################################ + def has_glide_to_sprite_script(block_list, all_sprite_names): + for block in block_list: + if isinstance(block, list) and (block[0] == 'glideTo:' \ + and block[-1] in all_sprite_names + ['_mouse_', '_random_'] \ + or has_glide_to_sprite_script(block, all_sprite_names)): + return True + return False + + def add_glide_to_workaround_script(block_list, positions_needed_for_sprite_names): + glide_to_workaround_bricks = [] + for block in block_list: + if isinstance(block, list): + if block[0] == 'glideTo:': + sprite_name = block[-1] + time_in_sec = block[1] + random_pos = False + if sprite_name == "_random_": + random_pos = True + elif sprite_name == "_mouse_": + workaround_info[ADD_MOUSE_SPRITE] = True + else: + positions_needed_for_sprite_names.add(sprite_name) + + if random_pos: + left_x, right_x = -STAGE_WIDTH_IN_PIXELS / 2, STAGE_WIDTH_IN_PIXELS / 2 + lower_y, upper_y = -STAGE_HEIGHT_IN_PIXELS / 2, STAGE_HEIGHT_IN_PIXELS / 2 + glide_to_workaround_bricks += [[ + "glideSecs:toX:y:elapsed:from:", + time_in_sec, + ["randomFrom:to:", left_x, right_x], + ["randomFrom:to:", lower_y, upper_y] + ]] + else: + x_pos_var_name = S2CC_POSITION_X_VARIABLE_NAME_PREFIX + sprite_name + y_pos_var_name = S2CC_POSITION_Y_VARIABLE_NAME_PREFIX + sprite_name + glide_to_workaround_bricks += [[ + "glideSecs:toX:y:elapsed:from:", + time_in_sec, + ["readVariable", x_pos_var_name], + ["readVariable", y_pos_var_name] + ]] + else: + glide_to_workaround_bricks += [add_glide_to_workaround_script(block, positions_needed_for_sprite_names)] + else: + glide_to_workaround_bricks += [block] + return glide_to_workaround_bricks + + for script in self.scripts: + if has_glide_to_sprite_script(script.blocks, all_sprite_names): + script.blocks = add_glide_to_workaround_script(script.blocks, positions_needed_for_sprite_names) + script.script_element = ScriptElement.from_raw_block(script.blocks) + + # save which sprites need position tracking from 'distanceTo'- and 'glideTo'-bricks workaround_info[ADD_POSITION_SCRIPT_TO_OBJECTS_KEY] = positions_needed_for_sprite_names ############################################################################################ @@ -380,7 +437,6 @@ def __init__(self, dict_, data_origin=""): for destination_sprite_name in position_script_to_be_added: if destination_sprite_name == "_mouse_": continue - sprite_object = sprite_name_sprite_mapping[destination_sprite_name] assert sprite_object is not None self._add_update_position_script_to_object(sprite_object) @@ -413,9 +469,9 @@ def _add_update_position_script_to_object(self, sprite_object): # update position script script_blocks = [ ["doForever", [ - ["setVar:to:", position_x_var_name, ["xpos"]], - ["setVar:to:", position_y_var_name, ["ypos"]], - ["wait:elapsed:from:", UPDATE_HELPER_VARIABLE_TIMEOUT] + ["setVar:to:", position_x_var_name, ["xpos"]], + ["setVar:to:", position_y_var_name, ["ypos"]], + ["wait:elapsed:from:", UPDATE_HELPER_VARIABLE_TIMEOUT] ]] ] sprite_object.scripts += [Script([0, 0, [[SCRIPT_GREEN_FLAG]] + script_blocks])] @@ -449,8 +505,8 @@ def _add_timer_script_to_stage_object(self): # timer counter script script_blocks = [ ["doForever", [ - ["changeVar:by:", S2CC_TIMER_VARIABLE_NAME, UPDATE_HELPER_VARIABLE_TIMEOUT], - ["wait:elapsed:from:", UPDATE_HELPER_VARIABLE_TIMEOUT] + ["changeVar:by:", S2CC_TIMER_VARIABLE_NAME, UPDATE_HELPER_VARIABLE_TIMEOUT], + ["wait:elapsed:from:", UPDATE_HELPER_VARIABLE_TIMEOUT] ]] ] self.objects[0].scripts += [Script([0, 0, [[SCRIPT_GREEN_FLAG]] + script_blocks])] @@ -563,12 +619,12 @@ def raw_project_code_from_project_folder_path(project_folder_path): is_binary_string = lambda bytesdata: bool(bytesdata.translate(None, textchars)) fp.seek(0, 0) # set file pointer back to the beginning of the file if is_binary_string(fp.read(1024)): # check first 1024 bytes - raise EnvironmentError("Invalid JSON file. The project's code-file "\ - "seems to be a binary file. Project might be very old "\ - "Scratch project. Scratch projects lower than 2.0 are "\ - "not supported!") + raise EnvironmentError("Invalid JSON file. The project's code-file " \ + "seems to be a binary file. Project might be very old " \ + "Scratch project. Scratch projects lower than 2.0 are " \ + "not supported!") else: - raise EnvironmentError("Invalid JSON file. But the project's "\ + raise EnvironmentError("Invalid JSON file. But the project's " \ "code-file seems to be no binary file...") @classmethod @@ -626,7 +682,7 @@ def read_md5_to_resource_path_mapping(): # self.name = name if name is not None else scratchwebapi.getMetaDataEntry(self.project_id, "title") - self.instructions, self.notes_and_credits, self.automatic_screenshot_image_url =\ + self.instructions, self.notes_and_credits, self.automatic_screenshot_image_url = \ scratchwebapi.getMetaDataEntry(self.project_id, "instructions", "description", "image") # self.instructions = scratchwebapi.getMetaDataEntry(self.project_id, "instructions") # self.notes_and_credits = scratchwebapi.getMetaDataEntry(self.project_id, "description") @@ -811,7 +867,7 @@ def cmp_block(block, other_block): for (block_arg_index, block_arg) in enumerate(block_args): other_block_arg = other_block_args[block_arg_index] if type(block_arg) != type(other_block_arg) \ - and not (isinstance(block_arg, (str, unicode)) and isinstance(block_arg, (str, unicode))): + and not (isinstance(block_arg, (str, unicode)) and isinstance(block_arg, (str, unicode))): return False if isinstance(block_arg, list): diff --git a/src/scratchtocatrobat/scratch/test_scratch.py b/src/scratchtocatrobat/scratch/test_scratch.py index e82d2f8e..41014397 100644 --- a/src/scratchtocatrobat/scratch/test_scratch.py +++ b/src/scratchtocatrobat/scratch/test_scratch.py @@ -1039,6 +1039,308 @@ def test_two_different_distance_blocks_in_different_objects(self): assert global_variables[2] == { "name": position_x_var_name1, "value": 0, "isPersistent": False } assert global_variables[3] == { "name": position_y_var_name1, "value": 0, "isPersistent": False } + def test_single_distance_block_to_mouse_pointer(self): + cls = self.__class__ + script_data = [0, 0, [["whenGreenFlag"], ["forward:", ["distanceTo:", "_mouse_"]]]] + cls.DISTANCE_HELPER_OBJECTS_DATA_TEMPLATE["children"][0]["scripts"] = [script_data] + cls.DISTANCE_HELPER_OBJECTS_DATA_TEMPLATE["children"][1]["scripts"] = [] + raw_project = scratch.RawProject(cls.DISTANCE_HELPER_OBJECTS_DATA_TEMPLATE) + position_x_var_name = scratch.S2CC_POSITION_X_VARIABLE_NAME_PREFIX + "_mouse_" + position_y_var_name = scratch.S2CC_POSITION_Y_VARIABLE_NAME_PREFIX + "_mouse_" + expected_first_object_script_data = [0, 0, [["whenGreenFlag"], ["forward:", + ["computeFunction:of:", "sqrt", + ["+", + ["*", + ["()", ["-", ["xpos"], ["readVariable", position_x_var_name]]], + ["()", ["-", ["xpos"], ["readVariable", position_x_var_name]]] + ], + ["*", + ["()", ["-", ["ypos"], ["readVariable", position_y_var_name]]], + ["()", ["-", ["ypos"], ["readVariable", position_y_var_name]]] + ] + ]] + ]]] + + + # validate + assert len(raw_project.objects) == 3 + [background_object, first_object, second_object] = raw_project.objects + + # background object + assert len(background_object.scripts) == 0 + + # first object + assert len(first_object.scripts) == 1 + script = first_object.scripts[0] + expected_script = scratch.Script(expected_first_object_script_data) + assert script == expected_script + + # mouse-sprite will be created during conversion and not in the pre-processing -> + # for now, the second sprite is irrelevant + assert len(second_object.scripts) == 0 + + # global variables; for the mouse-pointer workaround, the variables are created during conversion + # and not in the pre-process + global_variables = background_object._dict_object["variables"] + assert len(global_variables) == 0 + + # check the flag -> indicates that mouse-sprite has to be created during conversion + assert raw_project._has_mouse_position_script + +class TestGlideToObjectBlockWorkaround(unittest.TestCase): + GLIDE_TO_OBJECTS_DATA_TEMPLATE = { + "objName": "Stage", + "sounds": [], + "costumes": [], + "currentCostumeIndex": 0, + "penLayerMD5": "5c81a336fab8be57adc039a8a2b33ca9.png", + "penLayerID": 0, + "tempoBPM": 60, + "videoAlpha": 0.5, + "children": [{ "objName": "Sprite1", "scripts": None }, + { "objName": "Sprite2", "scripts": None }], + "info": {} + } + + def setUp(self): + unittest.TestCase.setUp(self) + cls = self.__class__ + cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE["scripts"] = [] + + def test_glide_to_different_sprite(self): + cls = self.__class__ + spinner_selection = "Sprite2" + time_in_sec = "1" + script_data = [0, 0, [["whenGreenFlag"], ["glideTo:", time_in_sec, spinner_selection]]] + cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE["children"][0]["scripts"] = [script_data] + cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE["children"][1]["scripts"] = [] + raw_project = scratch.RawProject(cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE) + + position_x_var_name = scratch.S2CC_POSITION_X_VARIABLE_NAME_PREFIX + spinner_selection + position_y_var_name = scratch.S2CC_POSITION_Y_VARIABLE_NAME_PREFIX + spinner_selection + expected_first_object_script_data = \ + [0, 0, [["whenGreenFlag"], [ + "glideSecs:toX:y:elapsed:from:", + time_in_sec, + ["readVariable", position_x_var_name], + ["readVariable", position_y_var_name] + ]]] + + expected_second_object_script_data = \ + [0, 0, [["whenGreenFlag"],[ + "doForever", [ + ["setVar:to:", position_x_var_name, ["xpos"]], + ["setVar:to:", position_y_var_name, ["ypos"]], + ["wait:elapsed:from:", scratch.UPDATE_HELPER_VARIABLE_TIMEOUT] + ]] + ]] + + # validate + assert len(raw_project.objects) == 3 + [background_object, first_object, second_object] = raw_project.objects + + # background object + assert len(background_object.scripts) == 0 + + # first object + assert len(first_object.scripts) == 1 + script = first_object.scripts[0] + expected_script = scratch.Script(expected_first_object_script_data) + assert script == expected_script + + # second object + assert len(second_object.scripts) == 1 + script = second_object.scripts[0] + expected_script = scratch.Script(expected_second_object_script_data) + assert script == expected_script + + # global variables + global_variables = background_object._dict_object["variables"] + assert len(global_variables) == 2 + assert global_variables[0] == { "name": position_x_var_name, "value": 0, "isPersistent": False } + assert global_variables[1] == { "name": position_y_var_name, "value": 0, "isPersistent": False } + + def test_glide_to_random_position(self): + cls = self.__class__ + spinner_selection = "_random_" + time_in_sec = "1" + script_data = [0, 0, [["whenGreenFlag"], ["glideTo:", time_in_sec, spinner_selection]]] + cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE["children"][0]["scripts"] = [script_data] + cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE["children"][1]["scripts"] = [] + raw_project = scratch.RawProject(cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE) + + # random position also needs a workaround -> different resolutions in scratch and pocket code + left_x, right_x = -scratch.STAGE_WIDTH_IN_PIXELS / 2, scratch.STAGE_WIDTH_IN_PIXELS / 2 + lower_y, upper_y = -scratch.STAGE_HEIGHT_IN_PIXELS / 2, scratch.STAGE_HEIGHT_IN_PIXELS / 2 + + expected_first_object_script_data = \ + [0, 0, [["whenGreenFlag"],[ + "glideSecs:toX:y:elapsed:from:", + time_in_sec, + ["randomFrom:to:", left_x, right_x], + ["randomFrom:to:", lower_y, upper_y] + ]]] + + # validate + assert len(raw_project.objects) == 3 + [background_object, first_object, second_object] = raw_project.objects + + # background object + assert len(background_object.scripts) == 0 + + # first object + assert len(first_object.scripts) == 1 + script = first_object.scripts[0] + expected_script = scratch.Script(expected_first_object_script_data) + assert script == expected_script + + # second object + assert len(second_object.scripts) == 0 + + def test_glide_to_mouse_poointer(self): + cls = self.__class__ + spinner_selection = "_mouse_" + time_in_sec = "1" + script_data = [0, 0, [["whenGreenFlag"], ["glideTo:", time_in_sec, spinner_selection]]] + cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE["children"][0]["scripts"] = [script_data] + cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE["children"][1]["scripts"] = [] + raw_project = scratch.RawProject(cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE) + + position_x_var_name = scratch.S2CC_POSITION_X_VARIABLE_NAME_PREFIX + spinner_selection + position_y_var_name = scratch.S2CC_POSITION_Y_VARIABLE_NAME_PREFIX + spinner_selection + expected_first_object_script_data = \ + [0, 0, [["whenGreenFlag"], [ + "glideSecs:toX:y:elapsed:from:", + time_in_sec, + ["readVariable", position_x_var_name], + ["readVariable", position_y_var_name] + ]]] + + # validate + assert len(raw_project.objects) == 3 + [background_object, first_object, second_object] = raw_project.objects + + # background object + assert len(background_object.scripts) == 0 + + # first object + assert len(first_object.scripts) == 1 + script = first_object.scripts[0] + expected_script = scratch.Script(expected_first_object_script_data) + assert script == expected_script + + # same as in the 'distanceTo'-block workaround, the mouse-sprite is created during + # conversion and not in the pre-processing -> no mouse-sprite yet + assert len(second_object.scripts) == 0 + + # global variables; for the mouse-pointer workaround, the variables are created during conversion + # and not in the pre-process + global_variables = background_object._dict_object["variables"] + assert len(global_variables) == 0 + + # check the flag -> indicates that mouse-sprite has to be created during conversion + assert raw_project._has_mouse_position_script + + def test_glide_to_random_single_nested(self): + cls = self.__class__ + spinner_selection = "_random_" + time_in_sec = "1" + script_data = [0, 0, [["whenGreenFlag"], ["doForever", ["glideTo:", time_in_sec, spinner_selection]]]] + cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE["children"][0]["scripts"] = [script_data] + cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE["children"][1]["scripts"] = [] + raw_project = scratch.RawProject(cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE) + + # random position also needs a workaround -> different resolutions in scratch and pocket code + left_x, right_x = -scratch.STAGE_WIDTH_IN_PIXELS / 2, scratch.STAGE_WIDTH_IN_PIXELS / 2 + lower_y, upper_y = -scratch.STAGE_HEIGHT_IN_PIXELS / 2, scratch.STAGE_HEIGHT_IN_PIXELS / 2 + + expected_first_object_script_data = \ + [0, 0, [["whenGreenFlag"], + ["doForever", + ["glideSecs:toX:y:elapsed:from:", + time_in_sec, + ["randomFrom:to:", left_x, right_x], + ["randomFrom:to:", lower_y, upper_y] + ] + ] + ]] + + # validate + assert len(raw_project.objects) == 3 + [background_object, first_object, second_object] = raw_project.objects + + # background object + assert len(background_object.scripts) == 0 + + # first object + assert len(first_object.scripts) == 1 + script = first_object.scripts[0] + expected_script = scratch.Script(expected_first_object_script_data) + assert script == expected_script + + # second object + assert len(second_object.scripts) == 0 + + def test_glide_to_objects_double_nested(self): + cls = self.__class__ + spinner_selection_random = "_random_" + spinner_selection_mouse = "_mouse_" + time_in_sec = "1" + + script_data = [0, 0, [["whenGreenFlag"], + ["doForever", + ["doIfElse", [">", ["randomFrom:to:", 1.0, 100.0], "50"], + ["glideTo:", time_in_sec, spinner_selection_random], + ["glideTo:", time_in_sec, spinner_selection_mouse] + ] + ] + ]] + cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE["children"][0]["scripts"] = [script_data] + cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE["children"][1]["scripts"] = [] + raw_project = scratch.RawProject(cls.GLIDE_TO_OBJECTS_DATA_TEMPLATE) + + # variables for x and y positions of the mouse-pointer + position_x_var_name = scratch.S2CC_POSITION_X_VARIABLE_NAME_PREFIX + spinner_selection_mouse + position_y_var_name = scratch.S2CC_POSITION_Y_VARIABLE_NAME_PREFIX + spinner_selection_mouse + + # random position option also needs a workaround -> different resolutions in scratch and pocket code + left_x, right_x = -scratch.STAGE_WIDTH_IN_PIXELS / 2, scratch.STAGE_WIDTH_IN_PIXELS / 2 + lower_y, upper_y = -scratch.STAGE_HEIGHT_IN_PIXELS / 2, scratch.STAGE_HEIGHT_IN_PIXELS / 2 + + expected_first_object_script_data = \ + [0, 0, [["whenGreenFlag"], + ["doForever", + ["doIfElse", [">", ["randomFrom:to:", 1.0, 100.0], "50"], + ["glideSecs:toX:y:elapsed:from:", + time_in_sec, + ["randomFrom:to:", left_x, right_x], + ["randomFrom:to:", lower_y, upper_y] + ], + ["glideSecs:toX:y:elapsed:from:", + time_in_sec, + ["readVariable", position_x_var_name], + ["readVariable", position_y_var_name] + ] + ] + ] + ]] + + # validate + assert len(raw_project.objects) == 3 + [background_object, first_object, second_object] = raw_project.objects + + # background object + assert len(background_object.scripts) == 0 + + # first object + assert len(first_object.scripts) == 1 + script = first_object.scripts[0] + expected_script = scratch.Script(expected_first_object_script_data) + assert script == expected_script + + # second object + assert len(second_object.scripts) == 0 + class TestShowSensorBlockWorkarounds(unittest.TestCase): SENSOR_HELPER_OBJECTS_DATA_TEMPLATE = { diff --git a/test/res/scratch/distance_to_mouse_pointer/b7853f557e4426412e64bb3da6531a99.svg b/test/res/scratch/distance_to_mouse_pointer/b7853f557e4426412e64bb3da6531a99.svg new file mode 100644 index 00000000..a537afb3 --- /dev/null +++ b/test/res/scratch/distance_to_mouse_pointer/b7853f557e4426412e64bb3da6531a99.svg @@ -0,0 +1,42 @@ + + + + costume1.1 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/res/scratch/distance_to_mouse_pointer/cd21514d0531fdffb22204e0ec5ed84a.svg b/test/res/scratch/distance_to_mouse_pointer/cd21514d0531fdffb22204e0ec5ed84a.svg new file mode 100644 index 00000000..15f73119 --- /dev/null +++ b/test/res/scratch/distance_to_mouse_pointer/cd21514d0531fdffb22204e0ec5ed84a.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/test/res/scratch/distance_to_mouse_pointer/project.json b/test/res/scratch/distance_to_mouse_pointer/project.json new file mode 100644 index 00000000..e6354010 --- /dev/null +++ b/test/res/scratch/distance_to_mouse_pointer/project.json @@ -0,0 +1,76 @@ +{ + "children": [ + { + "costumes": [ + { + "baseLayerID": "b7853f557e4426412e64bb3da6531a99", + "baseLayerMD5": "b7853f557e4426412e64bb3da6531a99.svg", + "bitmapResolution": 1, + "costumeName": "costume1", + "rotationCenterX": 48, + "rotationCenterY": 50 + } + ], + "currentCostumeIndex": 0, + "direction": 90, + "isDraggable": false, + "isStage": false, + "lists": [], + "objName": "Sprite1", + "rotationStyle": "all around", + "scale": 1.0, + "scratchX": -168, + "scratchY": -30, + "scripts": [ + [ + 1, + 1, + [ + [ + "whenKeyPressed", + "space" + ], + [ + "say:duration:elapsed:from:", + [ + "distanceTo:", + "_mouse_" + ], + 2.0 + ] + ] + ] + ], + "sounds": [], + "variables": [], + "visible": true + } + ], + "costumes": [ + { + "baseLayerID": "cd21514d0531fdffb22204e0ec5ed84a", + "baseLayerMD5": "cd21514d0531fdffb22204e0ec5ed84a.svg", + "bitmapResolution": 1, + "costumeName": "backdrop1", + "rotationCenterX": 240, + "rotationCenterY": 180 + } + ], + "currentCostumeIndex": 0, + "info": { + "agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36", + "projectID": 0, + "semver": "3.0.0", + "vm": "0.2.0-prerelease.20200402182733" + }, + "isStage": true, + "lists": [], + "objName": "Stage", + "penLayerID": 0, + "penLayerMD5": "Scratch3Doesn'tHaveThis", + "scripts": [], + "sounds": [], + "tempoBPM": 60, + "variables": [], + "videoAlpha": 0.5 +} \ No newline at end of file diff --git a/test/res/scratch/glide_to_mouse_pointer/b7853f557e4426412e64bb3da6531a99.svg b/test/res/scratch/glide_to_mouse_pointer/b7853f557e4426412e64bb3da6531a99.svg new file mode 100644 index 00000000..a537afb3 --- /dev/null +++ b/test/res/scratch/glide_to_mouse_pointer/b7853f557e4426412e64bb3da6531a99.svg @@ -0,0 +1,42 @@ + + + + costume1.1 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/res/scratch/glide_to_mouse_pointer/cd21514d0531fdffb22204e0ec5ed84a.svg b/test/res/scratch/glide_to_mouse_pointer/cd21514d0531fdffb22204e0ec5ed84a.svg new file mode 100644 index 00000000..15f73119 --- /dev/null +++ b/test/res/scratch/glide_to_mouse_pointer/cd21514d0531fdffb22204e0ec5ed84a.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/test/res/scratch/glide_to_mouse_pointer/project.json b/test/res/scratch/glide_to_mouse_pointer/project.json new file mode 100644 index 00000000..fe674c4b --- /dev/null +++ b/test/res/scratch/glide_to_mouse_pointer/project.json @@ -0,0 +1,79 @@ +{ + "children": [ + { + "costumes": [ + { + "baseLayerID": "b7853f557e4426412e64bb3da6531a99", + "baseLayerMD5": "b7853f557e4426412e64bb3da6531a99.svg", + "bitmapResolution": 1, + "costumeName": "costume1", + "rotationCenterX": 48, + "rotationCenterY": 50 + } + ], + "currentCostumeIndex": 0, + "direction": 90, + "isDraggable": false, + "isStage": false, + "lists": [], + "objName": "Sprite1", + "rotationStyle": "all around", + "scale": 1.0, + "scratchX": -167, + "scratchY": -13, + "scripts": [ + [ + 1, + 1, + [ + [ + "whenKeyPressed", + "space" + ], + [ + "glideTo:", + 1.0, + "_mouse_" + ] + ] + ] + ], + "sounds": [], + "variables": [], + "visible": true + } + ], + "costumes": [ + { + "baseLayerID": "cd21514d0531fdffb22204e0ec5ed84a", + "baseLayerMD5": "cd21514d0531fdffb22204e0ec5ed84a.svg", + "bitmapResolution": 1, + "costumeName": "backdrop1", + "rotationCenterX": 240, + "rotationCenterY": 180 + } + ], + "currentCostumeIndex": 0, + "info": { + "agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36", + "projectID": 0, + "semver": "3.0.0", + "vm": "0.2.0-prerelease.20200402182733" + }, + "isStage": true, + "lists": [], + "objName": "Stage", + "penLayerID": 0, + "penLayerMD5": "Scratch3Doesn'tHaveThis", + "scripts": [], + "sounds": [], + "tempoBPM": 60, + "variables": [ + { + "isPersistent": false, + "name": "my variable", + "value": 0 + } + ], + "videoAlpha": 0.5 +} \ No newline at end of file