Skip to content
New issue

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

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

Already on GitHub? # to your account

STCC-156-227 distanceTo- and glideTo-bricks #170

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion config/default.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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

;-------------------------------------------------------------------------------
Expand Down
12 changes: 4 additions & 8 deletions src/scratchtocatrobat/converter/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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_")
Expand Down Expand Up @@ -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:
Expand Down
80 changes: 74 additions & 6 deletions src/scratchtocatrobat/converter/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
104 changes: 80 additions & 24 deletions src/scratchtocatrobat/scratch/scratch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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_":
Expand All @@ -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

############################################################################################
Expand Down Expand Up @@ -380,7 +437,6 @@ def __init__(self, dict_, data_origin="<undefined>"):

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)
Expand Down Expand Up @@ -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])]
Expand Down Expand Up @@ -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])]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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):
Expand Down
Loading