diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ed307b68..3a74047c3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -74,7 +74,7 @@ jobs: pip list - run: mkdir upload - name: Build Linux AppImage - run: xvfb-run make linux + run: QT_QPA_PLATFORM=offscreen make linux # GitHub actions upload artifact breaks permissions, workaround using tar # https://github.com/actions/upload-artifact/issues/38 - name: Tar AppImage to maintain permissions diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c3dc435c8..98393b326 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -15,7 +15,7 @@ jobs: analyze: timeout-minutes: 20 name: Analyze - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 permissions: actions: read contents: read diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6a88e9ab..716fd4dfa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,8 +11,17 @@ jobs: timeout-minutes: 30 strategy: matrix: - os: [ubuntu-20.04, ubuntu-latest, macos-11, macos-latest, windows-2019, windows-latest] - python-version: ['3.8', '3.9', '3.10', '3.11'] + # macos-13 is the latest release on x86, and macos-14 is arm64 + os: [ubuntu-20.04, ubuntu-latest, macos-11, macos-13, macos-14, windows-2019, windows-latest] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + exclude: + # These Python versions are not available in macOS arm64 + - os: macos-14 + python-version: '3.7' + - os: macos-14 + python-version: '3.8' + - os: macos-14 + python-version: '3.9' fail-fast: false runs-on: ${{ matrix.os }} name: Test Py ${{ matrix.python-version }} - ${{ matrix.os }} @@ -103,7 +112,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - docker-tag: ['buster-2021-05-28', 'buster-legacy-2022-04-07'] + docker-tag: ['buster-2021-05-28', 'buster-legacy-2023-05-03', 'bullseye-2023-05-03'] fail-fast: false services: rpios: @@ -115,7 +124,7 @@ jobs: - name: Wait 2m30s for the docker image to start up QEMU and Raspberry Pi OS run: sleep 150 - name: Clone project & setup it as the bash entry directory - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@v1.0.0 with: host: rpios username: pi @@ -130,7 +139,7 @@ jobs: echo "cd ~/mu" > ~/.bashrc_new && cat ~/.bashrc >> ~/.bashrc_new rm ~/.bashrc && mv ~/.bashrc_new ~/.bashrc - name: Install Mu extra apt dependencies - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@v1.0.0 with: host: rpios username: pi @@ -140,22 +149,24 @@ jobs: sudo apt-get update sudo apt-get install -y python3-virtualenv - name: Create venv and install Python dependencies - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@v1.0.0 with: host: rpios username: pi password: raspberry port: ${{ job.services.rpios.ports[5022] }} command_timeout: 20m + # Some compiled packages take a while to be built in piwheels, so to + # avoid intermittent pip install failures we use `--prefer-binary` script: | python3 -m virtualenv ~/mu/.venv -v --python=python3 --system-site-packages echo "source ~/mu/.venv/bin/activate" > ~/.bashrc_new && cat ~/.bashrc >> ~/.bashrc_new rm ~/.bashrc && mv ~/.bashrc_new ~/.bashrc source .venv/bin/activate python -m pip list - python -m pip install ."[dev]" + python -m pip install ."[dev]" --prefer-binary - name: Environment info - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@v1.0.0 with: host: rpios username: pi @@ -168,7 +179,7 @@ jobs: python3 -m pip --version python3 -m pip list - name: Run tests - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@v1.0.0 with: host: rpios username: pi diff --git a/MANIFEST.in b/MANIFEST.in index cd51a5376..4682def7d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,6 +7,8 @@ include mu/resources/css/* include mu/resources/images/* include mu/resources/fonts/* include mu/resources/pygamezero/* +include mu/resources/pico/* +include mu/resources/neopia/* recursive-include mu/resources/web * include run.py recursive-include mu/locale * diff --git a/Makefile b/Makefile index e30e6fb86..0a07a47b1 100644 --- a/Makefile +++ b/Makefile @@ -113,9 +113,9 @@ macos: check # 1. Not really needed. # 2. Previously active venv would be "gone" on venv-pup deactivation. # Installing pup from a fork with the --pip-platform flag proof of concept - # and using it to install wheels for the `macosx_10_12_x86_64` platform + # and using it to install wheels for the `macosx_10_15_x86_64` platform ./venv-pup/bin/pip install git+https://github.com/carlosperate/pup.git@pip-platform - ./venv-pup/bin/pup package --launch-module=mu --nice-name="Mu Editor" --icon-path=./package/icons/mac_icon.icns --license-path=./LICENSE --pip-platform=macosx_10_12_x86_64 . + ./venv-pup/bin/pup package --launch-module=mu --nice-name="Mu Editor" --icon-path=./package/icons/mac_icon.icns --license-path=./LICENSE --pip-platform=macosx_10_15_x86_64 . rm -r venv-pup ls -la ./build/pup/ ls -la ./dist/ diff --git a/README.rst b/README.rst index 1bda61f6e..3d0d12f7d 100644 --- a/README.rst +++ b/README.rst @@ -1,26 +1,44 @@ -Mu - A Simple Python Code Editor +Custom Mu - Custom version of A Simple Python Code Editor `Mu `_ ================================ -.. image:: https://mu.readthedocs.io/en/latest/_images/logo.png +[How to make dev env.] + +1. install miniconda + +``https://docs.conda.io/projects/miniconda/en/latest/miniconda-install.html``
 + +2. Create virtual environment
 + +``conda create -n mu python=3.8`` + +3. Activate the created virtual env.
 + +``conda activate mu``
 + +4. Clone source codes
 + +``git clone https://github.com/roboticsware/mu`` + +5. Enter the directory of source codes
 + +``cd mu``

 + +6. Install dev dependencies
 + +``pip install -e ".[dev]"`` + +7. Run Mu
 + +``python run.py`` + +8. Build Mu + +``make win64 or macos`` + + +You can also reivew more information about extensive developer documentation `here `_. -Mu is a simple code editor for beginner programmers based on extensive feedback -from teachers and learners. Having said that, Mu is for anyone who wants to use -a simple "no frills" editor. -Mu is a modal editor with modes for many different ways to use Python to create -cool and interesting things. -Mu is written in Python and works on Windows, macOS, Linux and Raspberry Pi. -The project's public facing website is -`https://codewith.mu/ `_. We celebrate the work done by -users of mu at `https://madewith.mu/ `_. -We have `extensive developer documentation `_ -including a guide for -`setting up a development environment `_, -`contributor guidelines `_ and -`some suggested first steps `_. -We want our community to be a friendly place. Therefore, we expect contributors -and collaborators to follow our -`Code of Conduct `_. diff --git a/mu/__init__.py b/mu/__init__.py index 40ddacd05..73e6b87b8 100644 --- a/mu/__init__.py +++ b/mu/__init__.py @@ -6,7 +6,7 @@ __title__ = "mu-editor" __description__ = "A simple Python editor for beginner programmers." -__version__ = "1.2.3" +__version__ = "1.2.7" __license__ = "GPL3" __url__ = "https://github.com/mu-editor/mu" diff --git a/mu/app.py b/mu/app.py index 70cd9f3db..e2feb5577 100644 --- a/mu/app.py +++ b/mu/app.py @@ -471,6 +471,9 @@ def load_theme(theme): find_again_handlers = (editor.find_again, editor.find_again_backward) editor_window.connect_find_again(find_again_handlers, "F3") editor_window.connect_toggle_comments(editor.toggle_comments, "Ctrl+K") + editor_window.connect_close_tab(editor.close_tab, "Ctrl+W") + editor_window.connect_move_forward_tab(editor.move_forward_tab, "Ctrl+.") + editor_window.connect_move_backward_tab(editor.move_backward_tab, "Ctrl+,") editor.connect_to_status_bar(editor_window.status_bar) # Restore the previous session along with files passed by the os diff --git a/mu/contrib/microfs.py b/mu/contrib/microfs.py index df868a57d..fb07fb9f4 100644 --- a/mu/contrib/microfs.py +++ b/mu/contrib/microfs.py @@ -119,7 +119,7 @@ def get_serial(): return Serial(port, SERIAL_BAUD_RATE, timeout=1, parity="N") -def execute(commands, serial=None): +def execute(commands, serial=None, show_progress=False, callback=None): """ Sends the command to the connected micro:bit via serial and returns the result. If no serial connection is provided, attempts to autodetect the @@ -139,17 +139,22 @@ def execute(commands, serial=None): raw_on(serial) time.sleep(0.1) # Write the actual command and send CTRL-D to evaluate. - for command in commands: + total_size = len(commands) + for cnt, command in enumerate(commands): command_bytes = command.encode("utf-8") + # Up to 32 bytes can be stored separately in both transmit and receive modes of the UART for i in range(0, len(command_bytes), 32): serial.write(command_bytes[i : min(i + 32, len(command_bytes))]) time.sleep(0.01) - serial.write(b"\x04") + serial.write(b"\x04") # Soft Reset with CTRL-D response = serial.read_until(b"\x04>") # Read until prompt. - out, err = response[2:-2].split(b"\x04", 1) # Split stdout, stderr - result += out - if err: - return b"", err + if len(response) > 3: # Check if the split is possible + out, err = response[2:-2].split(b"\x04", 1) # Split stdout, stderr + result += out + if err: + return b"", err + if show_progress: + callback.emit(round(cnt / total_size * 100)) time.sleep(0.1) raw_off(serial) if close_serial: @@ -204,7 +209,7 @@ def rm(filename, serial=None): return True -def put(filename, target=None, serial=None): +def put(self, filename, target=None, serial=None): """ Puts a referenced file on the LOCAL file system onto the file system on the BBC micro:bit. @@ -214,7 +219,7 @@ def put(filename, target=None, serial=None): Returns True for success or raises an IOError if there's a problem. """ - if not os.path.isfile(filename): + if os.path.isdir(filename) or not os.path.isfile(filename): raise IOError("No such file.") with open(filename, "rb") as local: content = local.read() @@ -230,13 +235,13 @@ def put(filename, target=None, serial=None): commands.append("f(" + repr(line) + ")") content = content[64:] commands.append("fd.close()") - out, err = execute(commands, serial) + out, err = execute(commands, serial, True, self.on_put_update_file) if err: raise IOError(clean_error(err)) return True -def get(filename, target=None, serial=None): +def get(self, filename, target=None, serial=None): """ Gets a referenced file on the device's file system and copies it to the target (or current working directory if unspecified). @@ -270,12 +275,9 @@ def get(filename, target=None, serial=None): "while result:\n result = r(32)\n if result:\n u.write(result)\n", "f.close()", ] - out, err = execute(commands, serial) + out, err = execute(commands, serial, True, self.on_put_update_file) if err: raise IOError(clean_error(err)) - # Recombine the bytes while removing "b'" from start and "'" from end. - with open(target, "wb") as f: - f.write(out) return True diff --git a/mu/interface/dialogs.py b/mu/interface/dialogs.py index 579b72b8c..fce76bd3d 100644 --- a/mu/interface/dialogs.py +++ b/mu/interface/dialogs.py @@ -90,7 +90,7 @@ def setup(self, modes, current_mode): for name, item in modes.items(): if not item.is_debugger: litem = ModeItem( - item.name, item.description, item.icon, self.mode_list + item.name, _(item.description), item.icon, self.mode_list ) if item.icon == current_mode: self.mode_list.setCurrentItem(litem) @@ -585,7 +585,7 @@ def setup(self, log, settings, packages, mode, device_list): self.log_widget = LogWidget(self) self.log_widget.setup(log) self.tabs.addTab(self.log_widget, _("Current Log")) - if mode.short_name in ["python", "web", "pygamezero", "neobot"]: + if mode.short_name in ["python", "web", "pygamezero", "neopia"]: self.envar_widget = EnvironmentVariablesWidget(self) self.envar_widget.setup(settings.get("envars", "")) self.tabs.addTab(self.envar_widget, _("Python3 Environment")) @@ -596,7 +596,7 @@ def setup(self, log, settings, packages, mode, device_list): settings.get("microbit_runtime", ""), ) self.tabs.addTab(self.microbit_widget, _("BBC micro:bit Settings")) - if mode.short_name in ["python", "web", "pygamezero", "neobot"]: + if mode.short_name in ["python", "web", "pygamezero", "neopia"]: self.package_widget = PackagesWidget(self) self.package_widget.setup(packages) self.tabs.addTab(self.package_widget, _("Third Party Packages")) diff --git a/mu/interface/editor.py b/mu/interface/editor.py index 0ce18efc1..cb2a597cd 100644 --- a/mu/interface/editor.py +++ b/mu/interface/editor.py @@ -89,6 +89,8 @@ class EditorPane(QsciScintilla): open_file = pyqtSignal(str) # Signal fired when a context menu is requested. context_menu = pyqtSignal() + # Signal fired when a text selection is changed. + selected_text = pyqtSignal(str) def __init__(self, path, text, newline=NEWLINE): super().__init__() @@ -511,6 +513,7 @@ def highlight_selected_matches(self): # valid identifier-type word. # selected_text = self.selectedText() + self.selected_text.emit(selected_text) if not RE_VALID_WORD.match(selected_text): return diff --git a/mu/interface/main.py b/mu/interface/main.py index ddc16e6d2..9676eb013 100644 --- a/mu/interface/main.py +++ b/mu/interface/main.py @@ -36,6 +36,7 @@ QPushButton, QHBoxLayout, QProgressDialog, + QProgressBar, ) from PyQt6.QtGui import ( QKeySequence, @@ -224,9 +225,8 @@ def removeTab(self, tab_id): window = self.nativeParentWidget() modified = self.widget(tab_id).isModified() if modified: - msg = ( - "There is un-saved work, closing the tab will cause you " - "to lose it." + msg = _( + "There is un-saved work, closing the tab will cause you to lose it." ) if window.show_confirmation(msg) == QMessageBox.Cancel: return @@ -330,6 +330,7 @@ class Window(QMainWindow): data_received = pyqtSignal(bytes) open_file = pyqtSignal(str) load_theme = pyqtSignal(str) + selected_text = pyqtSignal(str) previous_folder = None debug_widths = None @@ -486,6 +487,11 @@ def on_open_file(file): new_tab.context_menu.connect(self.on_context_menu) + @new_tab.selected_text.connect + def on_selected_text(text): + # Bubble the signal up + self.selected_text.emit(text) + self.tabs.setCurrentIndex(new_tab_index) self.connect_zoom(new_tab) self.set_theme(self.theme) @@ -502,6 +508,22 @@ def focus_tab(self, tab): self.tabs.setCurrentIndex(index) tab.setFocus() + def move_tab(self, tab, is_forward): + """ + Move focused tab to forward / backward. + """ + tab_cnt = len(self.tabs) + if tab_cnt: + index = self.tabs.indexOf(tab) + if is_forward: + index += 1 + if index == tab_cnt: index = 0 + else: + index -= 1 + if index < 0: index = tab_cnt - 1 + self.tabs.setCurrentIndex(index) + tab.setFocus() + @property def tab_count(self): """ @@ -631,8 +653,11 @@ def on_open_file(file): self.fs_pane.microbit_fs.list_files.connect(file_manager.ls) self.fs_pane.local_fs.get.connect(file_manager.get) self.fs_pane.local_fs.put.connect(file_manager.put) + self.fs_pane.local_fs.pbar_update.connect(self.fs_pane.microbit_fs.on_put_update) + self.fs_pane.local_fs.itemDoubleClicked.connect(self.fs_pane.local_fs.on_item_double_clicked) self.fs_pane.local_fs.list_files.connect(file_manager.ls) file_manager.on_put_file.connect(self.fs_pane.microbit_fs.on_put) + file_manager.on_put_update_file.connect(self.fs_pane.microbit_fs.on_put_update) file_manager.on_delete_file.connect(self.fs_pane.microbit_fs.on_delete) file_manager.on_get_file.connect(self.fs_pane.local_fs.on_get) file_manager.on_list_fail.connect(self.fs_pane.on_ls_fail) @@ -1356,6 +1381,37 @@ def toggle_comments(self): if self.current_tab: self.current_tab.toggle_comments() + def connect_close_tab(self, handler, shortcut): + """ + Create a keyboard shortcut and associate it with a handler for closing + a current active tab. + """ + self.close_tab_shortcut = QShortcut(QKeySequence(shortcut), self) + self.close_tab_shortcut.activated.connect(handler) + + def close_tab(self): + """ + Close the currently active tab. + """ + if self.current_tab: + self.tabs.removeTab(self.widgets.index(self.current_tab)) + + def connect_move_forward_tab(self, handler, shortcut): + """ + Create a keyboard shortcut and associate it with a handler for moving + a current active tab forward. + """ + self.move_forward_tab_shortcut = QShortcut(QKeySequence(shortcut), self) + self.move_forward_tab_shortcut.activated.connect(handler) + + def connect_move_backward_tab(self, handler, shortcut): + """ + Create a keyboard shortcut and associate it with a handler for moving + a current active tab backward. + """ + self.move_backward_tab_shortcut = QShortcut(QKeySequence(shortcut), self) + self.move_backward_tab_shortcut.activated.connect(handler) + def show_device_selector(self): """ Reveals the device selector in the status bar @@ -1452,6 +1508,11 @@ def __init__(self, parent=None, mode="python"): self.mode = mode self.msg_duration = 5 + # Progress bar + self.progress_bar = QProgressBar() + self.addPermanentWidget(self.progress_bar) + self.progress_bar.setVisible(False) + # Mode selector. self.mode_label = QLabel() self.mode_label.setToolTip(_("Mu's current mode of behaviour.")) @@ -1516,3 +1577,9 @@ def device_connected(self, device): msg = _("Detected new {} device.").format(device.long_mode_name) self.set_message(msg, self.msg_duration * 1000) + + def set_pbar_value(self, amount): + self.progress_bar.setVisible(True) + self.progress_bar.setValue(amount) + if amount >= 100 or amount < 0: + self.progress_bar.setVisible(False) diff --git a/mu/interface/panes.py b/mu/interface/panes.py index 3462739e7..cc7bf88f8 100644 --- a/mu/interface/panes.py +++ b/mu/interface/panes.py @@ -682,8 +682,16 @@ class MuFileList(QListWidget): """ disable = pyqtSignal() + enable = pyqtSignal() list_files = pyqtSignal() + list_sub_files = pyqtSignal(list, str) set_message = pyqtSignal(str) + pbar_update = pyqtSignal(int) + cur_home_dir = '' + + @classmethod + def set_cur_home_dir(cls, val): + cls.cur_home_dir = val def show_confirm_overwrite_dialog(self): """ @@ -725,7 +733,7 @@ def dropEvent(self, event): ): self.disable.emit() local_filename = os.path.join( - self.home, source.currentItem().text() + self.cur_home_dir, source.currentItem().text() ) msg = _("Copying '{}' to device.").format(local_filename) logger.info(msg) @@ -739,6 +747,11 @@ def on_put(self, microbit_file): msg = _("'{}' successfully copied to device.").format(microbit_file) self.set_message.emit(msg) self.list_files.emit() + self.pbar_update.emit(-1) # To remove the pbar UI + self.enable.emit() + + def on_put_update(self, amount): + self.pbar_update.emit(amount) def contextMenuEvent(self, event): menu_current_item = self.currentItem() @@ -809,6 +822,8 @@ def on_get(self, microbit_file): ).format(microbit_file) self.set_message.emit(msg) self.list_files.emit() + self.pbar_update.emit(-1) # To remove the pbar UI + self.enable.emit() def contextMenuEvent(self, event): menu_current_item = self.currentItem() @@ -848,6 +863,20 @@ def contextMenuEvent(self, event): path = os.path.join(self.home, local_filename) self.put.emit(path, "main.py") + def on_item_double_clicked(self, item): + # Get the path + local_name = item.text() + path = os.path.join(self.home, local_name) + # Check the name is directory or file + if os.path.isfile(path): + logger.info("Open {} internally".format(local_name)) + # Send the signal bubbling up the tree + self.open_file.emit(path) + elif os.path.isdir(path): + self.home = path + MuFileList.set_cur_home_dir(path) # Change parent's sharedlass variable + self.list_sub_files.emit([], path + '/') # Go to sub directory + class FileSystemPane(QFrame): """ @@ -860,6 +889,7 @@ class FileSystemPane(QFrame): set_warning = pyqtSignal(str) list_files = pyqtSignal() open_file = pyqtSignal(str) + set_pbar_update = pyqtSignal(int) def __init__(self, home): super().__init__() @@ -881,7 +911,7 @@ def on_open_file(file): local_label.setText(_("Files on your computer:")) self.microbit_label = microbit_label self.local_label = local_label - self.microbit_fs = microbit_fs + self.microbit_fs = microbit_fs self.local_fs = local_fs self.set_font_size() layout.addWidget(microbit_label, 0, 0) @@ -889,9 +919,13 @@ def on_open_file(file): layout.addWidget(microbit_fs, 1, 0) layout.addWidget(local_fs, 1, 1) self.microbit_fs.disable.connect(self.disable) + self.microbit_fs.enable.connect(self.enable) self.microbit_fs.set_message.connect(self.show_message) + self.microbit_fs.pbar_update.connect(self.show_progressbar_update) self.local_fs.disable.connect(self.disable) + self.local_fs.enable.connect(self.enable) self.local_fs.set_message.connect(self.show_message) + self.local_fs.list_sub_files.connect(self.on_ls) def disable(self): """ @@ -908,8 +942,8 @@ def enable(self): """ self.microbit_fs.setDisabled(False) self.local_fs.setDisabled(False) - self.microbit_fs.setAcceptDrops(True) - self.local_fs.setAcceptDrops(True) + self.microbit_fs.setAcceptDrops(True) # Temporaraily + self.local_fs.setAcceptDrops(False) def show_message(self, message): """ @@ -923,7 +957,13 @@ def show_warning(self, message): """ self.set_warning.emit(message) - def on_ls(self, microbit_files): + def show_progressbar_update(self, amount): + """ + Emits the set_pbar_update signal. + """ + self.set_pbar_update.emit(amount) + + def on_ls(self, microbit_files, cwd=None): """ Displays a list of the files on the micro:bit. @@ -931,15 +971,22 @@ def on_ls(self, microbit_files): between Mu and the micro:bit, this enables the controls again for further interactions to take place. """ - self.microbit_fs.clear() + if cwd: + self.home = cwd # Update home directory + else: + cwd = self.home + if len(microbit_files): + self.microbit_fs.clear() + for f in microbit_files: + self.microbit_fs.addItem(f) self.local_fs.clear() - for f in microbit_files: - self.microbit_fs.addItem(f) local_files = [ f - for f in os.listdir(self.home) - if os.path.isfile(os.path.join(self.home, f)) + for f in os.listdir(cwd) ] + # We're not in a root directory + if os.path.dirname(cwd) != cwd: + local_files.insert(0, '..') # Add a item to go upper directory local_files.sort() for f in local_files: self.local_fs.addItem(f) @@ -971,6 +1018,7 @@ def on_put_fail(self, filename): "more information." ).format(filename) ) + self.enable() def on_delete_fail(self, filename): """ @@ -995,6 +1043,7 @@ def on_get_fail(self, filename): "more information." ).format(filename) ) + self.enable() def set_theme(self, theme): pass diff --git a/mu/locale/uz_UZ/LC_MESSAGES/mu.mo b/mu/locale/uz_UZ/LC_MESSAGES/mu.mo index 5e10f10ac..dc24a63e8 100644 Binary files a/mu/locale/uz_UZ/LC_MESSAGES/mu.mo and b/mu/locale/uz_UZ/LC_MESSAGES/mu.mo differ diff --git a/mu/locale/uz_UZ/LC_MESSAGES/mu.po b/mu/locale/uz_UZ/LC_MESSAGES/mu.po index da0e56fff..369cb963d 100644 --- a/mu/locale/uz_UZ/LC_MESSAGES/mu.po +++ b/mu/locale/uz_UZ/LC_MESSAGES/mu.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: mu-editor\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2023-11-25 00:20+0500\n" -"PO-Revision-Date: 2024-01-23 18:51\n" +"PO-Revision-Date: 2024-08-10 15:04\n" "Last-Translator: \n" "Language: uz\n" "Language-Team: Uzbek\n" @@ -294,7 +294,7 @@ msgstr "Tanlangan matnni REPL ga nusxalash" #: mu/interface/main.py:618 msgid "Filesystem on " -msgstr "Fayl tizimi yoqilgan" +msgstr "Fayl boshqaruv: " #: mu/interface/main.py:679 msgid "Python3 data tuple" @@ -412,11 +412,11 @@ msgstr "\"Mu\"da ochish" #: mu/interface/panes.py:825 msgid "Write to main.py on device" -msgstr "Qurilmadagi main.py ga yozing" +msgstr "Qurilmadagi main.py ga yozish" #: mu/interface/panes.py:828 msgid "Open" -msgstr "Ochish" +msgstr "Boshqa editorda ochish" #: mu/interface/panes.py:834 msgid "Opening '{}'" @@ -436,7 +436,7 @@ msgstr "Qurilmadagi fayllar roʻyxatini olishda muammo yuz berdi. Iltimos, texni #: mu/interface/panes.py:966 msgid "There was a problem copying the file '{}' onto the device. Please check Mu's logs for more information." -msgstr "“{}” faylini qurilmaga nusxalashda muammo yuz berdi. Qo'shimcha ma'lumot olish uchun Muning jurnallarini tekshiring." +msgstr "“{}” faylini qurilmaga nusxalashda muammo yuz berdi. Folderni nusxalash urinishi emas, aniq faylligini tekshiring. Va qo'shimcha ma'lumot olish uchun Muning jurnallarini tekshiring." #: mu/interface/panes.py:978 msgid "There was a problem deleting '{}' from the device. Please check Mu's logs for more information." @@ -513,7 +513,7 @@ msgstr "Nazariy jihatdan nazariya va amaliyot bir xil. Amalda, ular yo'q. ;-)" #: mu/logic.py:120 msgid "Debugging is twice as hard as writing the code in the first place." -msgstr "Nosozliklarni tuzatish kod yozishdan ko'ra ikki baravar qiyin." +msgstr "Xatolarni topib tuzatish birinchi navbatda kod yozishdan ikki baravar qiyin." #: mu/logic.py:121 msgid "It's fun to program." @@ -929,19 +929,23 @@ msgstr "" #: mu/modes/base.py:523 mu/modes/base.py:573 mu/modes/snek.py:526 msgid "Could not find an attached device." -msgstr "" +msgstr "Ulangan qurilmani topolmadi." #: mu/modes/base.py:524 msgid "Please make sure the device is plugged into this computer.\n\n" "It must have a version of MicroPython (or CircuitPython) flashed onto it before the REPL will work.\n\n" "Finally, press the device's reset button and wait a few seconds before trying again." -msgstr "" +msgstr "Qurilma ushbu kompyuterga ulanganligiga ishonch hosil qiling.\n\n" +"REPL ishlashidan oldin qurilmasi MicroPython (yoki CircuitPython) versiyasiga ega bo'lishi kerak.\n\n" +"Nihoyat, qurilmadagi qayta o'rnatish tugmasini bosing va qayta urinishdan oldin bir necha soniya kuting." #: mu/modes/base.py:574 msgid "Please make sure the device is plugged into this computer.\n\n" "It must have a version of MicroPython (or CircuitPython) flashed onto it before the Plotter will work.\n\n" "Finally, press the device's reset button and wait a few seconds before trying again." -msgstr "" +msgstr "Qurilma ushbu kompyuterga ulanganligiga ishonch hosil qiling.\n\n" +"Plotter ishlashidan oldin qurilmasi MicroPython (yoki CircuitPython) ga ega bo'lishi kerak.\n\n" +"Nihoyat, qurilmadagi qayta o'rnatish tugmasini bosing va qayta urinishdan oldin bir necha soniya kuting." #: mu/modes/circuitpython.py:37 msgid "CircuitPython" @@ -1079,7 +1083,7 @@ msgstr "" #: mu/modes/esp.py:87 mu/modes/microbit.py:145 msgid "Files" -msgstr "" +msgstr "Fayllar" #: mu/modes/esp.py:88 msgid "Access the file system on {board_name}." @@ -1095,45 +1099,47 @@ msgstr "" #: mu/modes/esp.py:135 mu/modes/microbit.py:519 msgid "REPL and file system cannot work at the same time." -msgstr "" +msgstr "REPL va fayl tizimi bir vaqtning o'zida ishlay olmaydi." #: mu/modes/esp.py:136 mu/modes/microbit.py:520 msgid "The REPL and file system both use the same USB serial connection. Only one can be active at any time. Toggle the file system off and try again." -msgstr "" +msgstr "REPL ham, fayl tizimi ham bir xil USB serial ulanishidan foydalanadi. Istalgan vaqtda faqat bitta faol boʻlishi mumkin. Fayl tizimini oʻchirib, qaytadan urinib koʻring." #: mu/modes/esp.py:155 mu/modes/microbit.py:539 msgid "The plotter and file system cannot work at the same time." -msgstr "" +msgstr "Plotter va fayl tizimi bir vaqtda ishlay olmaydi." #: mu/modes/esp.py:158 mu/modes/microbit.py:542 msgid "The plotter and file system both use the same USB serial connection. Only one can be active at any time. Toggle the file system off and try again." -msgstr "" +msgstr "Plotter ham, fayl tizimi ham bir xil USB serial ulanishidan foydalanadi. Istalgan vaqtda faqat bitta faol boʻlishi mumkin. Fayl tizimini oʻchirib, qaytadan urinib koʻring." #: mu/modes/esp.py:186 mu/modes/snek.py:417 msgid "Cannot run anything without any active editor tabs." -msgstr "" +msgstr "Agar faol tab bo'lmasa, hech narsa qilib bo'lmaydi." #: mu/modes/esp.py:187 mu/modes/snek.py:418 msgid "Running transfers the content of the current tab onto the device. It seems like you don't have any tabs open." -msgstr "" +msgstr "Ishga tushirishda tabdagi kodlarni qurilmaga o'tkazadi. Sizda hech qanday ochiq tab yo'qga o'xshaydi." #: mu/modes/esp.py:206 mu/modes/microbit.py:556 msgid "File system cannot work at the same time as the REPL or plotter." -msgstr "" +msgstr "Fayl tizimi REPL yoki plotter bilan bir vaqtda ishlay olmaydi." #: mu/modes/esp.py:210 mu/modes/microbit.py:560 msgid "The file system and the REPL and plotter use the same USB serial connection. Toggle the REPL and plotter off and try again." -msgstr "" +msgstr "Fayl tizimi va REPL va plotter bir xil USB serial ulanishidan foydalanadi. REPL va plotterni o'chiring va qayta urinib ko'ring." #: mu/modes/esp.py:237 msgid "Could not find an attached {board_name}" -msgstr "" +msgstr "Ulangan {board_name} ni topolmadi." #: mu/modes/esp.py:240 mu/modes/microbit.py:585 msgid "Please make sure the device is plugged into this computer.\n\n" "The device must have MicroPython flashed onto it before the file system will work.\n\n" "Finally, press the device's reset button and wait a few seconds before trying again." -msgstr "" +msgstr "Qurilma ushbu kompyuterga ulanganligiga ishonch hosil qiling.\n\n" +"Qurilma ishlashidan oldin qurilmasi MicroPython ga ega bo'lishi kerak.\n\n" +"Nihoyat, qurilmadagi qayta o'rnatish tugmasini bosing va qayta urinishdan oldin bir necha soniya kuting." #: mu/modes/esp.py:265 msgid "{board_name} board" @@ -1520,3 +1526,7 @@ msgstr "O'yinni tarqatish uchun bitta exe fayliga aylantiradi." msgid "Stop packaging your Pygame Zero game." msgstr "Bitta fayliga aylantirayotganini to'xtaydi." +#: #: mu/main.py:225 +msgid "There is un-saved work, closing the tab will cause you to lose it." +msgstr "Saqlanmagan ish bor, tabdan chiqish uni yo'qotishingizga olib keladi." + diff --git a/mu/logic.py b/mu/logic.py index e2ec1e75f..02b598765 100644 --- a/mu/logic.py +++ b/mu/logic.py @@ -98,6 +98,7 @@ "boy_3.png", "desert.png", "treasure_box.png", + "treasure_box_o.png", "field.png", "mole.png", "toy_hammer.png" @@ -130,8 +131,82 @@ "5_count_boy.py", "6_treasure_box.py", "7_mole_game.py", - "pgzhelper.py" ] +EXAMPLE_PGZ_IMAGES = [ + "bird0.png", + "bird1.png", + "bird2.png", + "birddead.png", + "top.png", + "bottom.png", + "background.png", + "background1.png", + "background2.png", + "ball.png", + "bar.png", + "block.png", + "space.jpg", + "enemy_bullet.png", + "enemy1_1.png", + "enemy1_2.png", + "explosion1.png", + "explosion2.png", + "grass.png", + "player_bullet.png", + "player.png", + "tank_blue.png", + "tank_dark.png", + "tank_green.png", + "tank_red.png", + "tank_sand.png", + "wall.png", + "bulletred2.png", + "bulletblue2.png", + "explosion3.png", + "explosion4.png", +] +EXAMPLE_PGZ_SOUNDS = [ + "block.wav", + "wall.wav", + "bar.wav", + "die.wav", + "win.wav", + "sfx_exp_medium12.wav", + "sfx_sounds_interaction25.wav", +] +EXAMPLE_PGZ_MUSIC = [ + "main_theme.mp3", +] +EXAMPLE_PGZ = [ + "flappybird.py", + "flappybird_neosoco.py", + "battle_city.py", + "breakout.py", + "twinbee.py", +] +EXAMPLE_NEOPIA = [ + "01-01_KobiBot.py", + "01-02_FutbolBot.py", + "01-06_Sehrli_ansanmbl.py", + "02-01-01_Barami.py", + "02-01-02_Barami.py", + "02-02_Neo_Bigl.py", + "02-03_Neo_kliner.py", + "02-04-01_Aqlli_avtomobil.py", + "02-04-02_Aqlli_avtomobil.py", + "02-05-01_Chiziq_trekeri.py", + "02-05-02_Chiziq_trekeri.py", + "03-01_AI_Radiokarnay.py", + "03-02_AI_Kamera.py", + "03-03_AI_Obyekt_topish.py", + "04-01_Fonar.py", + "04-03-01_Aqlli_avtomobil_2.py", + "04-03-02_Aqlli_avtomobil_2.py", + "04-04_Samosval_avtomobili.py", + "04-05_Robot_qol.py", + "04-06_Konveyer.py" +] + MOTD = [ # Candidate phrases for the message of the day (MOTD). _("Hello, World!"), _( @@ -863,6 +938,10 @@ def on_open_file(file): # Open the file self.direct_load(file) + @view.selected_text.connect + def on_selected_text(text): + self.find = text + def setup(self, modes): """ Define the available modes and ensure there's a default working @@ -876,16 +955,18 @@ def setup(self, modes): if not os.path.exists(wd): logger.debug("Creating directory: {}".format(wd)) os.makedirs(wd) - # Ensure PyGameZero assets are copied over. + # Place picozero Lib to root directory shutil.copy( - path("pgzhelper.py", "pygamezero/"), os.path.join(wd, "pgzhelper.py") + path("picozero.py", "pico/"), os.path.join(wd, "picozero.py") ) images_path = os.path.join(wd, "images") fonts_path = os.path.join(wd, "fonts") sounds_path = os.path.join(wd, "sounds") music_path = os.path.join(wd, "music") examples_path = os.path.join(wd, "examples") - entry_basic_path = os.path.join(wd, "examples/entry_basic/") + example_entry_b_path = os.path.join(wd, "examples/entry_basic/") + example_pgz_path = os.path.join(wd, "examples/pygame_zero/") + example_neopia_path = os.path.join(wd, "examples/neopia/") if not os.path.exists(images_path): logger.debug("Creating directory: {}".format(images_path)) os.makedirs(images_path) @@ -921,34 +1002,72 @@ def setup(self, modes): if not os.path.exists(examples_path): logger.debug("Creating directory: {}".format(examples_path)) os.makedirs(examples_path) - if not os.path.exists(entry_basic_path): - logger.debug("Creating directory: {}".format(entry_basic_path)) - os.makedirs(entry_basic_path) + # Entry basic examples + if not os.path.exists(example_entry_b_path): + logger.debug("Creating directory: {}".format(example_entry_b_path)) + os.makedirs(example_entry_b_path) for sfx in EXAMPLE_ENTRY_BASIC: shutil.copy( - path(sfx, "pygamezero/"), os.path.join(entry_basic_path, sfx) + path(sfx, "pygamezero/"), os.path.join(example_entry_b_path, sfx) ) - if not os.path.exists(entry_basic_path + 'images'): + if not os.path.exists(example_entry_b_path + 'images'): logger.debug("Creating directory: {}".format('images')) - os.makedirs(entry_basic_path + 'images') + os.makedirs(example_entry_b_path + 'images') for sfx in EXAMPLE_ENTRY_B_IMAGES: shutil.copy( - path(sfx, "pygamezero/"), os.path.join(entry_basic_path + 'images', sfx) + path(sfx, "pygamezero/"), os.path.join(example_entry_b_path + 'images', sfx) ) - if not os.path.exists(entry_basic_path + 'sounds'): + if not os.path.exists(example_entry_b_path + 'sounds'): logger.debug("Creating directory: {}".format('sounds')) - os.makedirs(entry_basic_path + 'sounds') + os.makedirs(example_entry_b_path + 'sounds') for sfx in EXAMPLE_ENTRY_B_SOUNDS: shutil.copy( - path(sfx, "pygamezero/"), os.path.join(entry_basic_path + 'sounds', sfx) + path(sfx, "pygamezero/"), os.path.join(example_entry_b_path + 'sounds', sfx) ) - if not os.path.exists(entry_basic_path + 'fonts'): + if not os.path.exists(example_entry_b_path + 'fonts'): logger.debug("Creating directory: {}".format('fonts')) - os.makedirs(entry_basic_path + 'fonts') + os.makedirs(example_entry_b_path + 'fonts') for sfx in EXAMPLE_ENTRY_B_FONTS: shutil.copy( - path(sfx, "pygamezero/"), os.path.join(entry_basic_path + 'fonts', sfx) + path(sfx, "pygamezero/"), os.path.join(example_entry_b_path + 'fonts', sfx) ) + # Pygame zero examples + if not os.path.exists(example_pgz_path): + logger.debug("Creating directory: {}".format(example_pgz_path)) + os.makedirs(example_pgz_path) + for sfx in EXAMPLE_PGZ: + shutil.copy( + path(sfx, "pygamezero/"), os.path.join(example_pgz_path, sfx) + ) + if not os.path.exists(example_pgz_path + 'images'): + logger.debug("Creating directory: {}".format('images')) + os.makedirs(example_pgz_path + 'images') + for sfx in EXAMPLE_PGZ_IMAGES: + shutil.copy( + path(sfx, "pygamezero/"), os.path.join(example_pgz_path + 'images', sfx) + ) + if not os.path.exists(example_pgz_path + 'sounds'): + logger.debug("Creating directory: {}".format('sounds')) + os.makedirs(example_pgz_path + 'sounds') + for sfx in EXAMPLE_PGZ_SOUNDS: + shutil.copy( + path(sfx, "pygamezero/"), os.path.join(example_pgz_path + 'sounds', sfx) + ) + if not os.path.exists(example_pgz_path + 'music'): + logger.debug("Creating directory: {}".format('music')) + os.makedirs(example_pgz_path + 'music') + for sfx in EXAMPLE_PGZ_MUSIC: + shutil.copy( + path(sfx, "pygamezero/"), os.path.join(example_pgz_path + 'music', sfx) + ) + # Neopia examples + if not os.path.exists(example_neopia_path): + logger.debug("Creating directory: {}".format(example_neopia_path)) + os.makedirs(example_neopia_path) + for sfx in EXAMPLE_NEOPIA: + shutil.copy( + path(sfx, "neopia/"), os.path.join(example_neopia_path, sfx) + ) # Ensure Web based assets are copied over. template_path = os.path.join(wd, "templates") static_path = os.path.join(wd, "static") @@ -1059,7 +1178,9 @@ def restore_session(self, paths=None): if "locale" in old_session: self.user_locale = old_session["locale"].strip() if self.user_locale: + logging.info("locale in old session: {}".format(self.user_locale)) i18n.set_language(self.user_locale) + self.modes[self.mode].code_template = _("# Write your code here :-)") old_window = old_session.get("window", {}) self._view.size_window(**old_window) @@ -1711,6 +1832,7 @@ def change_mode(self, mode): tab.breakpoint_handles = set() tab.reset_annotations() self.modes[mode].ensure_state() + self.modes[self.mode].code_template = _("# Write your code here :-)") self.show_status_message( _("Changed to {} mode.").format(self.modes[mode].name) ) @@ -1773,6 +1895,12 @@ def show_status_message(self, message, duration=5): """ self._view.status_bar.set_message(message, duration * 1000) + def show_progressbar_update(self, amount): + """ + Displays the referenced amount on the progress bar. + """ + self._view.status_bar.set_pbar_value(amount) + def debug_toggle_breakpoint(self, margin, line, modifiers): """ How to handle the toggling of a breakpoint. @@ -1934,11 +2062,23 @@ def toggle_comments(self): """ self._view.toggle_comments() - def delete_complete_line(self): + def close_tab(self): """ - Handle a shortcut for deleting a complete line on the current cursor. + Handle a shortcut for closing the current tab. """ - self._view.toggle_comments() + self._view.close_tab() + + def move_forward_tab(self): + """ + Handle a shortcut for moving the current tab forward. + """ + self._view.move_tab(self._view.current_tab, True) + + def move_backward_tab(self): + """ + Handle a shortcut for moving the current tab backward. + """ + self._view.move_tab(self._view.current_tab, False) def tidy_code(self): """ diff --git a/mu/modes/api/__init__.py b/mu/modes/api/__init__.py index 47f7963c9..d8523e680 100644 --- a/mu/modes/api/__init__.py +++ b/mu/modes/api/__init__.py @@ -10,6 +10,7 @@ from .pyboard import PYBOARD_APIS from .lego import LEGO_APIS from .neopia import NEOPIA_APIS +from .pico import PICO_APIS __all__ = [ "ADAFRUIT_APIS", @@ -24,4 +25,5 @@ "PYBOARD_APIS", "LEGO_APIS", "NEOPIA_APIS", + "PICO_APIS", ] diff --git a/mu/modes/api/neopia.py b/mu/modes/api/neopia.py index 7800fe3be..c75d522fc 100644 --- a/mu/modes/api/neopia.py +++ b/mu/modes/api/neopia.py @@ -2,7 +2,7 @@ Contains definitions for the Neopia related APIs so they can be used in the editor for autocomplete and call tips. -Copyright (c) 2015-2017 Nicholas H.Tollervey and others (see the AUTHORS file). +Copyright (c) 2024- Roboticsware and others (see the AUTHORS file). This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ "get_value(port) \n\nport: 'in1' ~ 'in3', 'remo', 'bat' \nGet a value from input port." ), _( - "set_value(port, value) \n\nport: 'out1' ~ 'out3', 'all' \value to set: 0 ~ 255 \nSet a value to output port." + "set_value(port, value) \n\nport: 'out1' ~ 'out3', 'all' \nvalue to set: 0 ~ 255 \nSet a value to output port." ), _( "convert_scale(port, 0, 255, 0, 100) \n\nport: 'in1' ~ 'in3' \nparam2, param3 - min and max value of original scale: 0 ~ 255 \nparam3, param4 - min and max value of scale to be converted \nConvert a range of values from input port to another." @@ -80,61 +80,61 @@ ], 'uz_UZ': [ _( - "get_value(port) \n\nport: 'in1' ~ 'in3', 'remo', 'bat' \nGet a value from input port." + "get_value(port) \n\nport: 'in1' ~ 'in3', 'pult', 'batareya' \nKiritish portidan qiymat olish." ), _( - "set_value(port, value) \n\nport: 'out1' ~ 'out3', 'all' \value to set: 0 ~ 255 \nSet a value to output port." + "set_value(port, qiymat) \n\nport: 'out1' ~ 'out3', 'all' \nqiymat o'rnatish: 0 ~ 255 \nChiqish portiga qiymat o'rnatish." ), _( - "convert_scale(port, 0, 255, 0, 100) \n\nport: 'in1' ~ 'in3' \nparam2, param3 - min and max value of original scale: 0 ~ 255 \nparam3, param4 - min and max value of scale to be converted \nConvert a range of values from input port to another." - ), + "convert_scale(port, 0, 255, 0, 100) \n\nport: 'in1' ~ 'in3' \n2-param, 3-param - asl o'lchovning minimal va maksimal qiymatlari: 0 ~ 255 \n4-param, 5-param - o'zgartirilgan o'lchovning minimal va maksimal qiymatlari \nKiritish porti qiymat o'lchovlari oralig'ini boshqa qiymatga o'zgartirish." + ), _( - "color_check(port, color) \n\nport: 'in1' ~ 'in3' \ncolor: 'white', 'green', etc \nDetect color from input port." + "color_check(port, rang) \n\nport: 'in1' ~ 'in3' \nrang: 'white', 'green', va hkz \nKiritish portidan rangni aniqlash." ), _( - "led_on(port, brightness) \n\nport: 'out1' ~ 'out3', 'all' \npercentage of brightness: '0', '10' ~ '100' \nTurn on LED with brightness adjustment." + "led_on(port, yorug'lik) \n\nport: 'out1' ~ 'out3', 'all' \nyorug'likni foizi: '0', '10' ~ '100' \nYorug'likni sozlab LEDni yoqish." ), _( - "led_off(port) \n\nport: 'out1' ~ 'out3', 'all' \nTurn off LED." + "led_off(port) \n\nport: 'out1' ~ 'out3', 'all' \nLEDni o'chirish." ), _( - "color_led_on(port, 255, 0, 0) \n\nport: 'out1' ~ 'out3', 'all' \nparam1 - Red: 0 ~ 255 \nparam2 - Green: 0 ~ 255 \nparam3 - Blue: 0 ~ 255 \nTurn on color LED with color." + "color_led_on(port, 255, 0, 0) \n\nport: 'out1' ~ 'out3', 'all' \n1-param - Qizil: 0 ~ 255 \n2-param - Yashil: 0 ~ 255 \n3-param - Moviy: 0 ~ 255 \nRangli LEDni yorug'likni sozlab yoqish." ), _( - "motor_move(direction) \n\ndirection: 'forward', 'backward', 'left', 'right', 'stop' \nMove forward by motor." + "motor_move(yo'nalish) \n\nyo'nalish: 'forward', 'backward', 'left', 'right', 'stop'\nMa'nosi 'oldinga', 'orqaga', 'chapga', 'o'nga', 'to'xtash' \nMotor harakatlantirish." ), _( - "motor_rotate(motor, direction, speed) \n\nmotor: 'both', 'left', 'right' \ndirection: 'forward', 'backward', 'left', 'right', 'stop' \nspeed percentage or input port: '0', '10' ~ '100', 'in1' ~ 'in3' \nRotate DC motor by a count of motor, direction and speed." + "motor_rotate(motor, yo'nalish, tezlik) \n\nmotor: 'both', 'left', 'right' (ikkalasi, chap, o'ng) \nyo'nalish: 'forward', 'backward', 'left', 'right', 'stop' \ntezlik foizi yoki kirish porti: '0', '10' ~ '100', 'in1' ~ 'in3' \nDC motorni qaysi motor, yo'nalish va tezlikni kiritib aylantirish." ), _( - "motor_stop('right') \n\nposition of motor: 'both', 'left', 'right' \nStop motor." + "motor_stop('right') \n\nqaysi motor: 'both', 'left', 'right' \nMotorni to'xtatish." ), _( - "buzzer(pitch, note, length) \n\npitch: '1' ~ '6' \nnote: 'c ', 'c#', 'db', 'd' ~ 'b' \nlength of note: '2', '4', '8', '16' \nPlay a sound by pitch, note with sharp and flat, and a length of note." + "buzzer(ton, nota, uzunlik) \n\nton: '1' ~ '6' \nnota: 'c ', 'c#', 'db', 'd' ~ 'b' \nnota uzunligi: '2', '4', '8', '16' \nTon/ohang, o'tkir va yassi nota va nota uzunligi bilan tovush chiqarish." ), _( - "buzzer_by_port(param) \n\nparam - port: 'in1' ~ 'in3' \nPlay a sound by value from input port." + "buzzer_by_port(param) \n\nparam - port: 'in1' ~ 'in3' \nKiritish porti qiymatidan tovush chiqarish." ), _( - "buzzer_stop() \n\nStop buzzer." + "buzzer_stop() \n\nBuzzerni to'xtatish." ), _( - "get_angle(port) \n\nport: 'in1' ~ 'in3' \nGet a degree of an angle sensor." + "get_angle(port) \n\nport: 'in1' ~ 'in3' \nBurchak sensori darajasini olish." ), _( - "servo_rotate(port, direction, speed) \n\noutput port: 'out1', 'out2', 'out3' \ndirection: 'forward', 'backward' \nspeed percentage or input port: '0', '10' ~ '100', 'in1' ~ 'in3' \nRotate servo motor by direction and speed." + "servo_rotate(port, yo'nalish, tezlik) \n\nchiqish porti: 'out1', 'out2', 'out3' \nYo'nalish: 'forward', 'backward' \ntezlik foizi yoki kirish porti: '0', '10' ~ '100', 'in1' ~ 'in3' \nServo motorni yo'nalish va tezlikni kiritib aylantirish." ), _( - "servo_reset_degree(port) \noutput port: 'out1' ~ 'out3', 'all' \n\nMake 0 degree where servo motor is currently." + "servo_reset_degree(port) \n\nchiqish porti: 'out1' ~ 'out3', 'all' \nServo motorning holatini 0 darajaga sozlash." ), _( - "servo_rotate_by_degree(port, direction, speed, angle) \n\noutput port: 'out1', 'out2', 'out3' \ndirection: 'forward', 'backward' \nspeed percentage or input port: '0', '10' ~ '100', 'in1' ~ 'in3' \ndegree of angle: '0', '5' ~ '180', 'in1' ~ 'in3' \nRotate servo motor by direction, speed and degree of angle." + "servo_rotate_by_degree(port, yo'nalish, tezlik, burchak) \n\nchiqish porti: 'out1', 'out2', 'out3' \nyo'nalish: 'forward', 'backward' \ntezlik foizi yoki kirish porti: '0', '10' ~ '100', 'in1' ~ 'in3' \nburchak darajasi yoki kirish porti: '0', '5' ~ '180', 'in1' ~ 'in3' \nServo motorni yo'nalish, tezlik va burchak darajasini sozlash." ), _( - "servo_stop(port) \n\noutput port: 'out1' ~ 'out3', 'all' \nStop servo motor." + "servo_stop(port) \n\nchiqish porti: 'out1' ~ 'out3', 'all' \nServo motorni to'xtatish." ), _( - "remote_button(button) \n\nbutton of RC: '1', '2', '3', 'up', 'left', 'right', 'down' \nCheck if the button of remote controller is pressed." + "remote_button(button) \n\nPult tugmasi: '1', '2', '3', 'up', 'left', 'right', 'down' \nMasofadan boshqarish tugmasi bosilganligini tekshirish." ) ], } \ No newline at end of file diff --git a/mu/modes/api/pico.py b/mu/modes/api/pico.py new file mode 100644 index 000000000..e2672b5d3 --- /dev/null +++ b/mu/modes/api/pico.py @@ -0,0 +1,119 @@ +""" +Contains definitions for the PI Pico related APIs so they can be +used in the editor for autocomplete and call tips. + +Copyright (c) 2024- Roboticsware and others (see the AUTHORS file). + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" + + +PICO_APIS = { + 'en_US': [ + _('AnalogInputDevice.is_active() \n\nReturns :data:`True` if the device is active.'), + _('AnalogInputDevice.threshold() \n\nThe threshold that the device must be above or below to be\nconsidered active. The default is 0.5.'), + _('AnalogInputDevice.voltage() \n\nReturns the voltage of the analogue device.'), + _('DigitalInputDevice.close() \n\nCloses the device and releases any resources. Once closed, the device\ncan no longer be used.'), + _('DigitalInputDevice.is_active() \n\nReturns :data:`True` if the device is active.'), + _('DigitalInputDevice.is_inactive() \n\nReturns :data:`True` if the device is inactive.'), + _('DigitalInputDevice.when_activated() \n\nReturns a :samp:`callback` that will be called when the device is activated.'), + _('DigitalInputDevice.when_deactivated() \n\nReturns a :samp:`callback` that will be called when the device is deactivated.'), + _('DigitalOutputDevice.close() \n\nCloses the device and turns the device off. Once closed, the device\ncan no longer be used.'), + _('DistanceSensor.distance() \n\nReturns the current distance measured by the sensor in meters. Note \nthat this property will have a value between 0 and max_distance.'), + _('DistanceSensor.max_distance() \n\nReturns the maximum distance that the sensor will measure in metres.'), + _('DistanceSensor.value() \n\nReturns a value between 0, indicating the reflector is either touching \nthe sensor or is sufficiently near that the sensor can’t tell the \ndifference, and 1, indicating the reflector is at or beyond the \nspecified max_distance. A return value of None indicates that the\necho was not received before the timeout.'), + _('InputDevice.active_state() \n\nSets or returns the active state of the device. If :data:`None` (the default),\nthe device will return the value that the pin is set to. If\n:data:`True`, the device will return :data:`True` if the pin is\nHIGH. If :data:`False`, the device will return :data:`False` if the\npin is LOW.'), + _('InputDevice.value() \n\nReturns the current value of the device. This is either :data:`True`\nor :data:`False` depending on the value of :attr:`active_state`.'), + _('Motor.backward([speed], [t], [wait]) \n\nMakes the motor turn "backward".\n\n:param float speed:\n The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1.\n\n:param float t:\n The time in seconds that the motor should turn for. If None is \n specified, the motor will stay on. The default is None.\n\n:param bool wait:\n If True, the method will block until the time `t` has expired. \n If False, the method will return and the motor will turn on in\n the background. Defaults to False. Only effective if `t` is not\n None.'), + _('Motor.close() \n\nCloses the device and releases any resources. Once closed, the device\ncan no longer be used.'), + _('Motor.forward([speed], [t], [wait]) \n\nMakes the motor turn "forward".\n\n:param float speed:\n The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1.\n\n:param float t:\n The time in seconds that the motor should turn for. If None is \n specified, the motor will stay on. The default is None.\n\n:param bool wait:\n If True, the method will block until the time `t` has expired. \n If False, the method will return and the motor will turn on in\n the background. Defaults to False. Only effective if `t` is not\n None.'), + _('Motor.off() \n\nStops the motor turning.'), + _('Motor.on([speed], [t], [wait]) \n\nTurns the motor on and makes it turn.\n\n:param float speed:\n The speed as a value between -1 and 1: 1 turns the motor at\n full speed in one direction, -1 turns the motor at full speed in\n the opposite direction. Defaults to 1.\n\n:param float t:\n The time in seconds that the motor should run for. If None is \n specified, the motor will stay on. The default is None.\n\n:param bool wait:\n If True, the method will block until the time `t` has expired. \n If False, the method will return and the motor will turn on in\n the background. Defaults to False. Only effective if `t` is not\n None.'), + _('Motor.value() \n\nSets or returns the motor speed as a value between -1 and 1: -1 is full\nspeed "backward", 1 is full speed "forward", 0 is stopped.'), + _('OutputDevice.active_high() \n\nSets or returns the active_high property. If :data:`True`, the\n:meth:`on` method will set the Pin to HIGH. If :data:`False`,\nthe :meth:`on` method will set the Pin to LOW (the :meth:`off` method\nalways does the opposite).'), + _('OutputDevice.blink([on_time], [off_time], [n], [wait]) \n\nMakes the device turn on and off repeatedly.\n\nArgs:\n on_time (float): The length of time in seconds that the\n device will be on. Defaults to 1.\n off_time (float): The length of time in seconds that the\n device will be off. If `None`, it will be the same as\n ``on_time``. Defaults to `None`.\n n (int): The number of times to repeat the blink operation.\n If None is specified, the device will continue blinking\n forever. The default is None.\n wait (bool): If True, the method will block until the device\n stops turning on and off. If False, the method will\n return and the device will turn on and off in the\n background. Defaults to False.'), + _('OutputDevice.close() \n\nTurns the device off.'), + _('OutputDevice.is_active() \n\nReturns :data:`True` if the device is on.'), + _('OutputDevice.off() \n\nTurns the device off.'), + _('OutputDevice.on([value], [t], [wait]) \n\nTurns the device on.\n\nArgs:\n value (float): The value to set when turning on. Defaults to\n 1.\n t (float): The time in seconds that the device should be on.\n If None is specified, the device will stay on. The\n default is None.\n wait (bool): If True, the method will block until the time\n `t` has expired. If False, the method will return and\n the device will turn on in the background. Defaults to\n False. Only effective if `t` is not None.'), + _('OutputDevice.toggle() \n\nIf the device is off, turn it on. If it is on, turn it off.'), + _('OutputDevice.value() \n\nSets or returns a value representing the state of the device: 1 is on, 0 is off.'), + _('PWMLED.LED([pwm], [active_high], [initial_value]) \n\nReturns an instance of :class:`DigitalLED` or :class:`PWMLED` depending on\nthe value of the `pwm` parameter. \n\n::\n\n from picozero import LED\n\n my_pwm_led = LED(1)\n\n my_digital_led = LED(2, pwm=False)\n\n:param int pin:\n The pin that the device is connected to.\n\n:param int pin:\n If `pwm` is :data:`True` (the default), a :class:`PWMLED` will be\n returned. If `pwm` is :data:`False`, a :class:`DigitalLED` will be\n returned. A :class:`PWMLED` can control the brightness of the LED but\n uses 1 PWM channel.\n\n:param bool active_high:\n If :data:`True` (the default), the :meth:`on` method will set the Pin\n to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to\n LOW (the :meth:`off` method always does the opposite).\n\n:param bool initial_value:\n If :data:`False` (the default), the device will be off initially. If\n :data:`True`, the device will be switched on initially.'), + _('PWMOutputDevice.blink([on_time], [off_time], [n], [wait], [fade_in_time], [fade_out_time], [fps]) \n\nMakes the device turn on and off repeatedly.\n\n:param float on_time:\n The length of time in seconds the device will be on. Defaults to 1.\n\n:param float off_time:\n The length of time in seconds the device will be off. If `None`, \n it will be the same as ``on_time``. Defaults to `None`.\n\n:param int n:\n The number of times to repeat the blink operation. If `None`, the \n device will continue blinking forever. The default is `None`.\n\n:param bool wait:\n If True, the method will block until the LED stops blinking. If False,\n the method will return and the LED will blink in the background.\n Defaults to False.\n\n:param float fade_in_time:\n The length of time in seconds to spend fading in. Defaults to 0.\n\n:param float fade_out_time:\n The length of time in seconds to spend fading out. If `None`,\n it will be the same as ``fade_in_time``. Defaults to `None`.\n\n:param int fps:\n The frames per second that will be used to calculate the number of\n steps between off/on states when fading. Defaults to 25.'), + _('PWMOutputDevice.close() \n\nCloses the device and turns the device off. Once closed, the device\ncan no longer be used.'), + _('PWMOutputDevice.freq() \n\nReturns the current frequency of the device.'), + _('PWMOutputDevice.freq(freq) \n\nSets the frequency of the device.'), + _('PWMOutputDevice.is_active() \n\nReturns :data:`True` if the device is on.'), + _('PWMOutputDevice.pulse([fade_in_time], [fade_out_time], [n], [wait], [fps]) \n\nMakes the device pulse on and off repeatedly.\n\n:param float fade_in_time:\n The length of time in seconds that the device will take to turn on.\n Defaults to 1.\n\n:param float fade_out_time:\n The length of time in seconds that the device will take to turn off.\n Defaults to 1.\n \n:param int fps:\n The frames per second that will be used to calculate the number of\n steps between off/on states. Defaults to 25.\n \n:param int n:\n The number of times to pulse the LED. If None, the LED will pulse\n forever. Defaults to None.\n\n:param bool wait:\n If True, the method will block until the LED stops pulsing. If False,\n the method will return and the LED will pulse in the background.\n Defaults to False.'), + _('PinMixin.pin() \n\nReturns the pin number used by the device.'), + _('PinsMixin.pins() \n\nReturns a tuple of pins used by the device.'), + _('RGBLED.blink([on_times], [fade_times], [colors], [n], [wait], [fps]) \n\nMakes the device blink between colours repeatedly.\n\nArgs:\n on_times (float): Single value or tuple of numbers of\n seconds to stay on each colour. Defaults to 1 second.\n fade_times (float): Single value or tuple of times to fade\n between each colour. Must be 0 if *pwm* was\n :data:`False` when the class was constructed.\n colors (tuple Tuple of colours to blink between, use ``(0, 0, 0)`` for off.):\n The colours to blink between. Defaults to red, green,\n blue.\n n (int or None): Number of times to blink; :data:`None` (the\n default) means forever.\n wait (bool): If :data:`False` (the default), use a Timer to\n manage blinking, continue blinking, and return\n immediately. If :data:`False`, only return when the\n blinking is finished (warning: the default value of *n*\n will result in this method never returning).'), + _('RGBLED.blue() \n\nRepresents the blue component of the LED as a value between 0 and 255 if *pwm* was :data:`True`\nwhen the class was constructed (but only takes values of 0 or 255 otherwise).'), + _('RGBLED.color() \n\nRepresents the colour of the LED as an RGB 3-tuple of ``(red, green,\nblue)`` where each value is between 0 and 255 if *pwm* was :data:`True`\nwhen the class was constructed (but only takes values of 0 or 255 otherwise).\nFor example, red would be ``(255, 0, 0)`` and yellow would be ``(255, 255,\n0)``, whereas orange would be ``(255, 127, 0)``.'), + _('RGBLED.cycle([fade_times], [colors], [n], [wait], [fps]) \n\nMakes the device fade in and out repeatedly.\n\nArgs:\n fade_times (float): Number of seconds to spend fading out.\n Defaults to 1.\n colors (tuple)\n on_color: Tuple of colours to cycle between. Defaults to\n red, green, blue.\n n (int or None): Number of times to cycle; :data:`None` (the\n default) means forever.'), + _('RGBLED.green() \n\nRepresents the green component of the LED as a value between 0 and 255 if *pwm* was :data:`True`\nwhen the class was constructed (but only takes values of 0 or 255 otherwise).'), + _('RGBLED.invert() \n\nInverts the state of the device. If the device is currently off\n(:attr:`value` is ``(0, 0, 0)``), this changes it to "fully" on\n(:attr:`value` is ``(1, 1, 1)``). If the device has a specific colour,\nthis method inverts the colour.'), + _('RGBLED.is_active() \n\nReturns :data:`True` if the LED is currently active (not black) and\n:data:`False` otherwise.'), + _('RGBLED.on() \n\nTurns the LED on. This is equivalent to setting the LED color to white, e.g.\n``(1, 1, 1)``.'), + _('RGBLED.pulse([fade_times], [colors], [n], [wait], [fps]) \n\nMakes the device fade between colours repeatedly.\n\nArgs:\n fade_times (float): Single value or tuple of numbers of\n seconds to spend fading. Defaults to 1.\n fade_out_time (float): Number of seconds to spend fading\n out. Defaults to 1.\n colors (tuple)\n on_color: Tuple of colours to pulse between in order.\n Defaults to red, off, green, off, blue, off.\n off_color (~colorzero.Color or tuple)\n n (int or None): Number of times to pulse; :data:`None` (the\n default) means forever.'), + _('RGBLED.red() \n\nRepresents the red component of the LED as a value between 0 and 255 if *pwm* was :data:`True`\nwhen the class was constructed (but only takes values of 0 or 255 otherwise).'), + _('RGBLED.toggle() \n\nToggles the state of the device. If the device has a specific colour, then that colour is saved and the device is turned off.\nIf the device is off, it will be changed to the last colour it had when it was on or, if none, to fully on (:attr:`value` is ``(1, 1, 1)``).'), + _('RGBLED.value() \n\nRepresents the colour of the LED as an RGB 3-tuple of ``(red, green,\nblue)`` where each value is between 0 and 1 if *pwm* was :data:`True`\nwhen the class was constructed (but only takes values of 0 or 1 otherwise).\nFor example, red would be ``(1, 0, 0)`` and yellow would be ``(1, 1,\n0)``, whereas orange would be ``(1, 0.5, 0)``.'), + _('Robot.backward([speed], [t], [wait]) \n\nMakes the robot move "backward".\n\nArgs:\n speed (float): The speed as a value between 0 and 1: 1 is\n full speed, 0 is stop. Defaults to 1.\n t (float): The time in seconds that the robot should move\n for. If None is specified, the robot will continue to\n move until stopped. The default is None.\n wait (bool): If True, the method will block until the time\n `t` has expired. If False, the method will return and\n the motor will turn on in the background. Defaults to\n False. Only effective if `t` is not None.'), + _('Robot.close() \n\nCloses the device and releases any resources. Once closed, the device\ncan no longer be used.'), + _('Robot.forward([speed], [t], [wait]) \n\nMakes the robot move "forward".\n\nArgs:\n speed (float): The speed as a value between 0 and 1: 1 is\n full speed, 0 is stop. Defaults to 1.\n t (float): The time in seconds that the robot should move\n for. If None is specified, the robot will continue to\n move until stopped. The default is None.\n wait (bool): If True, the method will block until the time\n `t` has expired. If False, the method will return and\n the motor will turn on in the background. Defaults to\n False. Only effective if `t` is not None.'), + _('Robot.left([speed], [t], [wait]) \n\nMakes the robot turn "left" by turning the left motor backward and the\nright motor forward.\n\nArgs:\n speed (float): The speed as a value between 0 and 1: 1 is\n full speed, 0 is stop. Defaults to 1.\n t (float): The time in seconds that the robot should turn\n for. If None is specified, the robot will continue to\n turn until stopped. The default is None.\n wait (bool): If True, the method will block until the time\n `t` has expired. If False, the method will return and\n the motor will turn on in the background. Defaults to\n False. Only effective if `t` is not None.'), + _('Robot.left_motor() \n\nReturns the left :class:`Motor`.'), + _('Robot.right([speed], [t], [wait]) \n\nMakes the robot turn "right" by turning the left motor forward and the\nright motor backward.\n\nArgs:\n speed (float): The speed as a value between 0 and 1: 1 is\n full speed, 0 is stop. Defaults to 1.\n t (float): The time in seconds that the robot should turn\n for. If None is specified, the robot will continue to\n turn until stopped. The default is None.\n wait (bool): If True, the method will block until the time\n `t` has expired. If False, the method will return and\n the motor will turn on in the background. Defaults to\n False. Only effective if `t` is not None.'), + _('Robot.right_motor() \n\nReturns the right :class:`Motor`.'), + _('Robot.stop() \n\nStops the robot.'), + _('Robot.value() \n\nRepresents the motion of the robot as a tuple of (left_motor_speed,\nright_motor_speed) with ``(-1, -1)`` representing full speed backwards,\n``(1, 1)`` representing full speed forwards, and ``(0, 0)``\nrepresenting stopped.'), + _('Servo.max() \n\nSet the servo to its maximum position.'), + _('Servo.mid() \n\nSet the servo to its mid-point position.'), + _('Servo.min() \n\nSet the servo to its minimum position.'), + _('Servo.off() \n\nTurn the servo "off" by setting the value to `None`.'), + _('Speaker.beep([on_time], [off_time], [n], [wait], [fade_in_time], [fade_out_time], [fps]) \n\nMakes the buzzer turn on and off repeatedly.\n\nArgs:\n on_time (float): The length of time in seconds that the\n device will be on. Defaults to 1.\n off_time (float): The length of time in seconds that the\n device will be off. If `None`, it will be the same as\n ``on_time``. Defaults to `None`.\n n (int): The number of times to repeat the beep operation.\n If `None`, the device will continue beeping forever. The\n default is `None`.\n wait (bool): If True, the method will block until the buzzer\n stops beeping. If False, the method will return and the\n buzzer will beep in the background. Defaults to False.\n fade_in_time (float): The length of time in seconds to spend\n fading in. Defaults to 0.\n fade_out_time (float): The length of time in seconds to\n spend fading out. If `None`, it will be the same as\n ``fade_in_time``. Defaults to `None`.\n fps (int): The frames per second that will be used to\n calculate the number of steps between off/on states when\n fading. Defaults to 25.'), + _('Speaker.freq() \n\nSets or returns the current frequency of the speaker.'), + _('Speaker.play([tune], [duration], [volume], [n], [wait]) \n\nPlays a tune for a given duration.\n\nArgs:\n tune (int):\n\n The tune to play can be specified as:\n\n + a single "note", represented as:\n + a frequency in Hz e.g. `440`\n + a midi note e.g. `60`\n + a note name as a string e.g. `"E4"`\n + a list of notes and duration e.g. `[440, 1]` or `["E4", 2]`\n + a list of two value tuples of (note, duration) e.g. `[(440,1), (60, 2), ("e4", 3)]`\n\n Defaults to `440`.\n volume (int): The volume of the tune; 1 is maximum volume, 0\n is mute. Defaults to 1.\n duration (float): The duration of each note in seconds.\n Defaults to 1.\n n (int): The number of times to play the tune. If None, the\n tune will play forever. Defaults to 1.\n wait (bool): If True, the method will block until the tune\n has finished. If False, the method will return and the\n tune will play in the background. Defaults to True.'), + _('Speaker.value() \n\nSets or returns the value of the speaker. The value is a tuple of (freq, volume).'), + _('Speaker.volume() \n\nSets or returns the volume of the speaker: 1 for maximum volume, 0 for off.'), + _('TemperatureSensor.conversion() \n\nSets or returns the conversion function for the device.'), + _('TemperatureSensor.temp() \n\nReturns the temperature of the device. If the conversion function is not\nset, this will return :data:`None`.'), + _('ValueChange.stop() \n\nStops the ValueChange object running.'), + _('picozero.AnalogInputDevice(pin, active_state, threshold) \n\nRepresents a generic input device with analogue functionality, e.g.\na potentiometer.\n\nArgs:\n pin (int): The pin that the device is connected to.\n active_state: The active state of the device. If :data:`True`\n (the default), the :class:`AnalogInputDevice` will assume\n that the device is active when the pin is high and above the\n threshold. If ``active_state`` is ``False``, the device will\n be active when the pin is low and below the threshold.\n threshold (float): The threshold that the device must be above\n or below to be considered active. The default is 0.5.'), + _('picozero.Button() \n\nRepresents a push button, which can be either pressed or released.\n\nArgs:\n pin (int): The pin that the device is connected to.\n pull_up (bool): If :data:`True` (the default), the device will\n be pulled up to HIGH. If :data:`False`, the device will be\n pulled down to LOW.\n bounce_time (float): The bounce time for the device. If set, the\n device will ignore any button presses that happen within the\n bounce time after a button release. This is useful to\n prevent accidental button presses from registering as\n multiple presses. Defaults to 0.02 seconds.'), + _('picozero.Buzzer() \n\nRepresents an active or passive buzzer, which can be turned on or off.\n\n:param int pin:\n The pin that the device is connected to.\n\n:param bool active_high:\n If :data:`True` (the default), the :meth:`on` method will set the Pin\n to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to\n LOW (the :meth:`off` method always does the opposite).\n\n:param bool initial_value:\n If :data:`False` (the default), the Buzzer will be off initially. If\n :data:`True`, the Buzzer will be switched on initially.'), + _('picozero.DigitalInputDevice(pin, pull_up, active_state, bounce_time) \n\nRepresents a generic input device with digital functionality e.g. buttons\nthat can be either active or inactive.\n\nArgs:\n pin (int): The pin that the device is connected to.\n pull_up (bool): If :data:`True`, the device will be pulled up to\n HIGH. If :data:`False` (the default), the device will be\n pulled down to LOW.\n active_state (bool): If :data:`True` (the default), the device\n will return :data:`True` if the pin is HIGH. If\n :data:`False`, the device will return :data:`False` if the\n pin is LOW.\n bounce_time (float): The bounce time for the device. If set, the\n device will ignore any button presses that happen within the\n bounce time after a button release. This is useful to\n prevent accidental button presses from registering as\n multiple presses. The default is :data:`None`.'), + _('picozero.DigitalLED() \n\nRepresents a simple LED, which can be switched on and off.\n\nArgs:\n pin (int): The pin that the device is connected to.\n active_high (bool): If :data:`True` (the default), the\n :meth:`on` method will set the Pin to HIGH. If\n :data:`False`, the :meth:`on` method will set the Pin to LOW\n (the :meth:`off` method always does the opposite).\n initial_value (bool): If :data:`False` (the default), the LED\n will be off initially. If :data:`True`, the LED will be\n switched on initially.'), + _('picozero.DigitalOutputDevice(pin, active_high, initial_value) \n\nRepresents a device driven by a digital pin.\n\nArgs:\n pin (int): The pin that the device is connected to.\n active_high (bool): If :data:`True` (the default), the\n :meth:`on` method will set the Pin to HIGH. If\n :data:`False`, the :meth:`on` method will set the Pin to LOW\n (the :meth:`off` method always does the opposite).\n initial_value (bool): If :data:`False` (the default), the LED\n will be off initially. If :data:`True`, the LED will be\n switched on initially.'), + _('picozero.DistanceSensor(echo, trigger, max_distance) \n\nRepresents a HC-SR04 ultrasonic distance sensor.\n\n:param int echo:\n The pin that the ECHO pin is connected to.\n\n:param int trigger:\n The pin that the TRIG pin is connected to. \n\n:param float max_distance:\n The :attr:`value` attribute reports a normalized value between 0 (too\n close to measure) and 1 (maximum distance). This parameter specifies\n the maximum distance expected in meters. This defaults to 1.'), + _('picozero.InputDevice(active_state) \n\nBase class for input devices.'), + _('picozero.Motor(forward, backward, pwm) \n\nRepresents a motor connected to a motor controller that has a two-pin\ninput. One pin drives the motor "forward", the other drives the motor\n"backward".\n\n:type forward: int\n:param forward:\n The GP pin that controls the "forward" motion of the motor. \n\n:type backward: int\n:param backward:\n The GP pin that controls the "backward" motion of the motor. \n\n:param bool pwm:\n If :data:`True` (the default), PWM pins are used to drive the motor. \n When using PWM pins, values between 0 and 1 can be used to set the \n speed.'), + _('picozero.OutputDevice(active_high, initial_value) \n\nBase class for output devices.'), + _('picozero.PWMBuzzer(pin, freq, duty_factor, active_high, initial_value) \n\nRepresents a passive buzzer driven by a PWM pin; the volume of the buzzer can be changed.\n\nArgs:\n pin (int): The pin that the buzzer is connected to.\n freq (int): The frequency of the PWM signal in hertz. Defaults\n to 440.\n duty_factor (int): The duty factor of the PWM signal. This is a\n value between 0 and 65535. Defaults to 1023.\n active_high (bool): If :data:`True` (the default), the\n :meth:`on` method will set the Pin to HIGH. If\n :data:`False`, the :meth:`on` method will set the Pin to LOW\n (the :meth:`off` method always does the opposite).\n initial_value (bool): If :data:`False` (the default), the buzzer\n will be off initially. If :data:`True`, the buzzer will be\n switched on initially.'), + _('picozero.PWMLED() \n\nRepresents an LED driven by a PWM pin; the brightness of the LED can be changed.\n\nArgs:\n pin (int): The pin that the device is connected to.\n freq (int): The frequency of the PWM signal in hertz. Defaults\n to 100.\n duty_factor (int): The duty factor of the PWM signal. This is a\n value between 0 and 65535. Defaults to 65535.\n active_high (bool): If :data:`True` (the default), the\n :meth:`on` method will set the Pin to HIGH. If\n :data:`False`, the :meth:`on` method will set the Pin to LOW\n (the :meth:`off` method always does the opposite).\n initial_value (bool): If :data:`False` (the default), the LED\n will be off initially. If :data:`True`, the LED will be\n switched on initially.'), + _('picozero.PWMOutputDevice(pin, freq, duty_factor, active_high, initial_value) \n\nRepresents a device driven by a PWM pin.\n\n:param int pin:\n The pin that the device is connected to.\n\n:param int freq:\n The frequency of the PWM signal in hertz. Defaults to 100.\n\n:param int duty_factor:\n The duty factor of the PWM signal. This is a value between 0 and 65535.\n Defaults to 65535.\n\n:param bool active_high:\n If :data:`True` (the default), the :meth:`on` method will set the Pin\n to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to\n LOW (the :meth:`off` method always does the opposite).\n\n:param bool initial_value:\n If :data:`False` (the default), the LED will be off initially. If\n :data:`True`, the LED will be switched on initially.'), + _('picozero.PinMixin() \n\nMixin used by devices that have a single pin number.'), + _('picozero.PinsMixin() \n\nMixin used by devices that use multiple pins.'), + _('picozero.Potentiometer() \n\nRepresents a potentiometer, which outputs a variable voltage\nbetween 0 and 3.3V.\n\nAlias for :class:`Pot`.\n\nArgs:\n pin (int): The pin that the device is connected to.\n active_state: The active state of the device. If :data:`True`\n (the default), the :class:`AnalogInputDevice` will assume\n that the device is active when the pin is high and above the\n threshold. If ``active_state`` is ``False``, the device will\n be active when the pin is low and below the threshold.\n threshold (float): The threshold that the device must be above\n or below to be considered active. The default is 0.5.'), + _('picozero.RGBLED(red, green, blue, active_high, initial_value, pwm) \n\nExtends :class:`OutputDevice` and represents a full colour LED component (composed\nof red, green, and blue LEDs).\nConnect the common cathode (longest leg) to a ground pin; connect each of\nthe other legs (representing the red, green, and blue anodes) to any GP\npins. You should use three limiting resistors (one per anode).\nThe following code will make the LED yellow::\n\n from picozero import RGBLED\n rgb = RGBLED(1, 2, 3)\n rgb.color = (1, 1, 0)\n\n0--255 colours are also supported::\n\n rgb.color = (255, 255, 0)\n\nArgs:\n red (int): The GP pin that controls the red component of the RGB\n LED.\n green (int): The GP pin that controls the green component of the\n RGB LED.\n blue (int): The GP pin that controls the blue component of the\n RGB LED.\n active_high (bool): Set to :data:`True` (the default) for common\n cathode RGB LEDs. If you are using a common anode RGB LED,\n set this to :data:`False`.\n initial_value (~colorzero.Color or tuple): The initial color for\n the RGB LED. Defaults to black ``(0, 0, 0)``.\n pwm (bool): If :data:`True` (the default), construct\n :class:`PWMLED` instances for each component of the RGBLED.\n If :data:`False`, construct :class:`DigitalLED` instances.'), + _("picozero.Robot(left, right, pwm) \n\nRepresents a generic dual-motor robot / rover / buggy.\n\nAlias for :class:`Rover`.\n\nThis class is constructed with two tuples representing the forward and\nbackward pins of the left and right controllers. For example,\nif the left motor's controller is connected to pins 12 and 13, while the\nright motor's controller is connected to pins 14 and 15, then the following\nexample will drive the robot forward::\n\n from picozero import Robot\n\n robot = Robot(left=(12, 13), right=(14, 15))\n robot.forward()\n\nArgs:\n left (tuple): A tuple of two pins representing the forward and\n backward inputs of the left motor's controller.\n right (tuple): A tuple of two pins representing the forward and\n backward inputs of the right motor's controller.\n pwm (bool): If :data:`True` (the default), pwm pins will be\n used, allowing variable speed control."), + _('picozero.Servo(pin, initial_value, min_pulse_width, max_pulse_width, frame_width, duty_factor) \n\nRepresents a PWM-controlled servo motor.\n\nSetting the `value` to 0 will move the servo to its minimum position,\n1 will move the servo to its maximum position. Setting the `value` to\n:data:`None` will turn the servo "off" (i.e. no signal is sent).\n\n:type pin: int\n:param pin:\n The pin the servo motor is connected to. \n\n:param bool initial_value:\n If :data:`0`, the servo will be set to its minimum position. If\n :data:`1`, the servo will set to its maximum position. If :data:`None`\n (the default), the position of the servo will not change.\n\n:param float min_pulse_width:\n The pulse width corresponding to the servo\'s minimum position. This\n defaults to 1ms.\n\n:param float max_pulse_width:\n The pulse width corresponding to the servo\'s maximum position. This\n defaults to 2ms.\n\n:param float frame_width:\n The length of time between servo control pulses measured in seconds.\n This defaults to 20ms which is a common value for servos.\n\n:param int duty_factor:\n The duty factor of the PWM signal. This is a value between 0 and 65535.\n Defaults to 65535. '), + _('picozero.Speaker(pin, initial_freq, initial_volume, duty_factor, active_high) \n\nRepresents a speaker driven by a PWM pin.\n\nArgs:\n pin (int): The pin that the speaker is connected to.\n initial_freq (int): The initial frequency of the PWM signal in\n hertz. Defaults to 440.\n initial_volume (int): The initial volume of the PWM signal. This\n is a value between 0 and 1. Defaults to 0.\n duty_factor (int): The duty factor of the PWM signal. This is a\n value between 0 and 65535. Defaults to 1023.\n active_high (bool): If :data:`True` (the default), the\n :meth:`on` method will set the Pin to HIGH. If\n :data:`False`, the :meth:`on` method will set the Pin to LOW\n (the :meth:`off` method always does the opposite).'), + _('picozero.Switch(pin, pull_up, bounce_time) \n\nRepresents a toggle switch, which is either open or closed.\n\nArgs:\n pin (int): The pin that the device is connected to.\n pull_up (bool): If :data:`True` (the default), the device will\n be pulled up to HIGH. If :data:`False`, the device will be\n pulled down to LOW.\n bounce_time (float): The bounce time for the device. If set, the\n device will ignore any button presses that happen within the\n bounce time after a button release. This is useful to\n prevent accidental button presses from registering as\n multiple presses. Defaults to 0.02 seconds.'), + _('picozero.TemperatureSensor(pin, active_state, threshold, conversion) \n\nRepresents a TemperatureSensor, which outputs a variable voltage. The voltage\ncan be converted to a temperature using a `conversion` function passed as a\nparameter.\n\nAlias for :class:`Thermistor` and :class:`TempSensor`.\n\nArgs:\n pin (int): The pin that the device is connected to.\n active_state: The active state of the device. If :data:`True`\n (the default), the :class:`AnalogInputDevice` will assume\n that the device is active when the pin is high and above the\n threshold. If ``active_state`` is ``False``, the device will\n be active when the pin is low and below the threshold.\n threshold (float): The threshold that the device must be above\n or below to be considered active. The default is 0.5.\n conversion (float): A function that takes a voltage and returns\n a temperature.\n\n e.g. The internal temperature sensor has a voltage range of 0.706V to 0.716V\n and would use the follow conversion function::\n\n def temp_conversion(voltage):\n return 27 - (voltage - 0.706)/0.001721\n\n temp_sensor = TemperatureSensor(pin, conversion=temp_conversion)\n\n If :data:`None` (the default), the ``temp`` property will return :data:`None`.'), + _("picozero.ValueChange(output_device, generator, n, wait) \n\nInternal class to control the value of an output device.\n\nArgs:\n output_device (OutputDevice): The OutputDevice object you wish\n to change the value of.\n generator: A generator function that yields a 2d list of\n ((value, seconds), *).\n\n The output_device's value will be set for the number of\n seconds.\n n (int): The number of times to repeat the sequence. If None,\n the sequence will repeat forever.\n wait (bool): If True the ValueChange object will block (wait)\n until the sequence has completed."), + _('picozero.pinout([output]) \n\nReturns a textual representation of the Raspberry Pi pico pins and functions.\n\nArgs:\n output (bool): If :data:`True` (the default) the pinout will be\n "printed".'), + ] +} \ No newline at end of file diff --git a/mu/modes/api/pygamezero.py b/mu/modes/api/pygamezero.py index ead731b26..eb50e49d1 100644 --- a/mu/modes/api/pygamezero.py +++ b/mu/modes/api/pygamezero.py @@ -21,497 +21,99 @@ PYGAMEZERO_APIS = { 'en_US': [ - _( - "actor.Actor(image, pos=None, anchor=None, **kwargs) \nRect(left, top, width, height) -> Rect\nRect((left, top), (width, height)) -> Rect\nRect(object) -> Rect\nPygame Zero object for storing rectangular coordinates" - ), - _( - "actor.atan2() \natan2(y, x)\n\nReturn the arc tangent (measured in radians) of y/x.\nUnlike atan(y/x), the signs of both x and y are considered." - ), - _( "actor.cos() \ncos(x)\n\nReturn the cosine of x (measured in radians)." ), - _( - "actor.degrees() \ndegrees(x)\n\nConvert angle x from radians to degrees." - ), - _( - "actor.pygame() \nPygame Zero is a set of Python modules designed for writing games.\nIt is written on top of the excellent SDL library. This allows you\nto create fully featured games and multimedia programs in the python\nlanguage. The package is highly portable, with games running on\nWindows, MacOS, OS X, BeOS, FreeBSD, IRIX, and Linux." - ), - _( - "actor.radians() \nradians(x)\n\nConvert angle x from degrees to radians." - ), - _( "actor.sin() \nsin(x)\n\nReturn the sine of x (measured in radians)." ), - _( "actor.sqrt() \nsqrt(x)\n\nReturn the square root of x."), - _( - "actor.transform_anchor(ax, ay, w, h, angle) \nTransform anchor based upon a rotation of a surface of size w x h." - ), - _( - 'animation.animate(object, tween=\'linear\', duration=1, on_finished=None, **targets) \nAn animation manager for object attribute animations.\n\nEach keyword argument given to the Animation on creation (except\n"type" and "duration") will be *tweened* from their current value\non the object to the target value specified.\n\nIf the value is a list or tuple, then each value inside that will\nbe tweened.\n\nThe update() method is automatically scheduled with the clock for\nthe duration of the animation.' - ), - _( - "animation.each_tick(callback) \nSchedule a callback to be called every tick.\n\nUnlike the standard scheduler functions, the callable is passed the\nelapsed clock time since the last call (the same value passed to tick)." - ), - _( "animation.pow() \npow(x, y)\n\nReturn x**y (x to the power of y)." ), - _( - "animation.sin() \nsin(x)\n\nReturn the sine of x (measured in radians)." - ), - _( - "animation.unschedule(callback) \nUnschedule the given callback.\n\nIf scheduled multiple times all instances will be unscheduled." - ), - _( - "clock.Clock() \nA clock used for event scheduling.\n\nWhen tick() is called, all events scheduled for before now will be called\nin order.\n\ntick() would typically be called from the game loop for the default clock.\n\nAdditional clocks could be created - for example, a game clock that could\nbe suspended in pause screens. Your code must take care of calling tick()\nor not. You could also run the clock at a different rate if desired, by\nscaling dt before passing it to tick()." - ), - _( - "clock.Event(time, cb, repeat=None) \nAn event scheduled for a future time.\n\nEvents are ordered by their scheduled execution time." - ), - _( - "clock.each_tick(callback) \nSchedule a callback to be called every tick.\n\nUnlike the standard scheduler functions, the callable is passed the\nelapsed clock time since the last call (the same value passed to tick)." - ), - _( - "clock.heapq() \nHeap queue algorithm (a.k.a. priority queue).\n\nHeaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for\nall k, counting elements from 0. For the sake of comparison,\nnon-existing elements are considered to be infinite. The interesting\nproperty of a heap is that a[0] is always its smallest element.\n\nUsage:\n\nheap = [] # creates an empty heap\nheappush(heap, item) # pushes a new item on the heap\nitem = heappop(heap) # pops the smallest item from the heap\nitem = heap[0] # smallest item on the heap without popping it\nheapify(x) # transforms list into a heap, in-place, in linear time\nitem = heapreplace(heap, item) # pops and returns smallest item, and adds\n # new item; the heap size is unchanged\n\nOur API differs from textbook heap algorithms as follows:\n\n- We use 0-based indexing. This makes the relationship between the\n index for a node and the indexes for its children slightly less\n obvious, but is more suitable since Python uses 0-based indexing.\n\n- Our heappop() method returns the smallest item, not the largest.\n\nThese two make it possible to view the heap as a regular Python list\nwithout surprises: heap[0] is the smallest item, and heap.sort()\nmaintains the heap invariant!" - ), - _( - "clock.method() \nmethod(function, instance)\n\nCreate a bound instance method object." - ), - _( - "clock.schedule(callback, delay) \nSchedule callback to be called once, at `delay` seconds from now.\n\n:param callback: A parameterless callable to be called.\n:param delay: The delay before the call (in clock time / seconds)." - ), - _( - "clock.schedule_interval(callback, delay) \nSchedule callback to be called every `delay` seconds.\n\nThe first occurrence will be after `delay` seconds.\n\n:param callback: A parameterless callable to be called.\n:param delay: The interval in seconds." - ), - _( - "clock.schedule_unique(callback, delay) \nSchedule callback to be called once, at `delay` seconds from now.\n\nIf it was already scheduled, postpone its firing.\n\n:param callback: A parameterless callable to be called.\n:param delay: The delay before the call (in clock time / seconds)." - ), - _( - "clock.tick(dt) \nUpdate the clock time and fire all scheduled events.\n\n:param dt: The elapsed time in seconds." - ), - _( - "clock.total_ordering(cls) \nClass decorator that fills in missing ordering methods" - ), - _( - "clock.unschedule(callback) \nUnschedule the given callback.\n\nIf scheduled multiple times all instances will be unscheduled." - ), - _( - "clock.weak_method(method) \nQuick weak method ref in case users aren't using Python 3.4" - ), - _( - "draw.circle(position, radius, (r, g, b)) \nDraw the outline of a circle." - ), - _( - "draw.filled_circle(position, radius, (r, g, b)) \nDraw a filled circle." - ), - _( - "draw.filled_rect(rect, (r, g, b)) \nDraw a filled rectangle. Takes a Rect object. For example, Rect((20, 20), (100, 100))" - ), - _( "draw.line(start, end, (r, g, b)) \nDraw a line from start to end." ), - _( - "draw.rect(rect, (r, g, b)) \nDraw the outline of a rectangle. Takes a Rect object. For example, Rect((20, 20), (100, 100))" - ), - _( - "draw.text(text, [pos, ]**kwargs) \nDraw text. There’s an extremely rich API for positioning and formatting text; see Pygame Zero Text Formatting for full details." - ), - _( - "draw.textbox(text, rect, **kwargs) \nDraw text, sized to fill the given Rect. There’s an extremely rich API for positioning and formatting text; see Pygame Zero Text Formatting for full details." - ), - _( - "keyboard.Keyboard() \nThe current state of the keyboard.\n\nEach attribute represents a key. For example, ::\n\n keyboard.a\n\nis True if the 'A' key is depressed, and False otherwise." - ), - _( - "keyboard.keys(value, names=None, *, module=None, qualname=None, type=None, start=1) \nAn enumeration." - ), - _( "keyboard.re() \nSupport for regular expressions (RE)." ), - _( - "keyboard.warn() \nIssue a warning, or maybe ignore it or raise an exception." - ), - _( - "music.ResourceLoader(subpath) \nAbstract resource loader.\n\nA resource loader is a singleton; resources are loaded from a named\nsubdirectory of the global 'root'. The `.load()` method actually loads\na resource.\n\nAdditionally, attribute access can be used to access and cache resources.\nDotted paths can be used to traverse directories." - ), - _( - "music.fadeout(seconds) \nFade out and eventually stop the music playback.\n\n:param seconds: The duration in seconds over which the sound will be faded\n out. For example, to fade out over half a second, call\n ``music.fadeout(0.5)``." - ), - _( "music.get_pos() \nget_pos() -> time\nget the music play time" ), - _( "music.get_volume() \nget_volume() -> value\nget the music volume" ), - _( - "music.is_playing(name) \nReturn True if the music is playing and not paused." - ), - _( - "music.pause() \nTemporarily stop playback of the music stream.\n\nCall `unpause()` to resume." - ), - _( - "music.pgzero.constants() \nNames for constants returned by Pygame Zero." - ), - _( - "music.play(name) \nPlay a music file from the music/ directory.\n\nThe music will loop when it finishes playing." - ), - _( "music.play_once(name) \nPlay a music file from the music/ directory." ), - _( - "music.queue(name) \nQueue a music file to follow the current track.\n\nThis will load a music file and queue it. A queued music file will begin as\nsoon as the current music naturally ends. If the current music is ever\nstopped or changed, the queued song will be lost." - ), - _( "music.rewind() \nrewind() -> None\nrestart music" ), - _( "music.set_pos() \nset_pos(pos) -> None\nset position to play from" ), - _( "music.set_volume() \nset_volume(value) -> None\nset the music volume" ), - _( "music.stop() \nstop() -> None\nstop the music playback" ), - _( - "music.unpause() \nResume playback of the music stream after it has been paused." - ), - _( - "screen.blit(image, (left, top)) \nDraw the image to the screen at the given position. \nblit() accepts either a Surface or a string as its image parameter. If image is a str then the named image will be loaded from the images/ directory." - ), - _( "screen.clear() \nReset the screen to black." ), - _( "screen.fill((red, green, blue)) \nFill the screen with a solid color." ), - _( "screen.Screen(surface) \nInterface to the screen." ), - _( - "screen.SurfacePainter(screen) \nInterface to pygame.draw that is bound to a surface." - ), - _( - "screen.pgzero.ptext() \npygame-text - high-level text rendering with Pygame Zero.\n\nThis module is directly copied from\n\n https://github.com/cosmologicon/pygame-text\n\nat revision c04e59b7382a832e117f0598cdcbc1bb3eb26db5\nand used under CC0." - ), - _( - "screen.pygame() \nPygame Zero is a set of Python modules designed for writing games.\nIt is written on top of the excellent SDL library. This allows you\nto create fully featured games and multimedia programs in the python\nlanguage. The package is highly portable, with games running on\nWindows, MacOS, OS X, BeOS, FreeBSD, IRIX, and Linux." - ), - _( - "screen.round_pos(pos) \nRound a tuple position so it can be used for drawing." - ), + _('actor.transform_anchor(ax, ay, w, h, angle) \n\nTransform anchor based upon a rotation of a surface of size w x h.'), + _('animation.animate(object, tween=\'linear\', duration=1, on_finished=None, **targets) \n\nAn animation manager for object attribute animations.\n\nEach keyword argument given to the Animation on creation (except\n"type" and "duration") will be *tweened* from their current value\non the object to the target value specified.\n\nIf the value is a list or tuple, then each value inside that will\nbe tweened.\n\nThe update() method is automatically scheduled with the clock for\nthe duration of the animation.'), + _('clock.Clock() \n\nA clock used for event scheduling.\n\nWhen tick() is called, all events scheduled for before now will be called\nin order.\n\ntick() would typically be called from the game loop for the default clock.\n\nAdditional clocks could be created - for example, a game clock that could\nbe suspended in pause screens. Your code must take care of calling tick()\nor not. You could also run the clock at a different rate if desired, by\nscaling dt before passing it to tick().'), + _('clock.Event(time, cb, repeat=None) \n\nAn event scheduled for a future time.\n\nEvents are ordered by their scheduled execution time.'), + _('clock.each_tick(callback) \n\nSchedule a callback to be called every tick.\n\nUnlike the standard scheduler functions, the callable is passed the\nelapsed clock time since the last call (the same value passed to tick).'), + _('clock.schedule(callback, delay) \n\nSchedule callback to be called once, at `delay` seconds from now.\n\n:param callback: A parameterless callable to be called.\n:param delay: The delay before the call (in clock time / seconds).'), + _('clock.schedule_interval(callback, delay) \n\nSchedule callback to be called every `delay` seconds.\n\nThe first occurrence will be after `delay` seconds.\n\n:param callback: A parameterless callable to be called.\n:param delay: The interval in seconds.'), + _('clock.schedule_unique(callback, delay) \n\nSchedule callback to be called once, at `delay` seconds from now.\n\nIf it was already scheduled, postpone its firing.\n\n:param callback: A parameterless callable to be called.\n:param delay: The delay before the call (in clock time / seconds).'), + _('clock.tick(dt) \n\nUpdate the clock time and fire all scheduled events.\n\n:param dt: The elapsed time in seconds.'), + _('clock.unschedule(callback) \n\nUnschedule the given callback.\n\nIf scheduled multiple times all instances will be unscheduled.'), + _("clock.weak_method(method) \n\nQuick weak method ref in case users aren't using Python 3.4"), + _("keyboard.Keyboard() \n\nThe current state of the keyboard.\n\nEach attribute represents a key. For example, ::\n\n keyboard.a\n\nis True if the 'A' key is depressed, and False otherwise."), + _('music.fadeout(seconds) \n\nFade out and eventually stop the music playback.\n\n:param seconds: The duration in seconds over which the sound will be faded\n out. For example, to fade out over half a second, call\n ``music.fadeout(0.5)``.'), + _('music.get_pos() \n\nget_pos() -> time\nget the music play time'), + _('music.get_volume() \n\nget_volume() -> value\nget the music volume'), + _('music.is_playing(name) \n\nReturn True if the music is playing and not paused.'), + _('music.pause() \n\nTemporarily stop playback of the music stream.\n\nCall `unpause()` to resume.'), + _('music.play(name) \n\nPlay a music file from the music/ directory.\n\nThe music will loop when it finishes playing.'), + _('music.play_once(name) \n\nPlay a music file from the music/ directory.'), + _('music.queue(name) \n\nQueue a music file to follow the current track.\n\nThis will load a music file and queue it. A queued music file will begin as\nsoon as the current music naturally ends. If the current music is ever\nstopped or changed, the queued song will be lost.'), + _('music.rewind() \n\nrewind() -> None\nrestart music'), + _('music.set_pos() \n\nset_pos(pos) -> None\nset position to play from'), + _('music.set_volume() \n\nset_volume(volume) -> None\nset the music volume'), + _('music.stop() \n\nstop() -> None\nstop the music playback'), + _('music.unpause() \n\nResume playback of the music stream after it has been paused.'), + _('screen.Screen(surface) \n\nInterface to the screen.'), + _('screen.SurfacePainter(screen) \n\nInterface to pygame.draw that is bound to a surface.'), + _('screen.round_pos(pos) \n\nRound a tuple position so it can be used for drawing.'), ], 'ko': [ - _( - "actor.Actor(이미지 파일명, [pos(화면표시 좌표값)=None], [anchor(객체의 중심값)=None], [기타 설정값들]) \n\nActor 객체를 생성합니다. 생성시 객체의 이미지 파일은 필수이나, 나머지 값들은 객체생성 추후에 개별적으로 설정할 수 있습니다." - ), - _( - "actor.atan2() \natan2(y, x)\n\nReturn the arc tangent (measured in radians) of y/x.\nUnlike atan(y/x), the signs of both x and y are considered." - ), - _( "actor.cos() \ncos(x)\n\nReturn the cosine of x (measured in radians)." ), - _( - "actor.degrees() \ndegrees(x)\n\nConvert angle x from radians to degrees." - ), - _( - "actor.pygame() \nPygame Zero is a set of Python modules designed for writing games.\nIt is written on top of the excellent SDL library. This allows you\nto create fully featured games and multimedia programs in the python\nlanguage. The package is highly portable, with games running on\nWindows, MacOS, OS X, BeOS, FreeBSD, IRIX, and Linux." - ), - _( - "actor.radians() \nradians(x)\n\nConvert angle x from degrees to radians." - ), - _( "actor.sin() \nsin(x)\n\nReturn the sine of x (measured in radians)." ), - _( "actor.sqrt() \nsqrt(x)\n\nReturn the square root of x."), - _( - "actor.transform_anchor(ax, ay, w, h, angle) \nTransform anchor based upon a rotation of a surface of size w x h." - ), - _( - 'animation.animate(object, tween=\'linear\', duration=1, on_finished=None, **targets) \nAn animation manager for object attribute animations.\n\nEach keyword argument given to the Animation on creation (except\n"type" and "duration") will be *tweened* from their current value\non the object to the target value specified.\n\nIf the value is a list or tuple, then each value inside that will\nbe tweened.\n\nThe update() method is automatically scheduled with the clock for\nthe duration of the animation.' - ), - _( - "animation.each_tick(callback) \nSchedule a callback to be called every tick.\n\nUnlike the standard scheduler functions, the callable is passed the\nelapsed clock time since the last call (the same value passed to tick)." - ), - _( "animation.pow() \npow(x, y)\n\nReturn x**y (x to the power of y)." ), - _( - "animation.sin() \nsin(x)\n\nReturn the sine of x (measured in radians)." - ), - _( - "animation.unschedule(callback) \nUnschedule the given callback.\n\nIf scheduled multiple times all instances will be unscheduled." - ), - _( - "clock.Clock() \nA clock used for event scheduling.\n\nWhen tick() is called, all events scheduled for before now will be called\nin order.\n\ntick() would typically be called from the game loop for the default clock.\n\nAdditional clocks could be created - for example, a game clock that could\nbe suspended in pause screens. Your code must take care of calling tick()\nor not. You could also run the clock at a different rate if desired, by\nscaling dt before passing it to tick()." - ), - _( - "clock.Event(time, cb, repeat=None) \nAn event scheduled for a future time.\n\nEvents are ordered by their scheduled execution time." - ), - _( - "clock.each_tick(callback) \nSchedule a callback to be called every tick.\n\nUnlike the standard scheduler functions, the callable is passed the\nelapsed clock time since the last call (the same value passed to tick)." - ), - _( - "clock.heapq() \nHeap queue algorithm (a.k.a. priority queue).\n\nHeaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for\nall k, counting elements from 0. For the sake of comparison,\nnon-existing elements are considered to be infinite. The interesting\nproperty of a heap is that a[0] is always its smallest element.\n\nUsage:\n\nheap = [] # creates an empty heap\nheappush(heap, item) # pushes a new item on the heap\nitem = heappop(heap) # pops the smallest item from the heap\nitem = heap[0] # smallest item on the heap without popping it\nheapify(x) # transforms list into a heap, in-place, in linear time\nitem = heapreplace(heap, item) # pops and returns smallest item, and adds\n # new item; the heap size is unchanged\n\nOur API differs from textbook heap algorithms as follows:\n\n- We use 0-based indexing. This makes the relationship between the\n index for a node and the indexes for its children slightly less\n obvious, but is more suitable since Python uses 0-based indexing.\n\n- Our heappop() method returns the smallest item, not the largest.\n\nThese two make it possible to view the heap as a regular Python list\nwithout surprises: heap[0] is the smallest item, and heap.sort()\nmaintains the heap invariant!" - ), - _( - "clock.method() \nmethod(function, instance)\n\nCreate a bound instance method object." - ), - _( - "clock.schedule(callback, delay) \nSchedule callback to be called once, at `delay` seconds from now.\n\n:param callback: A parameterless callable to be called.\n:param delay: The delay before the call (in clock time / seconds)." - ), - _( - "clock.schedule_interval(callback, delay) \nSchedule callback to be called every `delay` seconds.\n\nThe first occurrence will be after `delay` seconds.\n\n:param callback: A parameterless callable to be called.\n:param delay: The interval in seconds." - ), - _( - "clock.schedule_unique(callback, delay) \nSchedule callback to be called once, at `delay` seconds from now.\n\nIf it was already scheduled, postpone its firing.\n\n:param callback: A parameterless callable to be called.\n:param delay: The delay before the call (in clock time / seconds)." - ), - _( - "clock.tick(dt) \nUpdate the clock time and fire all scheduled events.\n\n:param dt: The elapsed time in seconds." - ), - _( - "clock.total_ordering(cls) \nClass decorator that fills in missing ordering methods" - ), - _( - "clock.unschedule(callback) \nUnschedule the given callback.\n\nIf scheduled multiple times all instances will be unscheduled." - ), - _( - "clock.weak_method(method) \nQuick weak method ref in case users aren't using Python 3.4" - ), - _( - "draw.circle(position, radius, (r, g, b)) \nDraw the outline of a circle." - ), - _( - "draw.filled_circle(position, radius, (r, g, b)) \nDraw a filled circle." - ), - _( - "draw.filled_rect(rect, (r, g, b)) \nDraw a filled rectangle. Takes a Rect object. For example, Rect((20, 20), (100, 100))" - ), - _( "draw.line(start, end, (r, g, b)) \nDraw a line from start to end." ), - _( - "draw.rect(rect, (r, g, b)) \nDraw the outline of a rectangle. Takes a Rect object. For example, Rect((20, 20), (100, 100))" - ), - _( - "draw.text(text, [pos, ]**kwargs) \nDraw text. There’s an extremely rich API for positioning and formatting text; see Pygame Zero Text Formatting for full details." - ), - _( - "draw.textbox(text, rect, **kwargs) \nDraw text, sized to fill the given Rect. There’s an extremely rich API for positioning and formatting text; see Pygame Zero Text Formatting for full details." - ), - _( - "keyboard.Keyboard() \nThe current state of the keyboard.\n\nEach attribute represents a key. For example, ::\n\n keyboard.a\n\nis True if the 'A' key is depressed, and False otherwise." - ), - _( - "keyboard.keys(value, names=None, *, module=None, qualname=None, type=None, start=1) \nAn enumeration." - ), - _( "keyboard.re() \nSupport for regular expressions (RE)." ), - _( - "keyboard.warn() \nIssue a warning, or maybe ignore it or raise an exception." - ), - _( - "music.ResourceLoader(subpath) \nAbstract resource loader.\n\nA resource loader is a singleton; resources are loaded from a named\nsubdirectory of the global 'root'. The `.load()` method actually loads\na resource.\n\nAdditionally, attribute access can be used to access and cache resources.\nDotted paths can be used to traverse directories." - ), - _( - "music.fadeout(seconds) \nFade out and eventually stop the music playback.\n\n:param seconds: The duration in seconds over which the sound will be faded\n out. For example, to fade out over half a second, call\n ``music.fadeout(0.5)``." - ), - _( "music.get_pos() \nget_pos() -> time\nget the music play time" ), - _( "music.get_volume() \nget_volume() -> value\nget the music volume" ), - _( - "music.is_playing(name) \nReturn True if the music is playing and not paused." - ), - _( - "music.pause() \nTemporarily stop playback of the music stream.\n\nCall `unpause()` to resume." - ), - _( - "music.pgzero.constants() \nNames for constants returned by Pygame Zero." - ), - _( - "music.play(name) \nPlay a music file from the music/ directory.\n\nThe music will loop when it finishes playing." - ), - _( "music.play_once(name) \nPlay a music file from the music/ directory." ), - _( - "music.queue(name) \nQueue a music file to follow the current track.\n\nThis will load a music file and queue it. A queued music file will begin as\nsoon as the current music naturally ends. If the current music is ever\nstopped or changed, the queued song will be lost." - ), - _( "music.rewind() \nrewind() -> None\nrestart music" ), - _( "music.set_pos() \nset_pos(pos) -> None\nset position to play from" ), - _( "music.set_volume() \nset_volume(value) -> None\nset the music volume" ), - _( "music.stop() \nstop() -> None\nstop the music playback" ), - _( - "music.unpause() \nResume playback of the music stream after it has been paused." - ), - _( - "screen.blit(image, (left, top)) \nDraw the image to the screen at the given position. \nblit() accepts either a Surface or a string as its image parameter. If image is a str then the named image will be loaded from the images/ directory." - ), - _( "screen.clear() \nReset the screen to black." ), - _( "screen.fill((red, green, blue)) \nFill the screen with a solid color." ), - _( "screen.Screen(surface) \nInterface to the screen." ), - _( - "screen.SurfacePainter(screen) \nInterface to pygame.draw that is bound to a surface." - ), - _( - "screen.pgzero.ptext() \npygame-text - high-level text rendering with Pygame Zero.\n\nThis module is directly copied from\n\n https://github.com/cosmologicon/pygame-text\n\nat revision c04e59b7382a832e117f0598cdcbc1bb3eb26db5\nand used under CC0." - ), - _( - "screen.pygame() \nPygame Zero is a set of Python modules designed for writing games.\nIt is written on top of the excellent SDL library. This allows you\nto create fully featured games and multimedia programs in the python\nlanguage. The package is highly portable, with games running on\nWindows, MacOS, OS X, BeOS, FreeBSD, IRIX, and Linux." - ), - _( - "screen.round_pos(pos) \nRound a tuple position so it can be used for drawing." - ), - _( "screen.fill((R, G, B 색상값)| 16진수 색상값 | 색상 키워드)) \n\n게임화면을 주어진 색상으로 채웁니다." ), - _( "actor.scale : Actor 이미지의 크기 스케일을 설정합니다." ), - _( "actor.say_for_sec(텍스트, 시간(초), [전경색=필수아님], [배경색=필수아님]) \n\nActor 이미지 위에 텍스트를 정해진 시간(초) 동안 보여줍니다." ), - _( "actor.pos : Actor의 anchor(중심) 좌표값" ), - _( "actor.anchor : Actor 중심의 좌표값" ), - _( "actor.flip_x : Actor 이미지를 x 방향으로 뒤집기" ), - _( "actor.flip_y : Actor 이미지를 y 방향으로 뒤집기" ), - _( "actor.angle : Actor 이미지를 특정 각도로 회전시키기" ), - _( "actor.images : Actor 객체의 애니메이션을 위한 이미지 그룹 설정" ), - _( "actor.next_image() \n\nActor 객체에 할당된 여러 이미지 중 다음 이미지로 이동시키다." ), - _( "actor.move_forward(이동값) \n\nActor 객체의 angle값을 전방 기준점으로 삼아 할당된 값만큼 전방으로 이동시킨다." ), - _( "actor.move_back(이동값) \n\nActor 객체의 angle값을 전방 기준점으로 삼아 할당된 값만큼 후방으로 이동시킨다." ), - _( "actor.move_left(이동값) \n\nActor 객체의 angle값을 전방 기준점으로 삼아 할당된 값만큼 좌측으로 이동시킨다." ), - _( "actor.move_right(이동값) \n\nActor 객체의 angle값을 전방 기준점으로 삼아 할당된 값만큼 우측으로 이동시킨다." ), - _( "actor.brush_init(스케치 영역, 두께, [색깔=필수아님]) \n\n스케치할 영역, 브러시의 두께와 색상 등을 설정합니다. " ), - _( "actor.brush_draw() \n\n그리기를 시작합니다." ), - _( "actor.brush_stop() \n\n그리기를 멈춥니다." ), - _( "actor.brush_clear() \n\n스케치영역을 모두 지웁니다." ), - _( "actor.colliderect(오브젝트) -> bool \n\nRect에 기반해 두 오브젝트의 충돌을 검사한다." ), - _( "actor.collide_pixel(오브젝트) -> (Tuple[int, int] | None) \n\n픽셀에 기반해 두 오브젝트의 충돌을 검사한다." ), - _( "actor.collidepoint_pixel(좌표값) -> int \n\n픽셀에 기반에 입력된 좌표와 오브젝트와의 충돌을 검사한다." ), - _( "game.exit() \n\n프로그램을 종료한다." ), + _('animation.animate(Actor객체, [pos=필수아님], [tween=필수아님], [duration=필수아님], [on_finished=필수아님], [기타 설정값]) \n\nActor객체를 애니메이션 합니다.\npos=이동할 위치\ntween=애니메이션 방법, 예를들어, \'linear\', \'accelerate\' 등\nduration=시간(초)\non_finished=애니메이션 종료시 호출 함수명'), + _("draw.filled_rect(rect, (R, G, B 색상값)) \n\n채워진 직사각형을 그립니다. 위치로 Rect 객체를 취합니다.\n예를 들어 Rect((x좌표, y좌표), (가로길이, 세로길이))"), + _("draw.text(텍스트, 위치, [기타 설정값들]) \n\n텍스트를 지정된 위치에 나타냅니다. 텍스트 위치 지정 및 서식 지정을 위한 매우 풍부한 API가 있습니다.\n자세한 내용은 Pygame Zero 라이브러리 메뉴얼을 참조하세요."), + _("draw.textbox(텍스트, rect, [기타 설정값들]) \n\n주어진 Rect를 채울 크기의 텍스트를 출력합니다. 텍스트 위치 지정 및 서식 지정을 위한 매우 풍부한 API가 있습니다.\n자세한 내용은 Pygame Zero 라이브러리 메뉴얼을 참조하세요."), + _("screen.blit(이미지, (화면좌측 좌표, 화면상단 좌표)) \n\n주어진 위치에 이미지를 화면에 그립니다. 이미지 매개변수로 Surface 또는 문자열을 허용합니다.\n이미지가 문자열이면 지정된 이미지가 images/ 폴더에서 로드됩니다."), + _("screen.fill((R, G, B 색상값)| 16진수 색상값 | 색상 키워드)) \n\n게임화면을 주어진 색상으로 채웁니다."), + _("actor.Actor(이미지 파일명, [pos(화면표시 좌표값)=None], [anchor(객체의 중심값)=None], [기타 설정값들]) \n\nActor 객체를 생성합니다. 생성시 객체의 이미지 파일은 필수이나, 나머지 값들은 객체생성 추후에 개별적으로 설정할 수 있습니다."), + _("actor.scale : Actor 이미지의 크기 스케일을 설정합니다."), + _("actor.draw() : Actor를 화면에 출력합니다."), + _("actor.say_for_sec(텍스트, 시간(초), [전경색=필수아님], [배경색=필수아님], [기타 설정값들]) \n\nActor 이미지 위에 텍스트를 정해진 시간(초) 동안 보여줍니다."), + _("actor.pos : Actor의 anchor(중심) 좌표값"), + _("actor.anchor : Actor 중심의 좌표값"), + _("actor.flip_x : Actor 이미지를 x 방향으로 뒤집기"), + _("actor.flip_y : Actor 이미지를 y 방향으로 뒤집기"), + _("actor.angle : Actor 이미지를 특정 각도로 회전시키기"), + _("actor.images : Actor 객체의 애니메이션을 위한 이미지 그룹 설정"), + _("actor.next_image() \n\nActor 객체에 할당된 여러 이미지 중 다음 이미지로 이동시킵니다."), + _("actor.move_forward(이동값) \n\nActor 객체의 angle값을 전방 기준점으로 삼아 할당된 값만큼 전방으로 이동시킵니다."), + _("actor.move_back(이동값) \n\nActor 객체의 angle값을 전방 기준점으로 삼아 할당된 값만큼 후방으로 이동시킵니다."), + _("actor.move_left(이동값) \n\nActor 객체의 angle값을 전방 기준점으로 삼아 할당된 값만큼 좌측으로 이동시킵니다."), + _("actor.move_right(이동값) \n\nActor 객체의 angle값을 전방 기준점으로 삼아 할당된 값만큼 우측으로 이동시킵니다."), + _("actor.brush_init(스케치 영역, 두께, [색깔=필수아님]) \n\n스케치할 영역, 브러시의 두께와 색상 등을 설정합니다."), + _("actor.brush_draw() \n\n그리기를 시작합니다."), + _("actor.brush_stop() \n\n그리기를 멈춥니다."), + _("actor.brush_clear() \n\n스케치영역을 모두 지웁니다."), + _("actor.colliderect(오브젝트) -> bool \n\nRect에 기반해 두 오브젝트의 충돌을 검사합니다."), + _("actor.collide_pixel(오브젝트) -> (Tuple[int, int] | None) \n\n픽셀에 기반해 두 오브젝트의 충돌을 검사합니다."), + _("actor.collidepoint_pixel(좌표값) -> int \n\n픽셀에 기반에 입력된 좌표와 오브젝트와의 충돌을 검사합니다."), + _("game.exit() \n\n프로그램을 종료한다."), + _("sounds.play([재생횟수=필수아님]) \n\n소리를 설정한 횟수만큼 재생합니다. 횟수 미설정시 단회 재생합니다."), + _("pygame.display.update([Rect=필수아님]) \n\n설정한 Rect의 영역의 화면을 갱신합니다.영역 미설정시 전체화면을 갱신합니다."), ], 'uz_UZ': [ - _( - "actor.Actor(rasm fayli, [pos(joylashuv)=Shart emas], [anchor(markaz)=Shart emas)], [va hokazolar]) \n\nActor obyektni yaratadi. Obyektni yaratishda rasm fayli talab qilinadi,\nammo qolgan qiymatlar obyekt yaratilgandan keyin alohida sozlash mumkin." - ), - _( - "actor.atan2() \natan2(y, x)\n\nReturn the arc tangent (measured in radians) of y/x.\nUnlike atan(y/x), the signs of both x and y are considered." - ), - _( "actor.cos() \ncos(x)\n\nReturn the cosine of x (measured in radians)." ), - _( - "actor.degrees() \ndegrees(x)\n\nConvert angle x from radians to degrees." - ), - _( - "actor.pygame() \nPygame Zero is a set of Python modules designed for writing games.\nIt is written on top of the excellent SDL library. This allows you\nto create fully featured games and multimedia programs in the python\nlanguage. The package is highly portable, with games running on\nWindows, MacOS, OS X, BeOS, FreeBSD, IRIX, and Linux." - ), - _( - "actor.radians() \nradians(x)\n\nConvert angle x from degrees to radians." - ), - _( "actor.sin() \nsin(x)\n\nReturn the sine of x (measured in radians)." ), - _( "actor.sqrt() \nsqrt(x)\n\nReturn the square root of x."), - _( - "actor.transform_anchor(ax, ay, w, h, angle) \nTransform anchor based upon a rotation of a surface of size w x h." - ), - _( - 'animation.animate(object, tween=\'linear\', duration=1, on_finished=None, **targets) \nAn animation manager for object attribute animations.\n\nEach keyword argument given to the Animation on creation (except\n"type" and "duration") will be *tweened* from their current value\non the object to the target value specified.\n\nIf the value is a list or tuple, then each value inside that will\nbe tweened.\n\nThe update() method is automatically scheduled with the clock for\nthe duration of the animation.' - ), - _( - "animation.each_tick(callback) \nSchedule a callback to be called every tick.\n\nUnlike the standard scheduler functions, the callable is passed the\nelapsed clock time since the last call (the same value passed to tick)." - ), - _( "animation.pow() \npow(x, y)\n\nReturn x**y (x to the power of y)." ), - _( - "animation.sin() \nsin(x)\n\nReturn the sine of x (measured in radians)." - ), - _( - "animation.unschedule(callback) \nUnschedule the given callback.\n\nIf scheduled multiple times all instances will be unscheduled." - ), - _( - "clock.Clock() \nA clock used for event scheduling.\n\nWhen tick() is called, all events scheduled for before now will be called\nin order.\n\ntick() would typically be called from the game loop for the default clock.\n\nAdditional clocks could be created - for example, a game clock that could\nbe suspended in pause screens. Your code must take care of calling tick()\nor not. You could also run the clock at a different rate if desired, by\nscaling dt before passing it to tick()." - ), - _( - "clock.Event(time, cb, repeat=None) \nAn event scheduled for a future time.\n\nEvents are ordered by their scheduled execution time." - ), - _( - "clock.each_tick(callback) \nSchedule a callback to be called every tick.\n\nUnlike the standard scheduler functions, the callable is passed the\nelapsed clock time since the last call (the same value passed to tick)." - ), - _( - "clock.heapq() \nHeap queue algorithm (a.k.a. priority queue).\n\nHeaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for\nall k, counting elements from 0. For the sake of comparison,\nnon-existing elements are considered to be infinite. The interesting\nproperty of a heap is that a[0] is always its smallest element.\n\nUsage:\n\nheap = [] # creates an empty heap\nheappush(heap, item) # pushes a new item on the heap\nitem = heappop(heap) # pops the smallest item from the heap\nitem = heap[0] # smallest item on the heap without popping it\nheapify(x) # transforms list into a heap, in-place, in linear time\nitem = heapreplace(heap, item) # pops and returns smallest item, and adds\n # new item; the heap size is unchanged\n\nOur API differs from textbook heap algorithms as follows:\n\n- We use 0-based indexing. This makes the relationship between the\n index for a node and the indexes for its children slightly less\n obvious, but is more suitable since Python uses 0-based indexing.\n\n- Our heappop() method returns the smallest item, not the largest.\n\nThese two make it possible to view the heap as a regular Python list\nwithout surprises: heap[0] is the smallest item, and heap.sort()\nmaintains the heap invariant!" - ), - _( - "clock.method() \nmethod(function, instance)\n\nCreate a bound instance method object." - ), - _( - "clock.schedule(callback, delay) \nSchedule callback to be called once, at `delay` seconds from now.\n\n:param callback: A parameterless callable to be called.\n:param delay: The delay before the call (in clock time / seconds)." - ), - _( - "clock.schedule_interval(callback, delay) \nSchedule callback to be called every `delay` seconds.\n\nThe first occurrence will be after `delay` seconds.\n\n:param callback: A parameterless callable to be called.\n:param delay: The interval in seconds." - ), - _( - "clock.schedule_unique(callback, delay) \nSchedule callback to be called once, at `delay` seconds from now.\n\nIf it was already scheduled, postpone its firing.\n\n:param callback: A parameterless callable to be called.\n:param delay: The delay before the call (in clock time / seconds)." - ), - _( - "clock.tick(dt) \nUpdate the clock time and fire all scheduled events.\n\n:param dt: The elapsed time in seconds." - ), - _( - "clock.total_ordering(cls) \nClass decorator that fills in missing ordering methods" - ), - _( - "clock.unschedule(callback) \nUnschedule the given callback.\n\nIf scheduled multiple times all instances will be unscheduled." - ), - _( - "clock.weak_method(method) \nQuick weak method ref in case users aren't using Python 3.4" - ), - _( - "draw.circle(position, radius, (r, g, b)) \nDraw the outline of a circle." - ), - _( - "draw.filled_circle(position, radius, (r, g, b)) \nDraw a filled circle." - ), - _( - "draw.filled_rect(rect, (r, g, b)) \nDraw a filled rectangle. Takes a Rect object. For example, Rect((20, 20), (100, 100))" - ), - _( "draw.line(start, end, (r, g, b)) \nDraw a line from start to end." ), - _( - "draw.rect(rect, (r, g, b)) \nDraw the outline of a rectangle. Takes a Rect object. For example, Rect((20, 20), (100, 100))" - ), - _( - "draw.text(text, [pos, ]**kwargs) \nDraw text. There’s an extremely rich API for positioning and formatting text; see Pygame Zero Text Formatting for full details." - ), - _( - "draw.textbox(text, rect, **kwargs) \nDraw text, sized to fill the given Rect. There’s an extremely rich API for positioning and formatting text; see Pygame Zero Text Formatting for full details." - ), - _( - "keyboard.Keyboard() \nThe current state of the keyboard.\n\nEach attribute represents a key. For example, ::\n\n keyboard.a\n\nis True if the 'A' key is depressed, and False otherwise." - ), - _( - "keyboard.keys(value, names=None, *, module=None, qualname=None, type=None, start=1) \nAn enumeration." - ), - _( "keyboard.re() \nSupport for regular expressions (RE)." ), - _( - "keyboard.warn() \nIssue a warning, or maybe ignore it or raise an exception." - ), - _( - "music.ResourceLoader(subpath) \nAbstract resource loader.\n\nA resource loader is a singleton; resources are loaded from a named\nsubdirectory of the global 'root'. The `.load()` method actually loads\na resource.\n\nAdditionally, attribute access can be used to access and cache resources.\nDotted paths can be used to traverse directories." - ), - _( - "music.fadeout(seconds) \nFade out and eventually stop the music playback.\n\n:param seconds: The duration in seconds over which the sound will be faded\n out. For example, to fade out over half a second, call\n ``music.fadeout(0.5)``." - ), - _( "music.get_pos() \nget_pos() -> time\nget the music play time" ), - _( "music.get_volume() \nget_volume() -> value\nget the music volume" ), - _( - "music.is_playing(name) \nReturn True if the music is playing and not paused." - ), - _( - "music.pause() \nTemporarily stop playback of the music stream.\n\nCall `unpause()` to resume." - ), - _( - "music.pgzero.constants() \nNames for constants returned by Pygame Zero." - ), - _( - "music.play(name) \nPlay a music file from the music/ directory.\n\nThe music will loop when it finishes playing." - ), - _( "music.play_once(name) \nPlay a music file from the music/ directory." ), - _( - "music.queue(name) \nQueue a music file to follow the current track.\n\nThis will load a music file and queue it. A queued music file will begin as\nsoon as the current music naturally ends. If the current music is ever\nstopped or changed, the queued song will be lost." - ), - _( "music.rewind() \nrewind() -> None\nrestart music" ), - _( "music.set_pos() \nset_pos(pos) -> None\nset position to play from" ), - _( "music.set_volume() \nset_volume(value) -> None\nset the music volume" ), - _( "music.stop() \nstop() -> None\nstop the music playback" ), - _( - "music.unpause() \nResume playback of the music stream after it has been paused." - ), - _( - "screen.blit(image, (left, top)) \nDraw the image to the screen at the given position. \nblit() accepts either a Surface or a string as its image parameter. If image is a str then the named image will be loaded from the images/ directory." - ), - _( "screen.clear() \nReset the screen to black." ), - _( "screen.fill((red, green, blue)) \nFill the screen with a solid color." ), - _( "screen.Screen(surface) \nInterface to the screen." ), - _( - "screen.SurfacePainter(screen) \nInterface to pygame.draw that is bound to a surface." - ), - _( - "screen.pgzero.ptext() \npygame-text - high-level text rendering with Pygame Zero.\n\nThis module is directly copied from\n\n https://github.com/cosmologicon/pygame-text\n\nat revision c04e59b7382a832e117f0598cdcbc1bb3eb26db5\nand used under CC0." - ), - _( - "screen.pygame() \nPygame Zero is a set of Python modules designed for writing games.\nIt is written on top of the excellent SDL library. This allows you\nto create fully featured games and multimedia programs in the python\nlanguage. The package is highly portable, with games running on\nWindows, MacOS, OS X, BeOS, FreeBSD, IRIX, and Linux." - ), - _( - "screen.round_pos(pos) \nRound a tuple position so it can be used for drawing." - ), - _( "screen.fill((R, G, B qiymati) | rang texti)) \n\nEkranni sozlangan rang bilan to'ldiring." ), - _( "actor.scale : Actor rasmining masshtabini sozlash." ), - _( "actor.say_for_sec(gap, soniya, [old rangi=Shart emas], [orqa rangi=Shart emas]) \n\nActor rasmining ustida sozlangan soniya davomida gapni ko'rsatadi." ), - _( "actor.pos : Actor joylashuvini sozlash. Lekin anchor qiymatiga asoslanadi." ), - _( "actor.anchor : Actor markazini sozlash." ), - _( "actor.flip_x : True bo'la Actor rasmini x yo'nalishida chappa qiladi." ), - _( "actor.flip_y : True bo'lsa Actor rasmini y yo'nalishida chappa qiladi." ), - _( "actor.angle : Actor rasmini sozlagan burchakga aylantiradi." ), - _( "actor.images : Actor ni animatsiya qilish uchun rasmining guruhini sozlash." ), - _( "actor.next_image() \n\nActor rasmlarning guruhidagi keyingi tartibli rasmga o'tadi." ), - _( "actor.move_forward(masofa) \n\nActor sozlangan masofa soniga qarab to'g'ri tomoniga yuradi." ), - _( "actor.move_back(masofa) \n\nActor sozlangan masofa soniga qarab orqa tomoniga yuradi." ), - _( "actor.move_left(masofa) \n\nActor sozlangan masofa soniga qarab chap tomoniga yuradi." ), - _( "actor.move_right(masofa) \n\nActor sozlangan masofa soniga qarab o'ng tomoniga yuradi." ), - _( "actor.brush_init(maydon, qalinlik, [rang=Shart emas]) \n\nCho'tka qancha maydon ichida qancha qalinlik bilan chizish kerakligini sozlaydi." ), - _( "actor.brush_draw() \n\nCho'tka chizishni boshlaydi." ), - _( "actor.brush_stop() \n\nCho'tka chizishni tuxtaydi." ), - _( "actor.brush_clear() \n\nCho'tka maydonini tozalaydi." ), - _( "actor.colliderect(obyekt) -> bool \n\nRect-ga asoslanib ikkita Actor obyekti o'rtasida to'qnashuv mavjudligini tekshiradi." ), - _( "actor.collide_pixel(obyekt) -> (Tuple[int, int] | None) \n\nRang bor pixel-ga asoslanib ikkita Actor obyekti o'rtasida to'qnashuv mavjudligini tekshiradi." ), - _( "actor.collidepoint_pixel((x, y joylashuvi)) -> int \n\nBerilgan x, y-joylashuviga asoslanib Actor obyekti bilan to'qnashuv mavjudligini tekshiradi." ), - _( "game.exit() \n\nDasturni tamomlaydi." ), + _("animation.animate(Actor obyekti, [pos=Shart emas], [duration=Shart emas], [tween=Shart emas], [on_finished=Shart emas], [boshqa sozlamalar])\n\nActorni animatsiya qiladi.\npos=Sozilangan joylashuvigacha ko'chirish animatsiyasi qiladi.\ntween=Animatsiya usuli, masalan \'linear\', \'accelerate\' va boshqalar.\nduration=Vaqt (sekund)\non_finished=Animatsiya tugashi bilan chaqirilaydigan funksiya nomi"), + _("draw.filled_rect(rect, (R, G, B qiymati)) \n\nToʻldirilgan toʻrtburchak chizadi. Rect obyekti orqali jolashuvni oladi.\nMasalan, Rect((x-koordinata, y-koordinata), (kenglik, balandlik ))"), + _("draw.text(text, joy, [boshqa sozlamalar])\n\nTextni belgilangan joyda chiqaradi. Textni joylashtirish va formatlash uchun juda boy API mavjud.\nBatafsil maʼlumot uchun Pygame Zero kutubxonasining qo'llanmasini qarang."), + _("draw.textbox(text, rect, [boshqa sozlamalar]) \n\nBerilgan Rectni to'ldiradigan o'lchamdagi textni chiqaradi. Textni joylashtirish va formatlash uchun juda boy API mavjud.\nBatafsil maʼlumot uchun Pygame Zero kutubxonasining qo'llanmasini qarang."), + _("screen.blit(rasm, (ekran chap koordinatasi, ekranning yuqori koordinatasi)) \n\nEkranda rasmni berilgan joyda chizadi. Surface yoki matnli qiymatini rasm parametri sifatida qabul qiladi.\nAgar rasm matnli qiymati boʻlsa, u images/papkadan yuklab oladi."), + _("screen.fill((R, G, B qiymati) | rang texti)) \n\nEkranni sozlangan rang bilan to'ldiring." ), + _("actor.Actor(rasm fayli, [pos(joylashuv)=Shart emas], [anchor(markaz)=Shart emas)], [boshqa sozlamalar]) \n\nActor obyektni yaratadi. Obyektni yaratishda rasm fayli talab qilinadi,\nammo qolgan qiymatlar obyekt yaratilgandan keyin alohida sozlash mumkin."), + _("actor.scale : Actor rasmining masshtabini sozlash."), + _("actor.say_for_sec(gap, soniya, [old rangi=Shart emas], [orqa rangi=Shart emas], [boshqa sozlamalar]) \n\nActor rasmining ustida sozlangan soniya davomida gapni ko'rsatadi."), + _("actor.pos : Actor joylashuvini sozlash. Lekin anchor qiymatiga asoslanadi."), + _("actor.anchor : Actor markazini sozlash."), + _("actor.draw() : Actorni ekranga chiqaradi."), + _("actor.flip_x : True bo'la Actor rasmini x yo'nalishida chappa qiladi."), + _("actor.flip_y : True bo'lsa Actor rasmini y yo'nalishida chappa qiladi."), + _("actor.angle : Actor rasmini sozlagan burchakga aylantiradi."), + _("actor.images : Actor ni animatsiya qilish uchun rasmining guruhini sozlash."), + _("actor.next_image() \n\nActor rasmlarning guruhidagi keyingi tartibli rasmga o'tadi."), + _("actor.move_forward(masofa) \n\nActor sozlangan masofa soniga qarab to'g'ri tomoniga yuradi."), + _("actor.move_back(masofa) \n\nActor sozlangan masofa soniga qarab orqa tomoniga yuradi."), + _("actor.move_left(masofa) \n\nActor sozlangan masofa soniga qarab chap tomoniga yuradi."), + _("actor.move_right(masofa) \n\nActor sozlangan masofa soniga qarab o'ng tomoniga yuradi."), + _("actor.brush_init(maydon, qalinlik, [rang=Shart emas]) \n\nCho'tka qancha maydon ichida qancha qalinlik bilan chizish kerakligini sozlaydi."), + _("actor.brush_draw() \n\nCho'tka chizishni boshlaydi."), + _("actor.brush_stop() \n\nCho'tka chizishni tuxtaydi."), + _("actor.brush_clear() \n\nCho'tka maydonini tozalaydi."), + _("actor.colliderect(obyekt) -> bool \n\nRect-ga asoslanib ikkita Actor obyekti o'rtasida to'qnashuv mavjudligini tekshiradi."), + _("actor.collide_pixel(obyekt) -> (Tuple[int, int] | None) \n\nRang bor pixel-ga asoslanib ikkita Actor obyekti o'rtasida to'qnashuv mavjudligini tekshiradi."), + _("actor.collidepoint_pixel((x, y joylashuvi)) -> int \n\nBerilgan x, y-joylashuviga asoslanib Actor obyekti bilan to'qnashuv mavjudligini tekshiradi."), + _("game.exit() \n\nDasturni tamomlaydi."), + _("sounds.play([ijro etish soni=Shart emas]) \n\nOvozni sozilangan marta ijro etadi. Agar sozlamasa, faqat bir marta ijro etadi."), + _("pygame.display.update([Rect=Shart emas]) \n\nSozilgangan Rect hududdagi ekranni yangilaydi.\nAgar Rect sozilanmagan boʻlsa, butun ekran yangilanadi."), ] } \ No newline at end of file diff --git a/mu/modes/base.py b/mu/modes/base.py index d086c66ff..4dc020912 100644 --- a/mu/modes/base.py +++ b/mu/modes/base.py @@ -379,6 +379,17 @@ def device_changed(self, new_device): """ pass + def merge_apis(self, fallback_apis, locale_apis): + for locale_api in locale_apis: + func_name = locale_api.split('(')[0] + for idx, data in enumerate(fallback_apis): + if func_name in data: + fallback_apis[idx] = locale_api + break + if data[0] == len(fallback_apis) - 1: # new api + fallback_apis.append(locale_api) + return fallback_apis + class MicroPythonMode(BaseMode): """ @@ -647,6 +658,8 @@ class FileManager(QObject): on_get_file = pyqtSignal(str) # Emitted when the file with referenced filename is put onto the device. on_put_file = pyqtSignal(str) + # Emitted while the file with referenced filename is put onto the device. + on_put_update_file = pyqtSignal(int) # Emitted when the file with referenced filename is deleted from the # device. on_delete_file = pyqtSignal(str) @@ -703,11 +716,12 @@ def get(self, device_filename, local_filename): failure signal. """ try: - microfs.get(device_filename, local_filename, serial=self.serial) + microfs.get(self, device_filename, local_filename, serial=self.serial) self.on_get_file.emit(device_filename) except Exception as ex: logger.error(ex) self.on_get_fail.emit(device_filename) + self.on_put_update_file.emit(-1) # To remove the pbar UI def put(self, local_filename, target=None): """ @@ -716,11 +730,12 @@ def put(self, local_filename, target=None): a failure signal. """ try: - microfs.put(local_filename, target=target, serial=self.serial) + microfs.put(self, local_filename, target=target, serial=self.serial) self.on_put_file.emit(os.path.basename(local_filename)) except Exception as ex: logger.error(ex) self.on_put_fail.emit(local_filename) + self.on_put_update_file.emit(-1) # To remove the pbar UI def delete(self, device_filename): """ diff --git a/mu/modes/esp.py b/mu/modes/esp.py index 01ac89daa..72162f039 100644 --- a/mu/modes/esp.py +++ b/mu/modes/esp.py @@ -22,6 +22,7 @@ from mu.interface.panes import CHARTS from PyQt6.QtCore import QThread import os +from mu.resources import load_icon logger = logging.getLogger(__name__) @@ -38,6 +39,7 @@ class ESPMode(MicroPythonMode): description = _("Write MicroPython on ESP8266/ESP32 boards.") icon = "esp" fs = None + running = False # The below list defines the supported devices, however, many # devices are using the exact same FTDI USB-interface, with vendor @@ -127,12 +129,9 @@ def toggle_repl(self, event): if self.repl: # Remove REPL super().toggle_repl(event) - self.set_buttons(files=True) elif not (self.repl): # Add REPL super().toggle_repl(event) - if self.repl: - self.set_buttons(files=False) else: message = _("REPL and file system cannot work at the same time.") information = _( @@ -151,7 +150,7 @@ def toggle_plotter(self, event): super().toggle_plotter(event) if self.plotter: self.set_buttons(files=False) - elif not (self.repl or self.plotter): + elif not self.running: self.set_buttons(files=True) else: message = _( @@ -181,30 +180,45 @@ def run(self): return """ logger.info("Running script.") - # Grab the Python script. - tab = self.view.current_tab - if tab is None: - # There is no active text editor. - message = _("Cannot run anything without any active editor tabs.") - information = _( - "Running transfers the content of the current tab" - " onto the device. It seems like you don't have " - " any tabs open." - ) - self.view.show_message(message, information) - return - python_script = tab.text().split("\n") - if not self.repl: - self.toggle_repl(None) - if self.repl and self.connection: - self.connection.send_commands(python_script) + run_slot = self.view.button_bar.slots["run"] + if not self.running: + # Grab the Python script. + tab = self.view.current_tab + if tab is None: + # There is no active text editor. + message = _("Cannot run anything without any active editor tabs.") + information = _( + "Running transfers the content of the current tab" + " onto the device. It seems like you don't have " + " any tabs open." + ) + self.view.show_message(message, information) + return + run_slot.setIcon(load_icon("stop")) + run_slot.setText(_("Stop")) + self.set_buttons(files=False) + self.set_buttons(repl=False) + python_script = tab.text().split("\n") + if not self.repl: + self.toggle_repl(None) # On + if self.repl and self.connection: + self.connection.send_commands(python_script) + self.running = True + else: + run_slot.setIcon(load_icon("run")) + run_slot.setText(_("Run")) + self.set_buttons(files=True) + self.set_buttons(repl=True) + self.toggle_repl(None) # Off + self.toggle_repl(None) # On - Reset + self.running = False def toggle_files(self, event): """ Check for the existence of the REPL or plotter before toggling the file system navigator for the MicroPython device on or off. """ - if self.repl: + if self.plotter: message = _( "File system cannot work at the same time as the " "REPL or plotter." @@ -216,6 +230,8 @@ def toggle_files(self, event): ) self.view.show_message(message, information) else: + if self.repl: + self.toggle_repl(None) # Off if self.fs is None: self.add_fs() if self.fs: @@ -268,6 +284,7 @@ def add_fs(self): ) self.fs.set_message.connect(self.editor.show_status_message) self.fs.set_warning.connect(self.view.show_message) + self.fs.set_pbar_update.connect(self.editor.show_progressbar_update) self.file_manager_thread.start() def remove_fs(self): diff --git a/mu/modes/neopia.py b/mu/modes/neopia.py index bf8c742fe..24e75713e 100644 --- a/mu/modes/neopia.py +++ b/mu/modes/neopia.py @@ -20,7 +20,7 @@ import os import logging from mu.modes.base import BaseMode -from mu.modes.api import PYTHON3_APIS, SHARED_APIS, PI_APIS, NEOPIA_APIS +from mu.modes.api import PYTHON3_APIS, SHARED_APIS, NEOPIA_APIS from mu.resources import load_icon from mu.interface.panes import CHARTS from ..virtual_environment import venv @@ -196,7 +196,8 @@ def api(self): """ user_locale = self.get_user_locale() try: - neopia_apis = list(NEOPIA_APIS[user_locale]) + # en_US is fallback if no translated api in the locale + neopia_apis = self.merge_apis(list(NEOPIA_APIS['en_US']), list(NEOPIA_APIS[user_locale])) except KeyError: # In case a translation is not exist neopia_apis = list(NEOPIA_APIS['en_US']) diff --git a/mu/modes/pico.py b/mu/modes/pico.py index f2833719c..e369059e2 100644 --- a/mu/modes/pico.py +++ b/mu/modes/pico.py @@ -18,7 +18,7 @@ """ import logging from mu.modes.esp import ESPMode -from mu.modes.api import SHARED_APIS # TODO: Work out Pico APIs +from mu.modes.api import SHARED_APIS, PICO_APIS logger = logging.getLogger(__name__) @@ -47,4 +47,11 @@ def api(self): Return a list of API specifications to be used by auto-suggest and call tips. """ - return SHARED_APIS + user_locale = self.get_user_locale() + try: + # en_US is fallback if no translated api in the locale + pico_apis = self.merge_apis(list(PICO_APIS['en_US']), list(PICO_APIS[user_locale])) + except KeyError: # In case a translation is not exist + pico_apis = list(PICO_APIS['en_US']) + + return SHARED_APIS + pico_apis diff --git a/mu/modes/pygamezero.py b/mu/modes/pygamezero.py index f6d73b2ef..ba21f2cb8 100644 --- a/mu/modes/pygamezero.py +++ b/mu/modes/pygamezero.py @@ -112,15 +112,18 @@ def api(self): """ user_locale = self.get_user_locale() try: - pygamezero_apis = list(PYGAMEZERO_APIS[user_locale]) + # en_US is fallback if no translated api in the locale + pgz_apis = self.merge_apis(list(PYGAMEZERO_APIS['en_US']), list(PYGAMEZERO_APIS[user_locale])) except KeyError: # In case a translation is not exist - pygamezero_apis = list(PYGAMEZERO_APIS['en_US']) + pgz_apis = list(PYGAMEZERO_APIS['en_US']) + try: - neopia_apis = list(NEOPIA_APIS[user_locale]) + # en_US is fallback if no translated api in the locale + neopia_apis = self.merge_apis(list(NEOPIA_APIS['en_US']), list(NEOPIA_APIS[user_locale])) except KeyError: # In case a translation is not exist neopia_apis = list(NEOPIA_APIS['en_US']) - return SHARED_APIS + PYTHON3_APIS + PI_APIS + pygamezero_apis + neopia_apis + return SHARED_APIS + PYTHON3_APIS + PI_APIS + pgz_apis + neopia_apis def play_toggle(self, event): """ diff --git a/mu/resources/neopia/01-01_KobiBot.py b/mu/resources/neopia/01-01_KobiBot.py new file mode 100644 index 000000000..cfa9c28d2 --- /dev/null +++ b/mu/resources/neopia/01-01_KobiBot.py @@ -0,0 +1,15 @@ +from neopia import * + +n = Neosoco() + +# LEDni yoqib 1 soniyadan keyin o'chirish +n.led_on('out1','100') +wait(1000) # milisekund orqali hisoblanadi + + +# Takrorlab miltiratish +while True: + n.led_on('out1','100') + wait(1000) + n.led_off('out1') + wait(1000) \ No newline at end of file diff --git a/mu/resources/neopia/01-02_FutbolBot.py b/mu/resources/neopia/01-02_FutbolBot.py new file mode 100644 index 000000000..4795b5556 --- /dev/null +++ b/mu/resources/neopia/01-02_FutbolBot.py @@ -0,0 +1,20 @@ +from neopia import * + +n = Neosoco() + +def on_press(key): + if key == Keyboard.UP: + n.motor_rotate('both', 'forward', '10') + elif key == Keyboard.DOWN: + n.motor_rotate('both', 'backward', '10') + elif key == Keyboard.LEFT: + n.motor_rotate('both', 'left', '10') + elif key == Keyboard.RIGHT: + n.motor_rotate('both', 'right', '10') + elif key == Keyboard.SPACE: + n.motor_stop('both') + elif key == Keyboard.ESC: + return False + +# Klaviaturning tugmachasini bosganda "on_press" funksiyasi chaqriladi +Keyboard.read(on_press) \ No newline at end of file diff --git a/mu/resources/neopia/01-06_Sehrli_ansanmbl.py b/mu/resources/neopia/01-06_Sehrli_ansanmbl.py new file mode 100644 index 000000000..1eab8edc4 --- /dev/null +++ b/mu/resources/neopia/01-06_Sehrli_ansanmbl.py @@ -0,0 +1,10 @@ +# Ichki buzzer orqali yangrash + +from neopia import * + +n = Neosoco() + +while True: + n.buzzer_by_port('in1') + wait(200) + n.buzzer_stop() \ No newline at end of file diff --git a/mu/resources/neopia/02-01-01_Barami.py b/mu/resources/neopia/02-01-01_Barami.py new file mode 100644 index 000000000..6cd1b9d90 --- /dev/null +++ b/mu/resources/neopia/02-01-01_Barami.py @@ -0,0 +1,12 @@ +# Ovoz sensor orqali Baramining tezligini boshqarish + +from neopia import * + +n = Neosoco() + +while True: + if n.get_value('in3') < 30: + n.motor_stop('right') + else: + n.motor_rotate('right', 'forward', 'in3') + \ No newline at end of file diff --git a/mu/resources/neopia/02-01-02_Barami.py b/mu/resources/neopia/02-01-02_Barami.py new file mode 100644 index 000000000..bfdea1065 --- /dev/null +++ b/mu/resources/neopia/02-01-02_Barami.py @@ -0,0 +1,20 @@ +# Klaviatura orqali Baramining tezligini boshqarish + +from neopia import * + +n = Neosoco() + +def on_press(key): + if Keyboard.key_to_str(key) == '0': + n.motor_stop('right') + elif Keyboard.key_to_str(key) == '1': + n.motor_rotate('right', 'forward', '10') + elif Keyboard.key_to_str(key) == '2': + n.motor_rotate('right', 'forward', '50') + elif Keyboard.key_to_str(key) == '3': + n.motor_rotate('right', 'forward', '100') + elif key == Keyboard.ESC: + return False + +# Klaviaturning tugmachasini bosganda "on_press" funksiyasi chaqriladi +Keyboard.read(on_press) \ No newline at end of file diff --git a/mu/resources/neopia/02-02_Neo_Bigl.py b/mu/resources/neopia/02-02_Neo_Bigl.py new file mode 100644 index 000000000..b61544ae6 --- /dev/null +++ b/mu/resources/neopia/02-02_Neo_Bigl.py @@ -0,0 +1,13 @@ +from neopia import * + +n = Neosoco() + +while True: + while n.get_value('in3') > 50: + continue + while n.get_value('in3') <= 50: + continue + + n.motor_rotate('both', 'left', '50') + wait(3000) + n.motor_stop('both') \ No newline at end of file diff --git a/mu/resources/neopia/02-03_Neo_kliner.py b/mu/resources/neopia/02-03_Neo_kliner.py new file mode 100644 index 000000000..433111118 --- /dev/null +++ b/mu/resources/neopia/02-03_Neo_kliner.py @@ -0,0 +1,9 @@ +from neopia import * + +n = Neosoco() + +while True: + if n.get_value('in1') < 20: + n.motor_rotate('both', 'forward', '50') + else: + n.motor_stop('both') \ No newline at end of file diff --git a/mu/resources/neopia/02-04-01_Aqlli_avtomobil.py b/mu/resources/neopia/02-04-01_Aqlli_avtomobil.py new file mode 100644 index 000000000..2b611ca82 --- /dev/null +++ b/mu/resources/neopia/02-04-01_Aqlli_avtomobil.py @@ -0,0 +1,17 @@ +# Masofa sensor orqali to’siqdan qochish dasturi + +from neopia import * + +n = Neosoco() + +while True: + if n.get_value('in2') <= 7: + n.motor_move('backward') + wait(500) + n.motor_stop('both') + n.motor_move('left') + wait(1000) + n.motor_stop('both') + else: + n.motor_rotate('both', 'forward', '50') + diff --git a/mu/resources/neopia/02-04-02_Aqlli_avtomobil.py b/mu/resources/neopia/02-04-02_Aqlli_avtomobil.py new file mode 100644 index 000000000..5b94d9645 --- /dev/null +++ b/mu/resources/neopia/02-04-02_Aqlli_avtomobil.py @@ -0,0 +1,9 @@ +# Acc Kontrol dasturalsh + +from neopia import * + +n = Neosoco() + +while True: + n.motor_rotate('both', 'forward', 'in2') + n.buzzer_by_port('in2') \ No newline at end of file diff --git a/mu/resources/neopia/02-05-01_Chiziq_trekeri.py b/mu/resources/neopia/02-05-01_Chiziq_trekeri.py new file mode 100644 index 000000000..7204373dd --- /dev/null +++ b/mu/resources/neopia/02-05-01_Chiziq_trekeri.py @@ -0,0 +1,12 @@ +from neopia import * + +n = Neosoco() + +while True: + if n.get_value('in1') and n.get_value('in2') > 40: + n.motor_rotate('both', 'forward', '30') + else: + if n.get_value('in1') <= 40: + n.motor_stop('left') + if n.get_value('in2') <= 40: + n.motor_stop('right') diff --git a/mu/resources/neopia/02-05-02_Chiziq_trekeri.py b/mu/resources/neopia/02-05-02_Chiziq_trekeri.py new file mode 100644 index 000000000..311b7ec11 --- /dev/null +++ b/mu/resources/neopia/02-05-02_Chiziq_trekeri.py @@ -0,0 +1,16 @@ +from neopia import * + +n = Neosoco() + +while True: + if n.get_value('in1') and n.get_value('in2') > 40: + n.motor_rotate('both', 'forward', '30') + else: + if n.get_value('in1') <= 40: + n.motor_stop('left') + if n.get_value('in2') <= 40: + n.motor_stop('right') + if n.get_value('in1') and n.get_value('in2') <= 40: + n.motor_move('left') + wait(500) + n.motor_stop('both') \ No newline at end of file diff --git a/mu/resources/neopia/03-01_AI_Radiokarnay.py b/mu/resources/neopia/03-01_AI_Radiokarnay.py new file mode 100644 index 000000000..f1a121537 --- /dev/null +++ b/mu/resources/neopia/03-01_AI_Radiokarnay.py @@ -0,0 +1,19 @@ +from neopia import * + +n = Neosoco() +result = False + +while not result: + try: + Voice.tts("I'm hearing. Please speak in 3 secconds.") + print("Eshityapman. 3 soniya ichida gapirib bering.") + result = Voice.stt() + print(result) + if result == "Chiroqni yoq": + n.led_on() + wait(3000) + elif result == "Chiroqni o'chir": + n.led_off() + wait(3000) + except: + result = False diff --git a/mu/resources/neopia/03-02_AI_Kamera.py b/mu/resources/neopia/03-02_AI_Kamera.py new file mode 100644 index 000000000..4ba206d4f --- /dev/null +++ b/mu/resources/neopia/03-02_AI_Kamera.py @@ -0,0 +1,12 @@ +from neopia import * + +n = Neosoco() + +fd = FaceDetection() +if fd.camera_open(0): + while True: + if fd.start_detection() > 0: + print('Kimdir kirdi!!!') + n.led_on() + n.buzzer() + n.led_off() \ No newline at end of file diff --git a/mu/resources/neopia/03-03_AI_Obyekt_topish.py b/mu/resources/neopia/03-03_AI_Obyekt_topish.py new file mode 100644 index 000000000..e93b18c42 --- /dev/null +++ b/mu/resources/neopia/03-03_AI_Obyekt_topish.py @@ -0,0 +1,17 @@ +from neopia import * + +n = Neosoco() +od = ObjectDetection() + +if od.camera_open(0): + while True: + result = od.start_detection() + if result: + obj_names, obj_coords = result + if 'sports ball' in obj_names: + n.led_on('out1', '100') + n.motor_rotate('both', 'forward', '10') + wait(1000) + n.led_off('out1') + else: + n.motor_rotate('both', 'left', '10') \ No newline at end of file diff --git a/mu/resources/neopia/04-01_Fonar.py b/mu/resources/neopia/04-01_Fonar.py new file mode 100644 index 000000000..fb50a0440 --- /dev/null +++ b/mu/resources/neopia/04-01_Fonar.py @@ -0,0 +1,9 @@ +from neopia import * + +n = Neosoco() + +while True: + if n.get_value('in1') == 255: + n.led_on('out1', '100') + else: + n.led_off('out1') \ No newline at end of file diff --git a/mu/resources/neopia/04-03-01_Aqlli_avtomobil_2.py b/mu/resources/neopia/04-03-01_Aqlli_avtomobil_2.py new file mode 100644 index 000000000..7e43f0873 --- /dev/null +++ b/mu/resources/neopia/04-03-01_Aqlli_avtomobil_2.py @@ -0,0 +1,25 @@ +from neopia import * + +n = Neosoco() + +while True: + if n.get_value('in2') <= 10: + n.motor_stop('both') + n.servo_reset_degree('out3') + wait(100) + n.servo_rotate_by_degree('out3', 'forward', '100', '90') + wait(1000) + chap_m = n.get_value('in2') + n.servo_rotate_by_degree('out3', 'backward', '100', '90') + wait(1000) + ong_m = n.get_value('in2') + n.servo_rotate_by_degree('out3', 'forward', '100', '0') + wait(1000) + if chap_m > ong_m: + n.motor_rotate('both', 'left', '50') + wait(1000) + else: + n.motor_rotate('both', 'right', '50') + wait(1000) + else: + n.motor_rotate('both', 'forward', '50') diff --git a/mu/resources/neopia/04-03-02_Aqlli_avtomobil_2.py b/mu/resources/neopia/04-03-02_Aqlli_avtomobil_2.py new file mode 100644 index 000000000..4f7d01a88 --- /dev/null +++ b/mu/resources/neopia/04-03-02_Aqlli_avtomobil_2.py @@ -0,0 +1,35 @@ +from neopia import * + +n = Neosoco() +chap_m = 0 +ong_m = 0 + +# Masofa sensor orqali to‘siqni tekshirish uchun funksiya yaratish +def servo_motor(): + global chap_m + global ong_m + + n.motor_stop('both') + n.servo_reset_degree('out3') + wait(100) + n.servo_rotate_by_degree('out3', 'forward', '100', '90') + wait(1000) + chap_m = n.get_value('in2') + n.servo_rotate_by_degree('out3', 'backward', '100', '90') + wait(1000) + ong_m = n.get_value('in2') + n.servo_rotate_by_degree('out3', 'forward', '100', '0') + wait(1000) + +# Funksiyadan foydalanib dastur tuzish +while True: + if n.get_value('in2') <= 10: + servo_motor() + if chap_m > ong_m: + n.motor_rotate('both', 'right', '50') + wait(1000) + else: + n.motor_rotate('both', 'left', '50') + wait(1000) + else: + n.motor_rotate('both', 'forward', '50') diff --git a/mu/resources/neopia/04-04_Samosval_avtomobili.py b/mu/resources/neopia/04-04_Samosval_avtomobili.py new file mode 100644 index 000000000..35205f5e3 --- /dev/null +++ b/mu/resources/neopia/04-04_Samosval_avtomobili.py @@ -0,0 +1,15 @@ +from neopia import * + +n = Neosoco() +n.servo_reset_degree('out1') + +def on_press(key): + if Keyboard.key_to_str(key) == '4': + n.servo_rotate_by_degree('out1', 'forward', '20', '60') + elif Keyboard.key_to_str(key) == '1': + n.servo_rotate_by_degree('out1', 'forward', '20', '0') + elif key == Keyboard.ESC: + return False + +# Klaviaturning tugmachasini bosganda "on_press" funksiyasi chaqriladi +Keyboard.read(on_press) \ No newline at end of file diff --git a/mu/resources/neopia/04-05_Robot_qol.py b/mu/resources/neopia/04-05_Robot_qol.py new file mode 100644 index 000000000..f647298a3 --- /dev/null +++ b/mu/resources/neopia/04-05_Robot_qol.py @@ -0,0 +1,17 @@ +from neopia import * + +n = Neosoco() + +while True: + if n.get_value('in1') < 80 and n.get_value('in2') < 80: + n.servo_stop('out1') + n.motor_stop('both') + elif n.get_value('in1') > 80 and n.get_value('in2') > 80: + n.servo_stop('out1') + n.motor_rotate('both', 'backward', '40') + elif n.get_value('in1') > 80 and n.get_value('in2') < 80: + n.servo_rotate('out1', 'forward', '10') + n.motor_rotate('both', 'forward', '30') + elif n.get_value('in1') < 80 and n.get_value('in2') > 80: + n.servo_rotate('out1', 'backward', '10') + n.motor_rotate('both', 'forward', '30') diff --git a/mu/resources/neopia/04-06_Konveyer.py b/mu/resources/neopia/04-06_Konveyer.py new file mode 100644 index 000000000..2341b9233 --- /dev/null +++ b/mu/resources/neopia/04-06_Konveyer.py @@ -0,0 +1,15 @@ +from neopia import * + +n = Neosoco() + +n.servo_reset_degree('out1') +n.motor_rotate('both', 'forward', '40') + +while True: + if n.get_value('in1') > 20 and n.get_value('in2') > 20: + n.servo_rotate_by_degree('out1', 'forward', '50', '20') + wait(500) + if n.get_value('in1') <= 20 and n.get_value('in2') <= 20: + if n.get_value('in2') > 0: + n.servo_rotate_by_degree('out1', 'backward', '50', '20') + wait(500) \ No newline at end of file diff --git a/mu/resources/pico/picozero.py b/mu/resources/pico/picozero.py new file mode 100644 index 000000000..a046bfe30 --- /dev/null +++ b/mu/resources/pico/picozero.py @@ -0,0 +1,2232 @@ +from machine import Pin, PWM, Timer, ADC, I2C +from micropython import schedule +from time import ticks_ms, ticks_us, sleep_ms, sleep +import time + + +############################################################################### +# LCD DEVICE +# This code was modified from original codes at https://github.com/T-622/RPI-PICO-I2C-LCD +############################################################################### +# Defines shifts or masks for the various LCD line attached to the PCF8574 +MASK_RS = 0x01 +MASK_RW = 0x02 +MASK_E = 0x04 +SHIFT_BACKLIGHT = 3 +SHIFT_DATA = 4 + +class LcdApi: + + LCD_CLR = 0x01 # DB0: clear display + LCD_HOME = 0x02 # DB1: return to home position + + LCD_ENTRY_MODE = 0x04 # DB2: set entry mode + LCD_ENTRY_INC = 0x02 # --DB1: increment + LCD_ENTRY_SHIFT = 0x01 # --DB0: shift + + LCD_ON_CTRL = 0x08 # DB3: turn lcd/cursor on + LCD_ON_DISPLAY = 0x04 # --DB2: turn display on + LCD_ON_CURSOR = 0x02 # --DB1: turn cursor on + LCD_ON_BLINK = 0x01 # --DB0: blinking cursor + + LCD_MOVE = 0x10 # DB4: move cursor/display + LCD_MOVE_DISP = 0x08 # --DB3: move display (0-> move cursor) + LCD_MOVE_RIGHT = 0x04 # --DB2: move right (0-> left) + + LCD_FUNCTION = 0x20 # DB5: function set + LCD_FUNCTION_8BIT = 0x10 # --DB4: set 8BIT mode (0->4BIT mode) + LCD_FUNCTION_2LINES = 0x08 # --DB3: two lines (0->one line) + LCD_FUNCTION_10DOTS = 0x04 # --DB2: 5x10 font (0->5x7 font) + LCD_FUNCTION_RESET = 0x30 # See "Initializing by Instruction" section + + LCD_CGRAM = 0x40 # DB6: set CG RAM address + LCD_DDRAM = 0x80 # DB7: set DD RAM address + + LCD_RS_CMD = 0 + LCD_RS_DATA = 1 + + LCD_RW_WRITE = 0 + LCD_RW_READ = 1 + + def __init__(self, num_lines, num_columns): + self.num_lines = num_lines + if self.num_lines > 4: + self.num_lines = 4 + self.num_columns = num_columns + if self.num_columns > 40: + self.num_columns = 40 + self.cursor_x = 0 + self.cursor_y = 0 + self.implied_newline = False + self.backlight = True + self.display_off() + self.backlight_on() + self.clear() + self.hal_write_command(self.LCD_ENTRY_MODE | self.LCD_ENTRY_INC) + self.hide_cursor() + self.display_on() + + def clear(self): + """Clears the LCD display and moves the cursor to the top left + corner. + """ + self.hal_write_command(self.LCD_CLR) + self.hal_write_command(self.LCD_HOME) + self.cursor_x = 0 + self.cursor_y = 0 + + def show_cursor(self): + """Causes the cursor to be made visible.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | + self.LCD_ON_CURSOR) + + def hide_cursor(self): + """Causes the cursor to be hidden.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY) + + def blink_cursor_on(self): + """Turns on the cursor, and makes it blink.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | + self.LCD_ON_CURSOR | self.LCD_ON_BLINK) + + def blink_cursor_off(self): + """Turns on the cursor, and makes it no blink (i.e. be solid).""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | + self.LCD_ON_CURSOR) + + def display_on(self): + """Turns on (i.e. unblanks) the LCD.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY) + + def display_off(self): + """Turns off (i.e. blanks) the LCD.""" + self.hal_write_command(self.LCD_ON_CTRL) + + def backlight_on(self): + """Turns the backlight on. + This isn't really an LCD command, but some modules have backlight + controls, so this allows the hal to pass through the command. + """ + self.backlight = True + self.hal_backlight_on() + + def backlight_off(self): + """Turns the backlight off. + This isn't really an LCD command, but some modules have backlight + controls, so this allows the hal to pass through the command. + """ + self.backlight = False + self.hal_backlight_off() + + def move_to(self, cursor_x, cursor_y): + """Moves the cursor position to the indicated position. The cursor + position is zero based (i.e. cursor_x == 0 indicates first column). + """ + self.cursor_x = cursor_x + self.cursor_y = cursor_y + addr = cursor_x & 0x3f + if cursor_y & 1: + addr += 0x40 # Lines 1 & 3 add 0x40 + if cursor_y & 2: # Lines 2 & 3 add number of columns + addr += self.num_columns + self.hal_write_command(self.LCD_DDRAM | addr) + + def putchar(self, char): + """Writes the indicated character to the LCD at the current cursor + position, and advances the cursor by one position. + """ + if char == '\n': + if self.implied_newline: + # self.implied_newline means we advanced due to a wraparound, + # so if we get a newline right after that we ignore it. + pass + else: + self.cursor_x = self.num_columns + else: + self.hal_write_data(ord(char)) + self.cursor_x += 1 + if self.cursor_x >= self.num_columns: + self.cursor_x = 0 + self.cursor_y += 1 + self.implied_newline = (char != '\n') + if self.cursor_y >= self.num_lines: + self.cursor_y = 0 + self.move_to(self.cursor_x, self.cursor_y) + + def putstr(self, string): + """Write the indicated string to the LCD at the current cursor + position and advances the cursor position appropriately. + """ + for char in string: + self.putchar(char) + + def custom_char(self, location, charmap): + """Write a character to one of the 8 CGRAM locations, available + as chr(0) through chr(7). + """ + location &= 0x7 + self.hal_write_command(self.LCD_CGRAM | (location << 3)) + self.hal_sleep_us(40) + for i in range(8): + self.hal_write_data(charmap[i]) + self.hal_sleep_us(40) + self.move_to(self.cursor_x, self.cursor_y) + + def hal_backlight_on(self): + """Allows the hal layer to turn the backlight on. + If desired, a derived HAL class will implement this function. + """ + pass + + def hal_backlight_off(self): + """Allows the hal layer to turn the backlight off. + If desired, a derived HAL class will implement this function. + """ + pass + + def hal_write_command(self, cmd): + """Write a command to the LCD. + It is expected that a derived HAL class will implement this + function. + """ + raise NotImplementedError + + def hal_write_data(self, data): + """Write data to the LCD. + It is expected that a derived HAL class will implement this + function. + """ + raise NotImplementedError + + def hal_sleep_us(self, usecs): + """Sleep for some time (given in microseconds).""" + time.sleep_us(usecs) + +class I2cLcd(LcdApi): + """Implements a character based lcd connected via PCF8574 on i2c.""" + + def __init__(self, scl_pin, sda_pin, i2c_addr=0x27, num_lines=2, num_columns=16): + self.i2c = I2C(id=1,scl=Pin(scl_pin),sda=Pin(sda_pin),freq=100000) + self.i2c_addr = i2c_addr + self.i2c.writeto(self.i2c_addr, bytearray([0])) + sleep_ms(20) # Allow LCD time to powerup + # Send reset 3 times + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + sleep_ms(5) # need to delay at least 4.1 msec + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + sleep_ms(1) + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + sleep_ms(1) + # Put LCD into 4 bit mode + self.hal_write_init_nibble(self.LCD_FUNCTION) + sleep_ms(1) + LcdApi.__init__(self, num_lines, num_columns) + cmd = self.LCD_FUNCTION + if num_lines > 1: + cmd |= self.LCD_FUNCTION_2LINES + self.hal_write_command(cmd) + + def hal_write_init_nibble(self, nibble): + """Writes an initialization nibble to the LCD. + This particular function is only used during intiialization. + """ + byte = ((nibble >> 4) & 0x0f) << SHIFT_DATA + self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E])) + self.i2c.writeto(self.i2c_addr, bytearray([byte])) + + def hal_backlight_on(self): + """Allows the hal layer to turn the backlight on.""" + self.i2c.writeto(self.i2c_addr, bytearray([1 << SHIFT_BACKLIGHT])) + + def hal_backlight_off(self): + """Allows the hal layer to turn the backlight off.""" + self.i2c.writeto(self.i2c_addr, bytearray([0])) + + def hal_write_command(self, cmd): + """Writes a command to the LCD. + Data is latched on the falling edge of E. + """ + byte = ((self.backlight << SHIFT_BACKLIGHT) | (((cmd >> 4) & 0x0f) << SHIFT_DATA)) + self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E])) + self.i2c.writeto(self.i2c_addr, bytearray([byte])) + byte = ((self.backlight << SHIFT_BACKLIGHT) | ((cmd & 0x0f) << SHIFT_DATA)) + self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E])) + self.i2c.writeto(self.i2c_addr, bytearray([byte])) + if cmd <= 3: + # The home and clear commands require a worst case delay of 4.1 msec + sleep_ms(5) + + def hal_write_data(self, data): + """Write data to the LCD.""" + byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | (((data >> 4) & 0x0f) << SHIFT_DATA)) + self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E])) + self.i2c.writeto(self.i2c_addr, bytearray([byte])) + byte = (MASK_RS | (self.backlight << SHIFT_BACKLIGHT) | ((data & 0x0f) << SHIFT_DATA)) + self.i2c.writeto(self.i2c_addr, bytearray([byte | MASK_E])) + self.i2c.writeto(self.i2c_addr, bytearray([byte])) + +############################################################################### +# EXCEPTIONS +############################################################################### + +class PWMChannelAlreadyInUse(Exception): + pass + +class EventFailedScheduleQueueFull(Exception): + pass + +############################################################################### +# SUPPORTING CLASSES +############################################################################### + +def clamp(n, low, high): return max(low, min(n, high)) + +def pinout(output=True): + """ + Returns a textual representation of the Raspberry Pi pico pins and functions. + + :param bool output: + If :data:`True` (the default) the pinout will be "printed". + + """ + pins = """ ---usb--- +GP0 1 |o o| -1 VBUS +GP1 2 |o o| -2 VSYS +GND 3 |o o| -3 GND +GP2 4 |o o| -4 3V3_EN +GP3 5 |o o| -5 3V3(OUT) +GP4 6 |o o| -6 ADC_VREF +GP5 7 |o o| -7 GP28 ADC2 +GND 8 |o o| -8 GND AGND +GP6 9 |o o| -9 GP27 ADC1 +GP7 10 |o o| -10 GP26 ADC0 +GP8 11 |o o| -11 RUN +GP9 12 |o o| -12 GP22 +GND 13 |o o| -13 GND +GP10 14 |o o| -14 GP21 +GP11 15 |o o| -15 GP20 +GP12 16 |o o| -16 GP19 +GP13 17 |o o| -17 GP18 +GND 18 |o o| -18 GND +GP14 19 |o o| -19 GP17 +GP15 20 |o o| -20 GP16 + ---------""" + + if output: + print(pins) + return pins + +class PinMixin: + """ + Mixin used by devices that have a single pin number. + """ + + @property + def pin(self): + """ + Returns the pin number used by the device. + """ + return self._pin_num + + def __str__(self): + return "{} (pin {})".format(self.__class__.__name__, self._pin_num) + +class PinsMixin: + """ + Mixin used by devices that use multiple pins. + """ + + @property + def pins(self): + """ + Returns a tuple of pins used by the device. + """ + return self._pin_nums + + def __str__(self): + return "{} (pins - {})".format(self.__class__.__name__, self._pin_nums) + +class ValueChange: + """ + Internal class to control the value of an output device. + + :param OutputDevice output_device: + The OutputDevice object you wish to change the value of. + + :param generator: + A generator function that yields a 2d list of + ((value, seconds), *). + + The output_device's value will be set for the number of + seconds. + + :param int n: + The number of times to repeat the sequence. If None, the + sequence will repeat forever. + + :param bool wait: + If True the ValueChange object will block (wait) until + the sequence has completed. + """ + def __init__(self, output_device, generator, n, wait): + self._output_device = output_device + self._generator = generator + self._n = n + + self._gen = self._generator() + + self._timer = Timer() + self._running = True + self._wait = wait + + self._set_value() + + def _set_value(self, timer_obj=None): + if self._wait: + # wait for the exection to end + next_seq = self._get_value() + while next_seq is not None: + value, seconds = next_seq + + self._output_device._write(value) + sleep(seconds) + + next_seq = self._get_value() + + else: + # run the timer + next_seq = self._get_value() + if next_seq is not None: + value, seconds = next_seq + + self._output_device._write(value) + self._timer.init(period=int(seconds * 1000), mode=Timer.ONE_SHOT, callback=self._set_value) + + if next_seq is None: + # the sequence has finished, turn the device off + self._output_device.off() + self._running = False + + def _get_value(self): + try: + return next(self._gen) + + except StopIteration: + + self._n = self._n - 1 if self._n is not None else None + if self._n == 0: + # it's the end, return None + return None + else: + # recreate the generator and start again + self._gen = self._generator() + return next(self._gen) + + def stop(self): + """ + Stops the ValueChange object running. + """ + self._running = False + self._timer.deinit() + +############################################################################### +# OUTPUT DEVICES +############################################################################### + +class OutputDevice: + """ + Base class for output devices. + """ + def __init__(self, active_high=True, initial_value=False): + self.active_high = active_high + if initial_value is not None: + self._write(initial_value) + self._value_changer = None + + @property + def active_high(self): + """ + Sets or returns the active_high property. If :data:`True`, the + :meth:`on` method will set the Pin to HIGH. If :data:`False`, + the :meth:`on` method will set the Pin to LOW (the :meth:`off` method + always does the opposite). + """ + return self._active_state + + @active_high.setter + def active_high(self, value): + self._active_state = True if value else False + self._inactive_state = False if value else True + + @property + def value(self): + """ + Sets or returns a value representing the state of the device: 1 is on, 0 is off. + """ + return self._read() + + @value.setter + def value(self, value): + self._stop_change() + self._write(value) + + def on(self, value=1, t=None, wait=False): + """ + Turns the device on. + + :param float value: + The value to set when turning on. Defaults to 1. + + :param float t: + The time in seconds that the device should be on. If None is + specified, the device will stay on. The default is None. + + :param bool wait: + If True, the method will block until the time `t` has expired. + If False, the method will return and the device will turn on in + the background. Defaults to False. Only effective if `t` is not + None. + """ + if t is None: + self.value = value + else: + self._start_change(lambda : iter([(value, t), ]), 1, wait) + + def off(self): + """ + Turns the device off. + """ + self.value = 0 + + @property + def is_active(self): + """ + Returns :data:`True` if the device is on. + """ + return bool(self.value) + + def toggle(self): + """ + If the device is off, turn it on. If it is on, turn it off. + """ + if self.is_active: + self.off() + else: + self.on() + + def blink(self, on_time=1, off_time=None, n=None, wait=False): + """ + Makes the device turn on and off repeatedly. + + :param float on_time: + The length of time in seconds that the device will be on. Defaults to 1. + + :param float off_time: + The length of time in seconds that the device will be off. If `None`, + it will be the same as ``on_time``. Defaults to `None`. + + :param int n: + The number of times to repeat the blink operation. If None is + specified, the device will continue blinking forever. The default + is None. + + :param bool wait: + If True, the method will block until the device stops turning on and off. + If False, the method will return and the device will turn on and off in + the background. Defaults to False. + """ + off_time = on_time if off_time is None else off_time + + self.off() + + # is there anything to change? + if on_time > 0 or off_time > 0: + self._start_change(lambda : iter([(1,on_time), (0,off_time)]), n, wait) + + def _start_change(self, generator, n, wait): + self._value_changer = ValueChange(self, generator, n, wait) + + def _stop_change(self): + if self._value_changer is not None: + self._value_changer.stop() + self._value_changer = None + + def close(self): + """ + Turns the device off. + """ + self.value = 0 + +class DigitalOutputDevice(OutputDevice, PinMixin): + """ + Represents a device driven by a digital pin. + + :param int pin: + The pin that the device is connected to. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the Pin + to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to + LOW (the :meth:`off` method always does the opposite). + + :param bool initial_value: + If :data:`False` (the default), the LED will be off initially. If + :data:`True`, the LED will be switched on initially. + """ + def __init__(self, pin, active_high=True, initial_value=False): + self._pin_num = pin + self._pin = Pin(pin, Pin.OUT) + super().__init__(active_high, initial_value) + + def _value_to_state(self, value): + return int(self._active_state if value else self._inactive_state) + + def _state_to_value(self, state): + return int(bool(state) == self._active_state) + + def _read(self): + return self._state_to_value(self._pin.value()) + + def _write(self, value): + self._pin.value(self._value_to_state(value)) + + def close(self): + """ + Closes the device and turns the device off. Once closed, the device + can no longer be used. + """ + super().close() + self._pin = None + +class DigitalLED(DigitalOutputDevice): + """ + Represents a simple LED, which can be switched on and off. + + :param int pin: + The pin that the device is connected to. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the Pin + to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to + LOW (the :meth:`off` method always does the opposite). + + :param bool initial_value: + If :data:`False` (the default), the LED will be off initially. If + :data:`True`, the LED will be switched on initially. + """ + pass + +DigitalLED.is_lit = DigitalLED.is_active + +class Buzzer(DigitalOutputDevice): + """ + Represents an active or passive buzzer, which can be turned on or off. + + :param int pin: + The pin that the device is connected to. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the Pin + to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to + LOW (the :meth:`off` method always does the opposite). + + :param bool initial_value: + If :data:`False` (the default), the Buzzer will be off initially. If + :data:`True`, the Buzzer will be switched on initially. + """ + pass + +Buzzer.beep = Buzzer.blink + +class PWMOutputDevice(OutputDevice, PinMixin): + """ + Represents a device driven by a PWM pin. + + :param int pin: + The pin that the device is connected to. + + :param int freq: + The frequency of the PWM signal in hertz. Defaults to 100. + + :param int duty_factor: + The duty factor of the PWM signal. This is a value between 0 and 65535. + Defaults to 65535. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the Pin + to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to + LOW (the :meth:`off` method always does the opposite). + + :param bool initial_value: + If :data:`False` (the default), the LED will be off initially. If + :data:`True`, the LED will be switched on initially. + """ + + PIN_TO_PWM_CHANNEL = ["0A","0B","1A","1B","2A","2B","3A","3B","4A","4B","5A","5B","6A","6B","7A","7B","0A","0B","1A","1B","2A","2B","3A","3B","4A","4B","5A","5B","6A","6B"] + _channels_used = {} + + def __init__(self, pin, freq=100, duty_factor=65535, active_high=True, initial_value=False): + self._check_pwm_channel(pin) + self._pin_num = pin + self._duty_factor = duty_factor + self._pwm = PWM(Pin(pin)) + self._pwm.freq(freq) + super().__init__(active_high, initial_value) + + def _check_pwm_channel(self, pin_num): + channel = PWMOutputDevice.PIN_TO_PWM_CHANNEL[pin_num] + if channel in PWMOutputDevice._channels_used.keys(): + raise PWMChannelAlreadyInUse( + "PWM channel {} is already in use by {}. Use a different pin".format( + channel, + str(PWMOutputDevice._channels_used[channel]) + ) + ) + else: + PWMOutputDevice._channels_used[channel] = self + + def _state_to_value(self, state): + return (state if self.active_high else self._duty_factor - state) / self._duty_factor + + def _value_to_state(self, value): + return int(self._duty_factor * (value if self.active_high else 1 - value)) + + def _read(self): + return self._state_to_value(self._pwm.duty_u16()) + + def _write(self, value): + self._pwm.duty_u16(self._value_to_state(value)) + + @property + def is_active(self): + """ + Returns :data:`True` if the device is on. + """ + return self.value != 0 + + @property + def freq(self): + """ + Returns the current frequency of the device. + """ + return self._pwm.freq() + + @freq.setter + def freq(self, freq): + """ + Sets the frequency of the device. + """ + self._pwm.freq(freq) + + def blink(self, on_time=1, off_time=None, n=None, wait=False, fade_in_time=0, fade_out_time=None, fps=25): + """ + Makes the device turn on and off repeatedly. + + :param float on_time: + The length of time in seconds the device will be on. Defaults to 1. + + :param float off_time: + The length of time in seconds the device will be off. If `None`, + it will be the same as ``on_time``. Defaults to `None`. + + :param int n: + The number of times to repeat the blink operation. If `None`, the + device will continue blinking forever. The default is `None`. + + :param bool wait: + If True, the method will block until the LED stops blinking. If False, + the method will return and the LED will blink in the background. + Defaults to False. + + :param float fade_in_time: + The length of time in seconds to spend fading in. Defaults to 0. + + :param float fade_out_time: + The length of time in seconds to spend fading out. If `None`, + it will be the same as ``fade_in_time``. Defaults to `None`. + + :param int fps: + The frames per second that will be used to calculate the number of + steps between off/on states when fading. Defaults to 25. + """ + self.off() + + off_time = on_time if off_time is None else off_time + fade_out_time = fade_in_time if fade_out_time is None else fade_out_time + + def blink_generator(): + if fade_in_time > 0: + for s in [ + (i * (1 / fps) / fade_in_time, 1 / fps) + for i in range(int(fps * fade_in_time)) + ]: + yield s + + if on_time > 0: + yield (1, on_time) + + if fade_out_time > 0: + for s in [ + (1 - (i * (1 / fps) / fade_out_time), 1 / fps) + for i in range(int(fps * fade_out_time)) + ]: + yield s + + if off_time > 0: + yield (0, off_time) + + # is there anything to change? + if on_time > 0 or off_time > 0 or fade_in_time > 0 or fade_out_time > 0: + self._start_change(blink_generator, n, wait) + + def pulse(self, fade_in_time=1, fade_out_time=None, n=None, wait=False, fps=25): + """ + Makes the device pulse on and off repeatedly. + + :param float fade_in_time: + The length of time in seconds that the device will take to turn on. + Defaults to 1. + + :param float fade_out_time: + The length of time in seconds that the device will take to turn off. + Defaults to 1. + + :param int fps: + The frames per second that will be used to calculate the number of + steps between off/on states. Defaults to 25. + + :param int n: + The number of times to pulse the LED. If None, the LED will pulse + forever. Defaults to None. + + :param bool wait: + If True, the method will block until the LED stops pulsing. If False, + the method will return and the LED will pulse in the background. + Defaults to False. + """ + self.blink(on_time=0, off_time=0, fade_in_time=fade_in_time, fade_out_time=fade_out_time, n=n, wait=wait, fps=fps) + + def close(self): + """ + Closes the device and turns the device off. Once closed, the device + can no longer be used. + """ + super().close() + del PWMOutputDevice._channels_used[ + PWMOutputDevice.PIN_TO_PWM_CHANNEL[self._pin_num] + ] + self._pwm.deinit() + self._pwm = None + +class PWMLED(PWMOutputDevice): + """ + Represents an LED driven by a PWM pin; the brightness of the LED can be changed. + + :param int pin: + The pin that the device is connected to. + + :param int freq: + The frequency of the PWM signal in hertz. Defaults to 100. + + :param int duty_factor: + The duty factor of the PWM signal. This is a value between 0 and 65535. + Defaults to 65535. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the Pin + to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to + LOW (the :meth:`off` method always does the opposite). + + :param bool initial_value: + If :data:`False` (the default), the LED will be off initially. If + :data:`True`, the LED will be switched on initially. + """ +PWMLED.brightness = PWMLED.value + +def LED(pin, pwm=True, active_high=True, initial_value=False): + """ + Returns an instance of :class:`DigitalLED` or :class:`PWMLED` depending on + the value of the `pwm` parameter. + + :: + + from picozero import LED + + my_pwm_led = LED(1) + + my_digital_led = LED(2, pwm=False) + + :param int pin: + The pin that the device is connected to. + + :param int pin: + If `pwm` is :data:`True` (the default), a :class:`PWMLED` will be + returned. If `pwm` is :data:`False`, a :class:`DigitalLED` will be + returned. A :class:`PWMLED` can control the brightness of the LED but + uses 1 PWM channel. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the Pin + to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to + LOW (the :meth:`off` method always does the opposite). + + :param bool initial_value: + If :data:`False` (the default), the device will be off initially. If + :data:`True`, the device will be switched on initially. + """ + if pwm: + return PWMLED( + pin=pin, + active_high=active_high, + initial_value=initial_value) + else: + return DigitalLED( + pin=pin, + active_high=active_high, + initial_value=initial_value) + +try: + pico_led = LED("LED", pwm=False) +except TypeError: + # older version of micropython before "LED" was supported + pico_led = LED(25, pwm=False) + +class PWMBuzzer(PWMOutputDevice): + """ + Represents a passive buzzer driven by a PWM pin; the volume of the buzzer can be changed. + + :param int pin: + The pin that the buzzer is connected to. + + :param int freq: + The frequency of the PWM signal in hertz. Defaults to 440. + + :param int duty_factor: + The duty factor of the PWM signal. This is a value between 0 and 65535. + Defaults to 1023. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the Pin + to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to + LOW (the :meth:`off` method always does the opposite). + + :param bool initial_value: + If :data:`False` (the default), the buzzer will be off initially. If + :data:`True`, the buzzer will be switched on initially. + """ + def __init__(self, pin, freq=440, duty_factor=1023, active_high=True, initial_value=False): + super().__init__(pin, freq, duty_factor, active_high, initial_value) + +PWMBuzzer.volume = PWMBuzzer.value +PWMBuzzer.beep = PWMBuzzer.blink + +class Speaker(OutputDevice, PinMixin): + """ + Represents a speaker driven by a PWM pin. + + :param int pin: + The pin that the speaker is connected to. + + :param int initial_freq: + The initial frequency of the PWM signal in hertz. Defaults to 440. + + :param int initial_volume: + The initial volume of the PWM signal. This is a value between 0 and + 1. Defaults to 0. + + :param int duty_factor: + The duty factor of the PWM signal. This is a value between 0 and 65535. + Defaults to 1023. + + :param bool active_high: + If :data:`True` (the default), the :meth:`on` method will set the Pin + to HIGH. If :data:`False`, the :meth:`on` method will set the Pin to + LOW (the :meth:`off` method always does the opposite). + """ + NOTES = { + 'b0': 31, 'c1': 33, 'c#1': 35, 'd1': 37, 'd#1': 39, 'e1': 41, 'f1': 44, 'f#1': 46, 'g1': 49,'g#1': 52, 'a1': 55, + 'a#1': 58, 'b1': 62, 'c2': 65, 'c#2': 69, 'd2': 73, 'd#2': 78, + 'e2': 82, 'f2': 87, 'f#2': 93, 'g2': 98, 'g#2': 104, 'a2': 110, 'a#2': 117, 'b2': 123, + 'c3': 131, 'c#3': 139, 'd3': 147, 'd#3': 156, 'e3': 165, 'f3': 175, 'f#3': 185, 'g3': 196, 'g#3': 208, 'a3': 220, 'a#3': 233, 'b3': 247, + 'c4': 262, 'c#4': 277, 'd4': 294, 'd#4': 311, 'e4': 330, 'f4': 349, 'f#4': 370, 'g4': 392, 'g#4': 415, 'a4': 440, 'a#4': 466, 'b4': 494, + 'c5': 523, 'c#5': 554, 'd5': 587, 'd#5': 622, 'e5': 659, 'f5': 698, 'f#5': 740, 'g5': 784, 'g#5': 831, 'a5': 880, 'a#5': 932, 'b5': 988, + 'c6': 1047, 'c#6': 1109, 'd6': 1175, 'd#6': 1245, 'e6': 1319, 'f6': 1397, 'f#6': 1480, 'g6': 1568, 'g#6': 1661, 'a6': 1760, 'a#6': 1865, 'b6': 1976, + 'c7': 2093, 'c#7': 2217, 'd7': 2349, 'd#7': 2489, + 'e7': 2637, 'f7': 2794, 'f#7': 2960, 'g7': 3136, 'g#7': 3322, 'a7': 3520, 'a#7': 3729, 'b7': 3951, + 'c8': 4186, 'c#8': 4435, 'd8': 4699, 'd#8': 4978 + } + + def __init__(self, pin, initial_freq=440, initial_volume=0, duty_factor=1023, active_high=True): + + self._pin_num = pin + self._pwm_buzzer = PWMBuzzer( + pin, + freq=initial_freq, + duty_factor=duty_factor, + active_high=active_high, + initial_value=None, + ) + + super().__init__(active_high, None) + self.volume = initial_volume + + def on(self, volume=1): + self.volume = volume + + def off(self): + self.volume = 0 + + @property + def value(self): + """ + Sets or returns the value of the speaker. The value is a tuple of (freq, volume). + """ + return tuple(self.freq, self.volume) + + @value.setter + def value(self, value): + self._stop_change() + self._write(value) + + @property + def volume(self): + """ + Sets or returns the volume of the speaker: 1 for maximum volume, 0 for off. + """ + return self._volume + + @volume.setter + def volume(self, value): + self._volume = value + self.value = (self.freq, self.volume) + + @property + def freq(self): + """ + Sets or returns the current frequency of the speaker. + """ + return self._pwm_buzzer.freq + + @freq.setter + def freq(self, freq): + self.value = (freq, self.volume) + + def _write(self, value): + # set the frequency + if value[0] is not None: + self._pwm_buzzer.freq = value[0] + + # write the volume value + if value[1] is not None: + self._pwm_buzzer.volume = value[1] + + def _to_freq(self, freq): + if freq is not None and freq != '' and freq != 0: + if type(freq) is str: + return int(self.NOTES[freq]) + elif freq <= 128 and freq > 0: # MIDI + midi_factor = 2**(1/12) + return int(440 * midi_factor ** (freq - 69)) + else: + return freq + else: + return None + + def beep(self, on_time=1, off_time=None, n=None, wait=False, fade_in_time=0, fade_out_time=None, fps=25): + """ + Makes the buzzer turn on and off repeatedly. + + :param float on_time: + The length of time in seconds that the device will be on. Defaults to 1. + + :param float off_time: + The length of time in seconds that the device will be off. If `None`, + it will be the same as ``on_time``. Defaults to `None`. + + :param int n: + The number of times to repeat the beep operation. If `None`, the + device will continue beeping forever. The default is `None`. + + :param bool wait: + If True, the method will block until the buzzer stops beeping. If False, + the method will return and the buzzer will beep in the background. + Defaults to False. + + :param float fade_in_time: + The length of time in seconds to spend fading in. Defaults to 0. + + :param float fade_out_time: + The length of time in seconds to spend fading out. If `None`, + it will be the same as ``fade_in_time``. Defaults to `None`. + + :param int fps: + The frames per second that will be used to calculate the number of + steps between off/on states when fading. Defaults to 25. + """ + self._pwm_buzzer.blink(on_time, off_time, n, wait, fade_in_time, fade_out_time, fps) + + def play(self, tune=440, duration=1, volume=1, n=1, wait=True): + """ + Plays a tune for a given duration. + + :param int tune: + + The tune to play can be specified as: + + + a single "note", represented as: + + a frequency in Hz e.g. `440` + + a midi note e.g. `60` + + a note name as a string e.g. `"E4"` + + a list of notes and duration e.g. `[440, 1]` or `["E4", 2]` + + a list of two value tuples of (note, duration) e.g. `[(440,1), (60, 2), ("e4", 3)]` + + Defaults to `440`. + + :param int volume: + The volume of the tune; 1 is maximum volume, 0 is mute. Defaults to 1. + + :param float duration: + The duration of each note in seconds. Defaults to 1. + + :param int n: + The number of times to play the tune. If None, the tune will play + forever. Defaults to 1. + + :param bool wait: + If True, the method will block until the tune has finished. If False, + the method will return and the tune will play in the background. + Defaults to True. + """ + + self.off() + + # tune isn't a list, so it must be a single frequency or note + if not isinstance(tune, (list, tuple)): + tune = [(tune, duration)] + # if the first element isn't a list, then it must be list of a single note and duration + elif not isinstance(tune[0], (list, tuple)): + tune = [tune] + + def tune_generator(): + for note in tune: + + # note isn't a list or tuple, it must be a single frequency or note + if not isinstance(note, (list, tuple)): + # make it into a tuple + note = (note, duration) + + # turn the notes into frequencies + freq = self._to_freq(note[0]) + freq_duration = note[1] + freq_volume = volume if freq is not None else 0 + + # if this is a tune of greater than 1 note, add gaps between notes + if len(tune) == 1: + yield ((freq, freq_volume), freq_duration) + else: + yield ((freq, freq_volume), freq_duration * 0.9) + yield ((freq, 0), freq_duration * 0.1) + + self._start_change(tune_generator, n, wait) + + def close(self): + self._pwm_buzzer.close() + +class RGBLED(OutputDevice, PinsMixin): + """ + Extends :class:`OutputDevice` and represents a full colour LED component (composed + of red, green, and blue LEDs). + Connect the common cathode (longest leg) to a ground pin; connect each of + the other legs (representing the red, green, and blue anodes) to any GP + pins. You should use three limiting resistors (one per anode). + The following code will make the LED yellow:: + + from picozero import RGBLED + rgb = RGBLED(1, 2, 3) + rgb.color = (1, 1, 0) + + 0–255 colours are also supported:: + + rgb.color = (255, 255, 0) + + :type red: int + :param red: + The GP pin that controls the red component of the RGB LED. + :type green: int + :param green: + The GP pin that controls the green component of the RGB LED. + :type blue: int + :param blue: + The GP pin that controls the blue component of the RGB LED. + :param bool active_high: + Set to :data:`True` (the default) for common cathode RGB LEDs. If you + are using a common anode RGB LED, set this to :data:`False`. + :type initial_value: ~colorzero.Color or tuple + :param initial_value: + The initial color for the RGB LED. Defaults to black ``(0, 0, 0)``. + :param bool pwm: + If :data:`True` (the default), construct :class:`PWMLED` instances for + each component of the RGBLED. If :data:`False`, construct + :class:`DigitalLED` instances. + + """ + def __init__(self, red=None, green=None, blue=None, active_high=True, + initial_value=(0, 0, 0), pwm=True): + self._pin_nums = (red, green, blue) + self._leds = () + self._last = initial_value + LEDClass = PWMLED if pwm else DigitalLED + self._leds = tuple( + LEDClass(pin, active_high=active_high) + for pin in (red, green, blue)) + super().__init__(active_high, initial_value) + + def _write(self, value): + if type(value) is not tuple: + value = (value, ) * 3 + for led, v in zip(self._leds, value): + led.value = v + + @property + def value(self): + """ + Represents the colour of the LED as an RGB 3-tuple of ``(red, green, + blue)`` where each value is between 0 and 1 if *pwm* was :data:`True` + when the class was constructed (but only takes values of 0 or 1 otherwise). + For example, red would be ``(1, 0, 0)`` and yellow would be ``(1, 1, + 0)``, whereas orange would be ``(1, 0.5, 0)``. + """ + return tuple(led.value for led in self._leds) + + @value.setter + def value(self, value): + self._stop_change() + self._write(value) + + @property + def is_active(self): + """ + Returns :data:`True` if the LED is currently active (not black) and + :data:`False` otherwise. + """ + return self.value != (0, 0, 0) + + is_lit = is_active + + def _to_255(self, value): + return round(value * 255) + + def _from_255(self, value): + return 0 if value == 0 else value / 255 + + @property + def color(self): + """ + Represents the colour of the LED as an RGB 3-tuple of ``(red, green, + blue)`` where each value is between 0 and 255 if *pwm* was :data:`True` + when the class was constructed (but only takes values of 0 or 255 otherwise). + For example, red would be ``(255, 0, 0)`` and yellow would be ``(255, 255, + 0)``, whereas orange would be ``(255, 127, 0)``. + """ + return tuple(self._to_255(v) for v in self.value) + + @color.setter + def color(self, value): + self.value = tuple(self._from_255(v) for v in value) + + @property + def red(self): + """ + Represents the red component of the LED as a value between 0 and 255 if *pwm* was :data:`True` + when the class was constructed (but only takes values of 0 or 255 otherwise). + """ + return self._to_255(self.value[0]) + + @red.setter + def red(self, value): + r, g, b = self.value + self.value = self._from_255(value), g, b + + @property + def green(self): + """ + Represents the green component of the LED as a value between 0 and 255 if *pwm* was :data:`True` + when the class was constructed (but only takes values of 0 or 255 otherwise). + """ + return self._to_255(self.value[1]) + + @green.setter + def green(self, value): + r, g, b = self.value + self.value = r, self._from_255(value), b + + @property + def blue(self): + """ + Represents the blue component of the LED as a value between 0 and 255 if *pwm* was :data:`True` + when the class was constructed (but only takes values of 0 or 255 otherwise). + """ + return self._to_255(self.value[2]) + + @blue.setter + def blue(self, value): + r, g, b = self.value + self.value = r, g, self._from_255(value) + + def on(self): + """ + Turns the LED on. This is equivalent to setting the LED color to white, e.g. + ``(1, 1, 1)``. + """ + self.value = (1, 1, 1) + + def invert(self): + """ + Inverts the state of the device. If the device is currently off + (:attr:`value` is ``(0, 0, 0)``), this changes it to "fully" on + (:attr:`value` is ``(1, 1, 1)``). If the device has a specific colour, + this method inverts the colour. + """ + r, g, b = self.value + self.value = (1 - r, 1 - g, 1 - b) + + def toggle(self): + """ + Toggles the state of the device. If the device has a specific colour, then that colour is saved and the device is turned off. + If the device is off, it will be changed to the last colour it had when it was on or, if none, to fully on (:attr:`value` is ``(1, 1, 1)``). + """ + if self.value == (0, 0, 0): + self.value = self._last or (1, 1, 1) + else: + self._last = self.value + self.value = (0, 0, 0) + + def blink(self, on_times=1, fade_times=0, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, wait=False, fps=25): + """ + Makes the device blink between colours repeatedly. + + :param float on_times: + Single value or tuple of numbers of seconds to stay on each colour. Defaults to 1 second. + :param float fade_times: + Single value or tuple of times to fade between each colour. Must be 0 if + *pwm* was :data:`False` when the class was constructed. + :type colors: tuple + Tuple of colours to blink between, use ``(0, 0, 0)`` for off. + :param colors: + The colours to blink between. Defaults to red, green, blue. + :type n: int or None + :param n: + Number of times to blink; :data:`None` (the default) means forever. + :param bool wait: + If :data:`False` (the default), use a Timer to manage blinking, + continue blinking, and return immediately. If :data:`False`, only + return when the blinking is finished (warning: the default value of + *n* will result in this method never returning). + """ + self.off() + + if type(on_times) is not tuple: + on_times = (on_times, ) * len(colors) + if type(fade_times) is not tuple: + fade_times = (fade_times, ) * len(colors) + # If any value is above zero then treat all as 0-255 values + if any(v > 1 for v in sum(colors, ())): + colors = tuple(tuple(self._from_255(v) for v in t) for t in colors) + + def blink_generator(): + + # Define a linear interpolation between + # off_color and on_color + + lerp = lambda t, fade_in, color1, color2: tuple( + (1 - t) * off + t * on + if fade_in else + (1 - t) * on + t * off + for off, on in zip(color2, color1) + ) + + for c in range(len(colors)): + if on_times[c] > 0: + yield (colors[c], on_times[c]) + + if fade_times[c] > 0: + for i in range(int(fps * fade_times[c])): + v = lerp(i * (1 / fps) / fade_times[c], True, colors[(c + 1) % len(colors)], colors[c]) + t = 1 / fps + yield (v, t) + + self._start_change(blink_generator, n, wait) + + def pulse(self, fade_times=1, colors=((0, 0, 0), (1, 0, 0), (0, 0, 0), (0, 1, 0), (0, 0, 0), (0, 0, 1)), n=None, wait=False, fps=25): + """ + Makes the device fade between colours repeatedly. + + :param float fade_times: + Single value or tuple of numbers of seconds to spend fading. Defaults to 1. + :param float fade_out_time: + Number of seconds to spend fading out. Defaults to 1. + :type colors: tuple + :param on_color: + Tuple of colours to pulse between in order. Defaults to red, off, green, off, blue, off. + :type off_color: ~colorzero.Color or tuple + :type n: int or None + :param n: + Number of times to pulse; :data:`None` (the default) means forever. + """ + on_times = 0 + self.blink(on_times, fade_times, colors, n, wait, fps) + + def cycle(self, fade_times=1, colors=((1, 0, 0), (0, 1, 0), (0, 0, 1)), n=None, wait=False, fps=25): + """ + Makes the device fade in and out repeatedly. + + :param float fade_times: + Single value or tuple of numbers of seconds to spend fading between colours. Defaults to 1. + :param float fade_times: + Number of seconds to spend fading out. Defaults to 1. + :type colors: tuple + :param on_color: + Tuple of colours to cycle between. Defaults to red, green, blue. + :type n: int or None + :param n: + Number of times to cycle; :data:`None` (the default) means forever. + """ + on_times = 0 + self.blink(on_times, fade_times, colors, n, wait, fps) + + def close(self): + super().close() + for led in self._leds: + led.close() + self._leds = None + +RGBLED.colour = RGBLED.color + +class Motor(PinsMixin): + """ + Represents a motor connected to a motor controller that has a two-pin + input. One pin drives the motor "forward", the other drives the motor + "backward". + + :type forward: int + :param forward: + The GP pin that controls the "forward" motion of the motor. + + :type backward: int + :param backward: + The GP pin that controls the "backward" motion of the motor. + + :param bool pwm: + If :data:`True` (the default), PWM pins are used to drive the motor. + When using PWM pins, values between 0 and 1 can be used to set the + speed. + + """ + def __init__(self, forward, backward, pwm=True): + self._pin_nums = (forward, backward) + self._forward = PWMOutputDevice(forward) if pwm else DigitalOutputDevice(forward) + self._backward = PWMOutputDevice(backward) if pwm else DigitalOutputDevice(backward) + + def on(self, speed=1, t=None, wait=False): + """ + Turns the motor on and makes it turn. + + :param float speed: + The speed as a value between -1 and 1: 1 turns the motor at + full speed in one direction, -1 turns the motor at full speed in + the opposite direction. Defaults to 1. + + :param float t: + The time in seconds that the motor should run for. If None is + specified, the motor will stay on. The default is None. + + :param bool wait: + If True, the method will block until the time `t` has expired. + If False, the method will return and the motor will turn on in + the background. Defaults to False. Only effective if `t` is not + None. + """ + if speed > 0: + self._backward.off() + self._forward.on(speed, t, wait) + + elif speed < 0: + self._forward.off() + self._backward.on(-speed, t, wait) + + else: + self.off() + + def off(self): + """ + Stops the motor turning. + """ + self._backward.off() + self._forward.off() + + @property + def value(self): + """ + Sets or returns the motor speed as a value between -1 and 1: -1 is full + speed "backward", 1 is full speed "forward", 0 is stopped. + """ + return self._forward.value + (-self._backward.value) + + @value.setter + def value(self, value): + if value != 0: + self.on(value) + else: + self.stop() + + def forward(self, speed=1, t=None, wait=False): + """ + Makes the motor turn "forward". + + :param float speed: + The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. + + :param float t: + The time in seconds that the motor should turn for. If None is + specified, the motor will stay on. The default is None. + + :param bool wait: + If True, the method will block until the time `t` has expired. + If False, the method will return and the motor will turn on in + the background. Defaults to False. Only effective if `t` is not + None. + """ + self.on(speed, t, wait) + + def backward(self, speed=1, t=None, wait=False): + """ + Makes the motor turn "backward". + + :param float speed: + The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. + + :param float t: + The time in seconds that the motor should turn for. If None is + specified, the motor will stay on. The default is None. + + :param bool wait: + If True, the method will block until the time `t` has expired. + If False, the method will return and the motor will turn on in + the background. Defaults to False. Only effective if `t` is not + None. + """ + self.on(-speed, t, wait) + + def close(self): + """ + Closes the device and releases any resources. Once closed, the device + can no longer be used. + """ + self._forward.close() + self._backward.close() + +Motor.start = Motor.on +Motor.stop = Motor.off + +class Robot: + """ + Represents a generic dual-motor robot / rover / buggy. + + Alias for :class:`Rover`. + + This class is constructed with two tuples representing the forward and + backward pins of the left and right controllers. For example, + if the left motor's controller is connected to pins 12 and 13, while the + right motor's controller is connected to pins 14 and 15, then the following + example will drive the robot forward:: + + from picozero import Robot + + robot = Robot(left=(12, 13), right=(14, 15)) + robot.forward() + + :param tuple left: + A tuple of two pins representing the forward and backward inputs of the + left motor's controller. + + :param tuple right: + A tuple of two pins representing the forward and backward inputs of the + right motor's controller. + + :param bool pwm: + If :data:`True` (the default), pwm pins will be used, allowing variable + speed control. + + """ + def __init__(self, left, right, pwm=True): + self._left = Motor(left[0], left[1], pwm) + self._right = Motor(right[0], right[1], pwm) + + @property + def left_motor(self): + """ + Returns the left :class:`Motor`. + """ + return self._left + + @property + def right_motor(self): + """ + Returns the right :class:`Motor`. + """ + return self._right + + @property + def value(self): + """ + Represents the motion of the robot as a tuple of (left_motor_speed, + right_motor_speed) with ``(-1, -1)`` representing full speed backwards, + ``(1, 1)`` representing full speed forwards, and ``(0, 0)`` + representing stopped. + """ + return (self._left.value, self._right.value) + + @value.setter + def value(self, value): + self._left.value, self._right.value = value + + def forward(self, speed=1, t=None, wait=False): + """ + Makes the robot move "forward". + + :param float speed: + The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. + + :param float t: + The time in seconds that the robot should move for. If None is + specified, the robot will continue to move until stopped. The default + is None. + + :param bool wait: + If True, the method will block until the time `t` has expired. + If False, the method will return and the motor will turn on in + the background. Defaults to False. Only effective if `t` is not + None. + """ + self._left.forward(speed, t, False) + self._right.forward(speed, t, wait) + + def backward(self, speed=1, t=None, wait=False): + """ + Makes the robot move "backward". + + :param float speed: + The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. + + :param float t: + The time in seconds that the robot should move for. If None is + specified, the robot will continue to move until stopped. The default + is None. + + :param bool wait: + If True, the method will block until the time `t` has expired. + If False, the method will return and the motor will turn on in + the background. Defaults to False. Only effective if `t` is not + None. + """ + self._left.backward(speed, t, False) + self._right.backward(speed, t, wait) + + def left(self, speed=1, t=None, wait=False): + """ + Makes the robot turn "left" by turning the left motor backward and the + right motor forward. + + :param float speed: + The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. + + :param float t: + The time in seconds that the robot should turn for. If None is + specified, the robot will continue to turn until stopped. The default + is None. + + :param bool wait: + If True, the method will block until the time `t` has expired. + If False, the method will return and the motor will turn on in + the background. Defaults to False. Only effective if `t` is not + None. + """ + self._left.backward(speed, t, False) + self._right.forward(speed, t, wait) + + def right(self, speed=1, t=None, wait=False): + """ + Makes the robot turn "right" by turning the left motor forward and the + right motor backward. + + :param float speed: + The speed as a value between 0 and 1: 1 is full speed, 0 is stop. Defaults to 1. + + :param float t: + The time in seconds that the robot should turn for. If None is + specified, the robot will continue to turn until stopped. The default + is None. + + :param bool wait: + If True, the method will block until the time `t` has expired. + If False, the method will return and the motor will turn on in + the background. Defaults to False. Only effective if `t` is not + None. + """ + self._left.forward(speed, t, False) + self._right.backward(speed, t, wait) + + def stop(self): + """ + Stops the robot. + """ + self._left.stop() + self._right.stop() + + def close(self): + """ + Closes the device and releases any resources. Once closed, the device + can no longer be used. + """ + self._left.close() + self._right.close() + +Rover = Robot + +class Servo(PWMOutputDevice): + """ + Represents a PWM-controlled servo motor. + + Setting the `value` to 0 will move the servo to its minimum position, + 1 will move the servo to its maximum position. Setting the `value` to + :data:`None` will turn the servo "off" (i.e. no signal is sent). + + :type pin: int + :param pin: + The pin the servo motor is connected to. + + :param bool initial_value: + If :data:`0`, the servo will be set to its minimum position. If + :data:`1`, the servo will set to its maximum position. If :data:`None` + (the default), the position of the servo will not change. + + :param float min_pulse_width: + The pulse width corresponding to the servo's minimum position. This + defaults to 1ms. + + :param float max_pulse_width: + The pulse width corresponding to the servo's maximum position. This + defaults to 2ms. + + :param float frame_width: + The length of time between servo control pulses measured in seconds. + This defaults to 20ms which is a common value for servos. + + :param int duty_factor: + The duty factor of the PWM signal. This is a value between 0 and 65535. + Defaults to 65535. + """ + def __init__(self, pin, initial_value=None, min_pulse_width=1/1000, max_pulse_width=2/1000, frame_width=20/1000, duty_factor=65535): + self._min_duty = int((min_pulse_width / frame_width) * duty_factor) + self._max_duty = int((max_pulse_width / frame_width) * duty_factor) + + super().__init__(pin, freq=int(1 / frame_width), duty_factor=duty_factor, initial_value=initial_value) + + def _state_to_value(self, state): + return None if state == 0 else clamp((state - self._min_duty) / (self._max_duty - self._min_duty), 0, 1) + + def _value_to_state(self, value): + return 0 if value is None else int(self._min_duty + ((self._max_duty - self._min_duty) * value)) + + def min(self): + """ + Set the servo to its minimum position. + """ + self.value = 0 + + def mid(self): + """ + Set the servo to its mid-point position. + """ + self.value = 0.5 + + def max(self): + """ + Set the servo to its maximum position. + """ + self.value = 1 + + def off(self): + """ + Turn the servo "off" by setting the value to `None`. + """ + self.value = None + +############################################################################### +# INPUT DEVICES +############################################################################### + +class InputDevice: + """ + Base class for input devices. + """ + def __init__(self, active_state=None): + self._active_state = active_state + + @property + def active_state(self): + """ + Sets or returns the active state of the device. If :data:`None` (the default), + the device will return the value that the pin is set to. If + :data:`True`, the device will return :data:`True` if the pin is + HIGH. If :data:`False`, the device will return :data:`False` if the + pin is LOW. + """ + return self._active_state + + @active_state.setter + def active_state(self, value): + self._active_state = True if value else False + self._inactive_state = False if value else True + + @property + def value(self): + """ + Returns the current value of the device. This is either :data:`True` + or :data:`False` depending on the value of :attr:`active_state`. + """ + return self._read() + +class DigitalInputDevice(InputDevice, PinMixin): + """ + Represents a generic input device with digital functionality e.g. buttons + that can be either active or inactive. + + :param int pin: + The pin that the device is connected to. + + :param bool pull_up: + If :data:`True`, the device will be pulled up to HIGH. If + :data:`False` (the default), the device will be pulled down to LOW. + + :param bool active_state: + If :data:`True` (the default), the device will return :data:`True` + if the pin is HIGH. If :data:`False`, the device will return + :data:`False` if the pin is LOW. + + :param float bounce_time: + The bounce time for the device. If set, the device will ignore + any button presses that happen within the bounce time after a + button release. This is useful to prevent accidental button + presses from registering as multiple presses. The default is + :data:`None`. + """ + def __init__(self, pin, pull_up=False, active_state=None, bounce_time=None): + super().__init__(active_state) + self._pin_num = pin + self._pin = Pin( + pin, + mode=Pin.IN, + pull=Pin.PULL_UP if pull_up else Pin.PULL_DOWN) + self._bounce_time = bounce_time + + if active_state is None: + self._active_state = False if pull_up else True + else: + self._active_state = active_state + + self._state = self._pin.value() + + self._when_activated = None + self._when_deactivated = None + + # setup interupt + self._pin.irq(self._pin_change, Pin.IRQ_RISING | Pin.IRQ_FALLING) + + def _state_to_value(self, state): + return int(bool(state) == self._active_state) + + def _read(self): + return self._state_to_value(self._state) + + def _pin_change(self, p): + # turn off the interupt + p.irq(handler=None) + + last_state = p.value() + + if self._bounce_time is not None: + # wait for stability + stop = ticks_ms() + (self._bounce_time * 1000) + while ticks_ms() < stop: + # keep checking, reset the stop if the value changes + if p.value() != last_state: + stop = ticks_ms() + self._bounce_time + last_state = p.value() + + # re-enable the interupt + p.irq(self._pin_change, Pin.IRQ_RISING | Pin.IRQ_FALLING) + + # did the value actually change? + if self._state != last_state: + # set the state + self._state = self._pin.value() + + # manage call backs + callback_to_run = None + if self.value and self._when_activated is not None: + callback_to_run = self._when_activated + + elif not self.value and self._when_deactivated is not None: + callback_to_run = self._when_deactivated + + if callback_to_run is not None: + + def schedule_callback(callback): + callback() + + try: + schedule(schedule_callback, callback_to_run) + + except RuntimeError as e: + if str(e) == "schedule queue full": + raise EventFailedScheduleQueueFull( + "{} - {} not run due to the micropython schedule being full".format( + str(self), callback_to_run.__name__)) + else: + raise e + + @property + def is_active(self): + """ + Returns :data:`True` if the device is active. + """ + return bool(self.value) + + @property + def is_inactive(self): + """ + Returns :data:`True` if the device is inactive. + """ + return not bool(self.value) + + @property + def when_activated(self): + """ + Returns a :samp:`callback` that will be called when the device is activated. + """ + return self._when_activated + + @when_activated.setter + def when_activated(self, value): + self._when_activated = value + + @property + def when_deactivated(self): + """ + Returns a :samp:`callback` that will be called when the device is deactivated. + """ + return self._when_deactivated + + @when_deactivated.setter + def when_deactivated(self, value): + self._when_deactivated = value + + def close(self): + """ + Closes the device and releases any resources. Once closed, the device + can no longer be used. + """ + self._pin.irq(handler=None) + self._pin = None + +class Switch(DigitalInputDevice): + """ + Represents a toggle switch, which is either open or closed. + + :param int pin: + The pin that the device is connected to. + + :param bool pull_up: + If :data:`True` (the default), the device will be pulled up to + HIGH. If :data:`False`, the device will be pulled down to LOW. + + :param float bounce_time: + The bounce time for the device. If set, the device will ignore + any button presses that happen within the bounce time after a + button release. This is useful to prevent accidental button + presses from registering as multiple presses. Defaults to 0.02 + seconds. + """ + def __init__(self, pin, pull_up=True, bounce_time=0.02): + super().__init__(pin=pin, pull_up=pull_up, bounce_time=bounce_time) + +Switch.is_closed = Switch.is_active +Switch.is_open = Switch.is_inactive +Switch.when_closed = Switch.when_activated +Switch.when_opened = Switch.when_deactivated + +class Button(Switch): + """ + Represents a push button, which can be either pressed or released. + + :param int pin: + The pin that the device is connected to. + + :param bool pull_up: + If :data:`True` (the default), the device will be pulled up to + HIGH. If :data:`False`, the device will be pulled down to LOW. + + :param float bounce_time: + The bounce time for the device. If set, the device will ignore + any button presses that happen within the bounce time after a + button release. This is useful to prevent accidental button + presses from registering as multiple presses. Defaults to 0.02 + seconds. + """ + pass + +Button.is_pressed = Button.is_active +Button.is_released = Button.is_inactive +Button.when_pressed = Button.when_activated +Button.when_released = Button.when_deactivated + +class AnalogInputDevice(InputDevice, PinMixin): + """ + Represents a generic input device with analogue functionality, e.g. + a potentiometer. + + :param int pin: + The pin that the device is connected to. + + :param active_state: + The active state of the device. If :data:`True` (the default), + the :class:`AnalogInputDevice` will assume that the device is + active when the pin is high and above the threshold. If + ``active_state`` is ``False``, the device will be active when + the pin is low and below the threshold. + + :param float threshold: + The threshold that the device must be above or below to be + considered active. The default is 0.5. + + """ + def __init__(self, pin, active_state=True, threshold=0.5): + self._pin_num = pin + super().__init__(active_state) + self._adc = ADC(pin) + self._threshold = float(threshold) + + def _state_to_value(self, state): + return (state if self.active_state else 65535 - state) / 65535 + + def _value_to_state(self, value): + return int(65535 * (value if self.active_state else 1 - value)) + + def _read(self): + return self._state_to_value(self._adc.read_u16()) + + @property + def threshold(self): + """ + The threshold that the device must be above or below to be + considered active. The default is 0.5. + """ + return self._threshold + + @threshold.setter + def threshold(self, value): + self._threshold = float(value) + + @property + def is_active(self): + """ + Returns :data:`True` if the device is active. + """ + return self.value > self.threshold + + @property + def voltage(self): + """ + Returns the voltage of the analogue device. + """ + return self.value * 3.3 + + def close(self): + self._adc = None + +class Potentiometer(AnalogInputDevice): + """ + Represents a potentiometer, which outputs a variable voltage + between 0 and 3.3V. + + Alias for :class:`Pot`. + + :param int pin: + The pin that the device is connected to. + + :param active_state: + The active state of the device. If :data:`True` (the default), + the :class:`AnalogInputDevice` will assume that the device is + active when the pin is high and above the threshold. If + ``active_state`` is ``False``, the device will be active when + the pin is low and below the threshold. + + :param float threshold: + The threshold that the device must be above or below to be + considered active. The default is 0.5. + + """ + pass + +Pot = Potentiometer + +def pico_temp_conversion(voltage): + # Formula for calculating temp from voltage for the onboard temperature sensor + return 27 - (voltage - 0.706)/0.001721 + +class TemperatureSensor(AnalogInputDevice): + """ + Represents a TemperatureSensor, which outputs a variable voltage. The voltage + can be converted to a temperature using a `conversion` function passed as a + parameter. + + Alias for :class:`Thermistor` and :class:`TempSensor`. + + :param int pin: + The pin that the device is connected to. + + :param active_state: + The active state of the device. If :data:`True` (the default), + the :class:`AnalogInputDevice` will assume that the device is + active when the pin is high and above the threshold. If + ``active_state`` is ``False``, the device will be active when + the pin is low and below the threshold. + + :param float threshold: + The threshold that the device must be above or below to be + considered active. The default is 0.5. + + :param float conversion: + A function that takes a voltage and returns a temperature. + + e.g. The internal temperature sensor has a voltage range of 0.706V to 0.716V + and would use the follow conversion function:: + + def temp_conversion(voltage): + return 27 - (voltage - 0.706)/0.001721 + + temp_sensor = TemperatureSensor(pin, conversion=temp_conversion) + + If :data:`None` (the default), the ``temp`` property will return :data:`None`. + + """ + def __init__(self, pin, active_state=True, threshold=0.5, conversion=None): + self._conversion = conversion + super().__init__(pin, active_state, threshold) + + @property + def temp(self): + """ + Returns the temperature of the device. If the conversion function is not + set, this will return :data:`None`. + """ + if self._conversion is not None: + return self._conversion(self.voltage) + else: + return None + + @property + def conversion(self): + """ + Sets or returns the conversion function for the device. + """ + return self._conversion + + @conversion.setter + def conversion(self, value): + self._conversion = value + +pico_temp_sensor = TemperatureSensor(4, True, 0.5, pico_temp_conversion) +TempSensor = TemperatureSensor +Thermistor = TemperatureSensor + +class DistanceSensor(PinsMixin): + """ + Represents a HC-SR04 ultrasonic distance sensor. + + :param int echo: + The pin that the ECHO pin is connected to. + + :param int trigger: + The pin that the TRIG pin is connected to. + + :param float max_distance: + The :attr:`value` attribute reports a normalized value between 0 (too + close to measure) and 1 (maximum distance). This parameter specifies + the maximum distance expected in meters. This defaults to 1. + """ + def __init__(self, echo, trigger, max_distance=1): + self._pin_nums = (echo, trigger) + self._max_distance = max_distance + self._echo = Pin(echo, mode=Pin.IN, pull=Pin.PULL_DOWN) + self._trigger = Pin(trigger, mode=Pin.OUT, value=0) + + def _read(self): + echo_on = None + echo_off = None + timed_out = False + + self._trigger.off() + sleep(0.000005) + self._trigger.on() + sleep(0.00001) + self._trigger.off() + + # If an echo isn't measured in 100 milliseconds, it should + # be considered out of range. The maximum length of the + # echo is 38 milliseconds but it's not known how long the + # transmission takes after the trigger + stop = ticks_ms() + 100 + while echo_off is None and not timed_out: + if self._echo.value() == 1 and echo_on is None: + echo_on = ticks_us() + if echo_on is not None and self._echo.value() == 0: + echo_off = ticks_us() + if ticks_ms() > stop: + timed_out = True + + if echo_off is None or timed_out: + return None + else: + distance = ((echo_off - echo_on) * 0.000343) / 2 + distance = min(distance, self._max_distance) + return distance + + @property + def value(self): + """ + Returns a value between 0, indicating the reflector is either touching + the sensor or is sufficiently near that the sensor can’t tell the + difference, and 1, indicating the reflector is at or beyond the + specified max_distance. A return value of None indicates that the + echo was not received before the timeout. + """ + distance = self.distance + return distance / self._max_distance if distance is not None else None + + @property + def distance(self): + """ + Returns the current distance measured by the sensor in meters. Note + that this property will have a value between 0 and max_distance. + """ + return self._read() + + @property + def max_distance(self): + """ + Returns the maximum distance that the sensor will measure in metres. + """ + return self._max_distance + diff --git a/mu/resources/pygamezero/5_count_boy.py b/mu/resources/pygamezero/5_count_boy.py index 6b4a94094..272a890cb 100644 --- a/mu/resources/pygamezero/5_count_boy.py +++ b/mu/resources/pygamezero/5_count_boy.py @@ -12,6 +12,7 @@ def draw(): screen.fill('white') boy.draw() + game.time.sleep(0.5) screen.draw.text('Times: ' + str(times), (20, 20), color='black') def update(): diff --git a/mu/resources/pygamezero/6_treasure_box.py b/mu/resources/pygamezero/6_treasure_box.py index 8a043246c..da6187a15 100644 --- a/mu/resources/pygamezero/6_treasure_box.py +++ b/mu/resources/pygamezero/6_treasure_box.py @@ -1,37 +1,49 @@ +import pygame from pgzhelper import * WIDTH = 960 HEIGHT = 540 treasure = Actor("treasure_box", (WIDTH / 2, HEIGHT / 2)) +treasure.images = ["treasure_box", "treasure_box_o"] input_text = "" input_done = False -input_rect = Rect(350, 400, 200, 50) +input_rect = Rect(350, 450, 200, 50) -guide_rect = Rect(300, 100, 400, 50) +guide_rect = Rect(300, 50, 400, 50) password = "1234" def draw(): screen.blit("desert", (0, 0)) treasure.draw() - screen.draw.textbox("Please input a password.", guide_rect) - screen.draw.filled_rect(input_rect, "pink") - screen.draw.textbox(input_text, input_rect) + # Guide textbox + screen.draw.filled_rect(guide_rect, 'black') if input_done: - screen.blit("desert", (0, 0)) - treasure.draw() - - if input_text == password: + if input_text == password: screen.draw.textbox("You got it!", guide_rect) - sounds.cheer.play() else: screen.draw.textbox("You failed!", guide_rect) - sounds.warning.play() pygame.display.update() game.exit() + else: + screen.draw.textbox("Please input a password.", guide_rect) + + # Input textbox + screen.draw.filled_rect(input_rect, "pink") + screen.draw.textbox(input_text, input_rect) + + +def update(): + if input_done: + if input_text == password: + treasure.sel_image("treasure_box_o") + sounds.cheer.play() + else: + sounds.warning.play() + def on_key_down(key, unicode): global input_text, input_done diff --git a/mu/resources/pygamezero/7_mole_game.py b/mu/resources/pygamezero/7_mole_game.py index fd262e0d0..e38038470 100644 --- a/mu/resources/pygamezero/7_mole_game.py +++ b/mu/resources/pygamezero/7_mole_game.py @@ -8,11 +8,14 @@ hammer.scale = 0.5 hammer.angle = 40 +score = 0 +hammer_pressed = False + GAP_FROM_SCR = 50 moles = [] for _ in range(6): mole = Actor('mole') - mole.anchor=('left', 'top') + mole.anchor = ('left', 'top') x = random.randint(GAP_FROM_SCR, WIDTH - mole.width + GAP_FROM_SCR) y = random.randint(GAP_FROM_SCR, HEIGHT - mole.height + GAP_FROM_SCR) mole.pos = (x, y) @@ -20,16 +23,14 @@ mole.visible = False moles.append(mole) -score = 0 -hammer_pressed = False - def draw(): - global score, hammer_pressed + global score screen.blit('field', (0, 0)) for mole in moles: - if mole.visible: mole.draw() + if mole.visible: + mole.draw() if hammer_pressed and mole.visible and mole.collide_pixel(hammer): sounds.toi.play() moles.remove(mole) @@ -40,7 +41,7 @@ def draw(): def update(): - if random.randint(0, 20) == 0: + if random.randint(0, 10) == 0: if len(moles) != 0: mole_list = random.sample(moles, 1) mole_list[0].visible = not mole_list[0].visible @@ -53,7 +54,7 @@ def on_mouse_move(pos): def on_mouse_down(): - global score, hammer_pressed + global hammer_pressed hammer_pressed = True animate(hammer, angle=75, tween='accelerate', duration=0.1, on_finished=animation_done) diff --git a/mu/resources/pygamezero/background1.png b/mu/resources/pygamezero/background1.png new file mode 100644 index 000000000..bad92b7b1 Binary files /dev/null and b/mu/resources/pygamezero/background1.png differ diff --git a/mu/resources/pygamezero/background2.png b/mu/resources/pygamezero/background2.png new file mode 100644 index 000000000..c105a3c5c Binary files /dev/null and b/mu/resources/pygamezero/background2.png differ diff --git a/mu/resources/pygamezero/ball.png b/mu/resources/pygamezero/ball.png new file mode 100644 index 000000000..eeb974ddd Binary files /dev/null and b/mu/resources/pygamezero/ball.png differ diff --git a/mu/resources/pygamezero/bar.png b/mu/resources/pygamezero/bar.png new file mode 100644 index 000000000..daa3a3128 Binary files /dev/null and b/mu/resources/pygamezero/bar.png differ diff --git a/mu/resources/pygamezero/bar.wav b/mu/resources/pygamezero/bar.wav new file mode 100644 index 000000000..098bedfb1 Binary files /dev/null and b/mu/resources/pygamezero/bar.wav differ diff --git a/mu/resources/pygamezero/battle_city.py b/mu/resources/pygamezero/battle_city.py new file mode 100644 index 000000000..03134a8d5 --- /dev/null +++ b/mu/resources/pygamezero/battle_city.py @@ -0,0 +1,189 @@ +from pgzhelper import * +import random + +WIDTH = 800 +HEIGHT = 600 + +bullets = [] +bullet_delay_cnt = 0 +BULLET_DELAY = 50 +enemy_bullets = [] +explosions = [] +winner = '' + +tank = Actor("tank_blue", (400, 575)) +tank.angle = 90 + +ENEMY_MOVE_DELAY = 20 +MAX_ENEMIES = 3 +enemies = [] +for i in range(MAX_ENEMIES): + enemy = Actor("tank_red") + enemy.angle = 270 + enemy.x = (i + 1) * WIDTH / (MAX_ENEMIES + 1) + enemy.y = 25 + enemy.move_cnt = 0 + enemies.append(enemy) + +walls = [] +WALL_SIZE = 50 +# 50x50 pixel sized walls +for x in range(int(WIDTH / WALL_SIZE)): + # Substract 2 to blank both first and last row + for y in range(int(HEIGHT / WALL_SIZE - 2)): + # Randomly leave blank without wall + if random.randint(0, 100) < 50: + wall = Actor("wall", anchor=("left", "top")) + wall.x = x * WALL_SIZE + wall.y = y * WALL_SIZE + WALL_SIZE # Add WALL_SIZE to blank first row + walls.append(wall) + + +def move_player(player): + # Save the original position of the tank + original_x = player.x + original_y = player.y + + if player == tank: # My tank + if keyboard.right: + player.angle = 0 + player.x += 2 + elif keyboard.left: + player.angle = 180 + player.x -= 2 + elif keyboard.up: + player.angle = 90 + player.y -= 2 + elif keyboard.down: + player.angle = 270 + player.y += 2 + else: # Enemy + if player.angle == 0: + player.x += 2 + elif player.angle == 90: + player.y -= 2 + elif player.angle == 180: + player.x -= 2 + elif player.angle == 270: + player.y += 2 + + # Return player to original position if colliding with wall + if player.collidelist(walls) != -1: + player.x = original_x + player.y = original_y + + # Don't drive off the screen! + if player.left < 0 or player.right > WIDTH \ + or player.top < 0 or player.bottom > HEIGHT: + player.x = original_x + player.y = original_y + + +def fire_bullets(player, bullets): + if player == tank: + bullet = Actor("bulletblue2") + else: + bullet = Actor("bulletred2") + + bullet.angle = player.angle + bullet.pos = player.pos + bullets.append(bullet) + + +def collide_bullets(bullets): + global winner + + for bullet in bullets: + if bullet.angle == 0: + bullet.x += 5 + elif bullet.angle == 90: + bullet.y -= 5 + elif bullet.angle == 180: + bullet.x -= 5 + elif bullet.angle == 270: + bullet.y += 5 + + # Walls + wall_index = bullet.collidelist(walls) + if wall_index != -1: + del walls[wall_index] + bullets.remove(bullet) + + # Out of screen + if bullet.x < 0 or bullet.x > 800 \ + or bullet.y < 0 or bullet.y > 600: + bullets.remove(bullet) + + # Enemies + if bullets != enemy_bullets: + enemy_index = bullet.collidelist(enemies) + if enemy_index != -1: + bullets.remove(bullet) + explosion = Actor("explosion3") + explosion.pos = enemies[enemy_index].pos + explosion.images = ["explosion3", "explosion4"] + explosion.fps = 8 + explosion.duration = 15 + explosions.append(explosion) + del enemies[enemy_index] + if len(enemies) == 0: + winner = "You" + else: + if bullet.colliderect(tank): + winner = "Enemy" + + # Animate explosion + for explosion in explosions: + explosion.animate() + explosion.duration -= 1 + if explosion.duration == 0: + explosions.remove(explosion) + + +def draw(): + screen.blit('grass', (0, 0)) + tank.draw() + for enemy in enemies: + enemy.draw() + for wall in walls: + wall.draw() + for bullet in bullets: + bullet.draw() + for bullet in enemy_bullets: + bullet.draw() + for explosion in explosions: + explosion.draw() + + if winner: + screen.draw.text(winner + " Win!", \ + midbottom=(WIDTH / 2, HEIGHT / 2), fontsize=100) + + +def update(): + global bullet_delay_cnt, enemy_move_cnt + + # This part is for my tank + if winner == '': + move_player(tank) + if bullet_delay_cnt == 0: # Re-launch possible after the delay ends + if keyboard.space: + sounds.sfx_exp_medium12.play() + fire_bullets(tank, bullets) + bullet_delay_cnt = BULLET_DELAY + else: + bullet_delay_cnt -= 1 + collide_bullets(bullets) + + # This part is for the enemies + for enemy in enemies: + choice = random.randint(0, 2) + if enemy.move_cnt > 0: # Move tank + enemy.move_cnt -= 1 + move_player(enemy) + elif choice == 0: # Init movement delay + enemy.move_cnt = ENEMY_MOVE_DELAY + elif choice == 1: # Turn directions + enemy.angle = random.randint(0, 3) * 90 + else: # Fire canon shot + fire_bullets(enemy, enemy_bullets) + collide_bullets(enemy_bullets) diff --git a/mu/resources/pygamezero/block.png b/mu/resources/pygamezero/block.png new file mode 100644 index 000000000..7f47b328b Binary files /dev/null and b/mu/resources/pygamezero/block.png differ diff --git a/mu/resources/pygamezero/block.wav b/mu/resources/pygamezero/block.wav new file mode 100644 index 000000000..bd980a5b4 Binary files /dev/null and b/mu/resources/pygamezero/block.wav differ diff --git a/mu/resources/pygamezero/breakout.py b/mu/resources/pygamezero/breakout.py new file mode 100644 index 000000000..8e5f6c616 --- /dev/null +++ b/mu/resources/pygamezero/breakout.py @@ -0,0 +1,82 @@ +from pgzhelper import * + +TITLE = 'Breakout' +WIDTH = 800 +HEIGHT = 600 + +GAP_FROM_SCREEN = 50 +ball = Actor('ball', (WIDTH / 2, HEIGHT / 2)) +ball.radius = ball.width / 2 +bar = Actor('bar', (WIDTH / 2, HEIGHT - GAP_FROM_SCREEN)) + +# Create 4 x 8 block dummy +blocks = [] +for block_row in range(4): + for block_col in range(8): + block = Actor( + 'block', + (block_col * 100, block_row * 32 + GAP_FROM_SCREEN), + anchor=('left', 'top') + ) + blocks.append(block) + +# Set velocity of ball +vx = 5 +vy = -5 + + +def draw(): + screen.blit('space', (0, 0)) + ball.draw() + bar.draw() + for block in blocks: + block.draw() + +def update(): + global vx, vy + + # Limit the movement of bar in the window + if bar.left < 0: + bar.left = 0 + if bar.right > WIDTH: + bar.right = WIDTH + + # Move the ball by velocity + ball.move_ip(vx, vy) + + # When the ball hits left or rignt wall + if ball.left < 0 or ball.right > WIDTH: + vx = -vx # Make x of velocity opposite direction + sounds.wall.play() + + # When the ball hits upper wall + if ball.top < 0: + vy = -vy # Make y of velocity opposite direction + sounds.wall.play() + + # When the ball hits the bar + if ball.circle_colliderect(bar) == True: + ball.y -= 10 # Move back by 10 pixels before changing a direction + vy = -vy # Make y of velocity opposite direction + sounds.bar.play() + + # When the ball hits the block + b_index = ball.collidelist(blocks) + if b_index != -1: + vy = -vy # Make y of velocity opposite direction + sounds.block.play() + blocks.pop(b_index) + + # Exit the game + if ball.bottom > HEIGHT: + sounds.die.play() + game.exit() + + if not blocks: + sounds.win.play() + vx = 0 + vy = 0 + +def on_mouse_move(pos): + x, y = pos + bar.centerx = x \ No newline at end of file diff --git a/mu/resources/pygamezero/bulletblue2.png b/mu/resources/pygamezero/bulletblue2.png new file mode 100644 index 000000000..08ccc6fc7 Binary files /dev/null and b/mu/resources/pygamezero/bulletblue2.png differ diff --git a/mu/resources/pygamezero/bulletred2.png b/mu/resources/pygamezero/bulletred2.png new file mode 100644 index 000000000..e584de2ff Binary files /dev/null and b/mu/resources/pygamezero/bulletred2.png differ diff --git a/mu/resources/pygamezero/die.wav b/mu/resources/pygamezero/die.wav new file mode 100644 index 000000000..e9cd94994 Binary files /dev/null and b/mu/resources/pygamezero/die.wav differ diff --git a/mu/resources/pygamezero/enemy1_1.png b/mu/resources/pygamezero/enemy1_1.png new file mode 100644 index 000000000..9ca2bf6bb Binary files /dev/null and b/mu/resources/pygamezero/enemy1_1.png differ diff --git a/mu/resources/pygamezero/enemy1_2.png b/mu/resources/pygamezero/enemy1_2.png new file mode 100644 index 000000000..bfa622429 Binary files /dev/null and b/mu/resources/pygamezero/enemy1_2.png differ diff --git a/mu/resources/pygamezero/enemy_bullet.png b/mu/resources/pygamezero/enemy_bullet.png new file mode 100644 index 000000000..13238c799 Binary files /dev/null and b/mu/resources/pygamezero/enemy_bullet.png differ diff --git a/mu/resources/pygamezero/explosion1.png b/mu/resources/pygamezero/explosion1.png new file mode 100644 index 000000000..56e911c47 Binary files /dev/null and b/mu/resources/pygamezero/explosion1.png differ diff --git a/mu/resources/pygamezero/explosion2.png b/mu/resources/pygamezero/explosion2.png new file mode 100644 index 000000000..434677535 Binary files /dev/null and b/mu/resources/pygamezero/explosion2.png differ diff --git a/mu/resources/pygamezero/explosion3.png b/mu/resources/pygamezero/explosion3.png new file mode 100644 index 000000000..c3dce2b86 Binary files /dev/null and b/mu/resources/pygamezero/explosion3.png differ diff --git a/mu/resources/pygamezero/explosion4.png b/mu/resources/pygamezero/explosion4.png new file mode 100644 index 000000000..945b4cfb3 Binary files /dev/null and b/mu/resources/pygamezero/explosion4.png differ diff --git a/mu/resources/pygamezero/flappybird.py b/mu/resources/pygamezero/flappybird.py new file mode 100644 index 000000000..fbe9da6ad --- /dev/null +++ b/mu/resources/pygamezero/flappybird.py @@ -0,0 +1,87 @@ +from pgzhelper import * +import random + +TITLE = "Flappy Bird" +WIDTH = 400 +HEIGHT = 708 + +GRAVITY = 0.3 +drop_speed = 0 +GAP = 140 # Gap between pipes +bird_alive = True +score = 0 +PIPE_SPEED = 3 + +# Create actors +flappy_bird = Actor('bird1', (75, 350)) +flappy_bird.images = ['bird0', 'bird1', 'bird2'] +flappy_bird.fps = 10 + +top_pipe = Actor('top', (350, 0)) +bottom_pipe = Actor('bottom', (350, top_pipe.height + GAP)) + + +def draw(): + screen.blit('background', (0, 0)) + flappy_bird.draw() + top_pipe.draw() + bottom_pipe.draw() + + # Show a score + screen.draw.text( + str(score), + color = 'white', + midtop = (WIDTH / 2, 10), + fontsize = 70, + shadow = (1, 1) + ) + +# Create random pipe +def reset_pipes(): + random_y = random.randint(-100, 100) + top_pipe.y = random_y + bottom_pipe.y = top_pipe.height + GAP + random_y + top_pipe.x = WIDTH + bottom_pipe.x = WIDTH + +def update(): + global drop_speed, bird_alive, score + drop_speed += GRAVITY + flappy_bird.y += drop_speed + if bird_alive == True: + flappy_bird.animate() + + top_pipe.x -= PIPE_SPEED + bottom_pipe.x -= PIPE_SPEED + + # Seamless pipes + if top_pipe.right < 0 or top_pipe.right < 0: + reset_pipes() + if bird_alive == True: + score += 1 + + # Check collision between bir and pipes + if flappy_bird.colliderect(top_pipe) or flappy_bird.colliderect(bottom_pipe): + flappy_bird.image = "birddead" + bird_alive = False + + # Restart a game + if flappy_bird.y > HEIGHT or flappy_bird.y < 0 : + flappy_bird.image = 'bird1' + bird_alive = True + flappy_bird.center = (75, 350) + drop_speed = 0 + reset_pipes() + score = 0 + +# When mouse or keyboard is pressed +def on_mouse_down(): + global drop_speed + if bird_alive == True: + drop_speed = -5 + +def on_key_down(): + global drop_speed + if bird_alive == True: + drop_speed = -5 + diff --git a/mu/resources/pygamezero/flappybird_neosoco.py b/mu/resources/pygamezero/flappybird_neosoco.py new file mode 100644 index 000000000..12bf80514 --- /dev/null +++ b/mu/resources/pygamezero/flappybird_neosoco.py @@ -0,0 +1,93 @@ +from pgzhelper import * +from neopia import * +import random + +TITLE = 'Flappy Bird' +WIDTH = 400 +HEIGHT = 708 + +GRAVITY = 0.3 +drop_speed = 0 +bird_alive = True + +GAP = 140 # Gap between pipes +PIPE_SPEED = -3 + +score = 0 + +# Create actors +flappy_bird = Actor('bird1', (75, 350)) +flappy_bird.images = ['bird0', 'bird1', 'bird2'] +flappy_bird.fps = 10 + +top_pipe = Actor('top', (350,0)) +bottom_pipe = Actor('bottom', (350, top_pipe.height + GAP)) + +# Create Neosoco obj. +n = Neosoco() + + +def draw(): + screen.blit('background', (0, 0)) + flappy_bird.draw() + top_pipe.draw() + bottom_pipe.draw() + + screen.draw.text( + str(score), + color = 'white', + midtop = (WIDTH/2, 10), + fontsize = 70, + shadow = (1, 1) + ) + +# Create random pipe +def reset_pipes(): + random_y = random.randint(-100, 100) + top_pipe.y = random_y + bottom_pipe.y = top_pipe.height + GAP + random_y + top_pipe.x = WIDTH + bottom_pipe.x = WIDTH + +def update(): + global drop_speed, bird_alive, score + drop_speed += GRAVITY + flappy_bird.y += drop_speed + if bird_alive == True: + flappy_bird.animate() + + top_pipe.x += PIPE_SPEED + bottom_pipe.x += PIPE_SPEED + + # Seamless pipes + if top_pipe.right < 0 or bottom_pipe.right < 0: + reset_pipes() + if bird_alive == True: + score += 1 + + # Check collision between bir and pipes + if flappy_bird.colliderect(top_pipe) or flappy_bird.colliderect(bottom_pipe): + flappy_bird.image = "birddead" + bird_alive = False + + # Restart a game + if flappy_bird.y > HEIGHT or flappy_bird.y < 0: + flappy_bird.image = "bird1" + bird_alive = True + flappy_bird.center = (75, 350) + drop_speed = 0 + reset_pipes() + score = 0 + n.led_off() + + if n.get_value() > 50: # default port: IN1 + n.led_on() # default port: OUT1 + wait(20) + on_mouse_down() + +# When mouse is pressed +def on_mouse_down(): + global drop_speed + if bird_alive == True: + drop_speed = -3.5 + n.led_off() diff --git a/mu/resources/pygamezero/grass.png b/mu/resources/pygamezero/grass.png new file mode 100644 index 000000000..8e2592beb Binary files /dev/null and b/mu/resources/pygamezero/grass.png differ diff --git a/mu/resources/pygamezero/main_theme.mp3 b/mu/resources/pygamezero/main_theme.mp3 new file mode 100644 index 000000000..9d459ec0e Binary files /dev/null and b/mu/resources/pygamezero/main_theme.mp3 differ diff --git a/mu/resources/pygamezero/pgzhelper.py b/mu/resources/pygamezero/pgzhelper.py deleted file mode 100644 index 8499424ef..000000000 --- a/mu/resources/pygamezero/pgzhelper.py +++ /dev/null @@ -1,1478 +0,0 @@ -"""pgzhelper - Enhance Pygame Zero with additional capabilities. - -This module is directly copied from - - https://github.com/roboticsware/pgzhelper - -at revision a4b60d00b824c7cea10845966297e34e6df995a5 -and used under CC0. - -""" -# flake8: noqa: E501 -# from __future__ import annotations -import math -import pygame -from pgzero.actor import Actor, POS_TOPLEFT, ANCHOR_CENTER, transform_anchor -from pgzero import game, loaders -from pgzero.screen import ptext -import sys -import time -from typing import Sequence, Tuple, Union -from pygame import Vector2 - -_Coordinate = Union[Tuple[float, float], Sequence[float], Vector2] -_fullscreen = False - - -def set_fullscreen(): - global _fullscreen - mod = sys.modules['__main__'] - mod.screen.surface = pygame.display.set_mode( - (mod.WIDTH, mod.HEIGHT), pygame.FULLSCREEN) - _fullscreen = True - - -def set_windowed(): - global _fullscreen - mod = sys.modules['__main__'] - mod.screen.surface = pygame.display.set_mode((mod.WIDTH, mod.HEIGHT)) - _fullscreen = False - - -def toggle_fullscreen(): - if _fullscreen: - set_windowed() - else: - set_fullscreen() - - -def hide_mouse(): - pygame.mouse.set_visible(False) - - -def show_mouse(): - pygame.mouse.set_visible(True) - - -def distance_to(from_x, from_y, to_x, to_y): - dx = to_x - from_x - dy = to_y - from_y - return math.sqrt(dx**2 + dy**2) - - -def distance_to_squared(from_x, from_y, to_x, to_y): - dx = to_x - from_x - dy = to_y - from_y - return dx**2 + dy**2 - - -def direction_to(from_x, from_y, to_x, to_y): - dx = to_x - from_x - dy = from_y - to_y - - angle = math.degrees(math.atan2(dy, dx)) - if angle > 0: - return angle - - return 360 + angle - - -def get_move(direction, distance): - angle = math.radians(direction) - dx = distance * math.cos(angle) - dy = -distance * math.sin(angle) - return (dx, dy) - - -def move(x, y, direction, distance): - dx, dy = get_move(direction, distance) - return (x + dx, y + dy) - - -class Collide(): - @staticmethod - def line_line(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): - l1x2_l1x1 = l1x2 - l1x1 - l1y2_l1y1 = l1y2 - l1y1 - - determinant = (l2y2 - l2y1) * l1x2_l1x1 - (l2x2 - l2x1) * l1y2_l1y1 - - # Simplify: Parallel lines are never considered to be intersecting - if determinant == 0: - return False - - uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - (l2y2 - l2y1) * (l1x1 - l2x1)) / determinant - if uA < 0 or uA > 1: - return False - - uB = (l1x2_l1x1 * (l1y1 - l2y1) - l1y2_l1y1 * (l1x1 - l2x1)) / determinant - if uB < 0 or uB > 1: - return False - - return True - - @staticmethod - def line_lines(l1x1, l1y1, l1x2, l1y2, l2): - l1x2_l1x1 = l1x2 - l1x1 - l1y2_l1y1 = l1y2 - l1y1 - - i = 0 - for l in l2: - determinant = (l[3] - l[1]) * l1x2_l1x1 - (l[2] - l[0]) * l1y2_l1y1 - - # Simplify: Parallel lines are never considered to be intersecting - if determinant == 0: - i += 1 - continue - - uA = ((l[2] - l[0]) * (l1y1 - l[1]) - - (l[3] - l[1]) * (l1x1 - l[0])) / determinant - uB = (l1x2_l1x1 * (l1y1 - l[1]) - l1y2_l1y1 * (l1x1 - l[0])) / determinant - if 0 <= uA <= 1 and 0 <= uB <= 1: - return i - - i += 1 - - return -1 - - @staticmethod - def line_line_XY(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): - determinant = (l2y2 - l2y1) * (l1x2 - l1x1) - (l2x2 - l2x1) * (l1y2 - l1y1) - - # Simplify: Parallel lines are never considered to be intersecting - if determinant == 0: - return (None, None) - - uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - (l2y2 - l2y1) * (l1x1 - l2x1)) / determinant - uB = ((l1x2 - l1x1) * (l1y1 - l2y1) - (l1y2 - l1y1) * (l1x1 - l2x1)) / determinant - - if 0 <= uA <= 1 and 0 <= uB <= 1: - ix = l1x1 + uA * (l1x2 - l1x1) - iy = l1y1 + uA * (l1y2 - l1y1) - return (ix, iy) - - return (None, None) - - @staticmethod - def line_line_dist(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): - ix, iy = Collide.line_line_XY(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2) - if ix is not None: - return distance_to(l1x1, l1y1, ix, iy) - return None - - @staticmethod - def line_line_dist_squared(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2): - ix, iy = Collide.line_line_XY(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2) - if ix is not None: - return distance_to_squared(l1x1, l1y1, ix, iy) - return None - - @staticmethod - def line_circle(x1, y1, x2, y2, cx, cy, radius): - r_sq = radius ** 2 - - dist_sq = (x1 - cx) ** 2 + (y1 - cy) ** 2 - if dist_sq <= r_sq: - return True - - dist_sq = (x2 - cx) ** 2 + (y2 - cy) ** 2 - if dist_sq <= r_sq: - return True - - dx = x2 - x1 - dy = y2 - y1 - l_sq = dx ** 2 + dy ** 2 - dot = (((cx - x1) * dx) + ((cy - y1) * dy)) / l_sq - - ix = x1 + dot * dx - if (dx != 0) and (ix < x1) == (ix < x2): - return False - - iy = y1 + dot * dy - if (dy != 0) and (iy < y1) == (iy < y2): - return False - - dist_sq = (ix - cx) ** 2 + (iy - cy) ** 2 - if dist_sq <= r_sq: - return True - - return False - - @staticmethod - def line_circle_XY(x1, y1, x2, y2, cx, cy, radius): - if Collide.circle_point(cx, cy, radius, x1, y1): - return (x1, y1) - - x1 -= cx - y1 -= cy - x2 -= cx - y2 -= cy - - if x2 < x1: - x_min, x_max = x2, x1 - else: - x_min, x_max = x1, x2 - - if y2 < y1: - y_min, y_max = y2, y1 - else: - y_min, y_max = y1, y2 - - # Coefficients of circle - c_r2 = radius ** 2 - - # Simplify if dx == 0: Vertical line - dx = x2 - x1 - if dx == 0: - d = c_r2 - x1**2 - if d < 0: - return (None, None) - elif d == 0: - i = 0 - else: - i = math.sqrt(d) - - iy = None - if y_min <= i <= y_max: - iy = i - - if y_min <= -i <= y_max: - if iy is None or abs(i - y1) > abs(-i - y1): - iy = -i - - if iy: - return (x1 + cx, iy + cy) - return (None, None) - - # Gradient of line - l_m = (y2 - y1) / dx - - # Simplify if l_m == 0: Horizontal line - if l_m == 0: - d = c_r2 - y1**2 - if d < 0: - return (None, None) - elif d == 0: - i = 0 - else: - i = math.sqrt(d) - ix = None - if x_min <= i <= x_max: - ix = i - - if x_min <= -i <= x_max: - if ix is None or abs(i - x1) > abs(-i - x1): - ix = -i - - if ix: - return (ix + cx, y1 + cy) - return (None, None) - - # y intercept - l_c = y1 - l_m * x1 - - # Coefficients of quadratic - a = 1 + l_m**2 - b = 2 * l_c * l_m - c = l_c**2 - c_r2 - - # Calculate discriminant and solve quadratic - discriminant = b**2 - 4 * a * c - if discriminant < 0: - return (None, None) - - if discriminant == 0: - d_root = 0 - else: - d_root = math.sqrt(discriminant) - - ix = None - i1 = (-b + d_root) / (2 * a) - if x_min <= i1 <= x_max: - ix = i1 - - i2 = (-b - d_root) / (2 * a) - if x_min <= i2 <= x_max: - if ix is None or abs(i1 - x1) > abs(i2 - x1): - ix = i2 - - if ix: - return (ix + cx, l_m * ix + l_c + cy) - - return (None, None) - - @staticmethod - def line_circle_dist(x1, y1, x2, y2, cx, cy, radius): - ix, iy = Collide.line_circle_XY(x1, y1, x2, y2, cx, cy, radius) - if ix is not None: - return distance_to(x1, y1, ix, iy) - return None - - @staticmethod - def line_circle_dist_squared(x1, y1, x2, y2, cx, cy, radius): - ix, iy = Collide.line_circle_XY(x1, y1, x2, y2, cx, cy, radius) - if ix is not None: - return distance_to_squared(x1, y1, ix, iy) - return None - - @staticmethod - def line_rect(x1, y1, x2, y2, rx, ry, w, h): - if Collide.rect_points(rx, ry, w, h, [(x1, y1), (x2, y2)]) != -1: - return True - - half_w = w / 2 - half_h = h / 2 - rect_lines = [ - [rx - half_w, ry - half_h, rx - half_w, ry + half_h], - [rx - half_w, ry - half_h, rx + half_w, ry - half_h], - [rx + half_w, ry + half_h, rx - half_w, ry + half_h], - [rx + half_w, ry + half_h, rx + half_w, ry - half_h], - ] - if Collide.line_lines(x1, y1, x2, y2, rect_lines) != -1: - return True - - return False - - @staticmethod - def line_rect_XY(x1, y1, x2, y2, rx, ry, w, h): - if Collide.rect_point(rx, ry, w, h, x1, y1): - return (x1, y1) - - half_w = w / 2 - half_h = h / 2 - rect_lines = [ - [rx - half_w, ry - half_h, rx - half_w, ry + half_h], - [rx - half_w, ry - half_h, rx + half_w, ry - half_h], - [rx + half_w, ry + half_h, rx - half_w, ry + half_h], - [rx + half_w, ry + half_h, rx + half_w, ry - half_h], - ] - XYs = [] - for l in rect_lines: - ix, iy = Collide.line_line_XY(x1, y1, x2, y2, l[0], l[1], l[2], l[3]) - if ix is not None: - XYs.append((ix, iy)) - - length = len(XYs) - if length == 0: - return (None, None) - elif length == 1: - return XYs[0] - - ix, iy = XYs[0] - shortest_dist = (ix - x1) ** 2 + (iy - y1) ** 2 - for XY in XYs: - dist = (XY[0] - x1) ** 2 + (XY[1] - y1) ** 2 - if dist < shortest_dist: - ix, iy = XY - shortest_dist = dist - - return (ix, iy) - - @staticmethod - def line_rect_dist(x1, y1, x2, y2, rx, ry, w, h): - ix, iy = Collide.line_rect_XY(x1, y1, x2, y2, rx, ry, w, h) - if ix is not None: - return distance_to(x1, y1, ix, iy) - return None - - @staticmethod - def line_rect_dist_squared(x1, y1, x2, y2, rx, ry, w, h): - ix, iy = Collide.line_rect_XY(x1, y1, x2, y2, rx, ry, w, h) - if ix is not None: - return distance_to_squared(x1, y1, ix, iy) - return None - - @staticmethod - def line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - tx = x1 - ox - ty = y1 - oy - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: - return (x1, y1) - - wc = half_width * costheta - hs = half_height * sintheta - hc = half_height * costheta - ws = half_width * sintheta - p = [ - [ox + wc + hs, oy + hc - ws], - [ox - wc + hs, oy + hc + ws], - [ox + wc - hs, oy - hc - ws], - [ox - wc - hs, oy - hc + ws], - ] - obb_lines = [ - [p[0][0], p[0][1], p[1][0], p[1][1]], - [p[1][0], p[1][1], p[3][0], p[3][1]], - [p[3][0], p[3][1], p[2][0], p[2][1]], - [p[2][0], p[2][1], p[0][0], p[0][1]] - ] - - XYs = [] - for l in obb_lines: - ix, iy = Collide.line_line_XY(x1, y1, x2, y2, l[0], l[1], l[2], l[3]) - if ix is not None: - XYs.append((ix, iy)) - - length = len(XYs) - if length == 0: - return (None, None) - elif length == 1: - return XYs[0] - - ix, iy = XYs[0] - shortest_dist = (ix - x1) ** 2 + (iy - y1) ** 2 - for XY in XYs: - dist = (XY[0] - x1) ** 2 + (XY[1] - y1) ** 2 - if dist < shortest_dist: - ix, iy = XY - shortest_dist = dist - - return (ix, iy) - - @staticmethod - def line_obb_dist(x1, y1, x2, y2, ox, oy, w, h, angle): - ix, iy = Collide.line_obb_XY(x1, y1, x2, y2, ox, oy, w, h, angle) - if ix is not None: - return distance_to(x1, y1, ix, iy) - return None - - @staticmethod - def line_obb_dist_squared(x1, y1, x2, y2, ox, oy, w, h, angle): - ix, iy = Collide.obb_line_XY(x1, y1, x2, y2, ox, oy, w, h, angle) - if ix is not None: - return distance_to_squared(x1, y1, ix, iy) - return None - - @staticmethod - def circle_point(x1, y1, radius, x2, y2): - rSquare = radius ** 2 - dSquare = (x2 - x1)**2 + (y2 - y1)**2 - - if dSquare < rSquare: - return True - - return False - - @staticmethod - def circle_points(x, y, radius, points): - rSquare = radius ** 2 - - i = 0 - for point in points: - try: - px = point[0] - py = point[1] - except (KeyError, TypeError): - px = point.x - py = point.y - dSquare = (px - x)**2 + (py - y)**2 - - if dSquare < rSquare: - return i - i += 1 - - return -1 - - @staticmethod - def circle_line(cx, cy, radius, x1, y1, x2, y2): - return Collide.line_circle(x1, y1, x2, y2, cx, cy, radius) - - @staticmethod - def circle_circle(x1, y1, r1, x2, y2, r2): - rSquare = (r1 + r2) ** 2 - dSquare = (x2 - x1)**2 + (y2 - y1)**2 - - if dSquare < rSquare: - return True - - return False - - @staticmethod - def circle_rect(cx, cy, cr, rx, ry, rw, rh): - h_w = rw / 2 - h_h = rh / 2 - rect_l = rx - h_w - rect_t = ry - h_h - - if cx < rect_l: - dx2 = (cx - rect_l) ** 2 - elif cx > (rect_l + rw): - dx2 = (cx - rect_l - rw) ** 2 - else: - dx2 = 0 - - if cy < rect_t: - dy2 = (cy - rect_t) ** 2 - elif cy > (rect_t + rh): - dy2 = (cy - rect_t - rh) ** 2 - else: - dy2 = 0 - - dist2 = dx2 + dy2 - - if dist2 < (cr ** 2): - return True - - return False - - @staticmethod - def rect_point(x, y, w, h, px, py): - half_w = w / 2 - half_h = h / 2 - - if ( - px < x - half_w - or px > x + half_w - or py < y - half_h - or py > y + half_h - ): - return False - - return True - - @staticmethod - def rect_points(x, y, w, h, points): - half_w = w / 2 - half_h = h / 2 - min_x = x - half_w - max_x = x + half_w - min_y = y - half_h - max_y = y + half_h - - i = 0 - for point in points: - try: - px = point[0] - py = point[1] - except (KeyError, TypeError): - px = point.x - py = point.y - if ( - px >= min_x - and px <= max_x - and py >= min_y - and py <= max_y - ): - return i - i += 1 - - return -1 - - @staticmethod - def rect_line(x, y, w, h, lx1, ly1, lx2, ly2): - return Collide.line_rect(lx1, ly1, lx2, ly2, x, y, w, h) - - @staticmethod - def rect_circle(rx, ry, rw, rh, cx, cy, cr): - return Collide.circle_rect(cx, cy, cr, rx, ry, rw, rh) - - @staticmethod - def rect_rect(x1, y1, w1, h1, x2, y2, w2, h2): - h_w1 = w1 / 2 - h_h1 = h1 / 2 - h_w2 = w2 / 2 - h_h2 = h2 / 2 - - if ( - x2 - h_w2 > x1 + h_w1 - or x2 + h_w2 < x1 - h_w1 - or y2 - h_h2 > y1 + h_h1 - or y2 + h_h2 < y1 - h_h1 - ): - return False - - return True - - @staticmethod - def obb_point(x, y, w, h, angle, px, py): - half_width = w / 2 - half_height = h / 2 - b_radius_sq = half_width ** 2 + half_height ** 2 - tx = px - x - ty = py - y - - if tx ** 2 + ty ** 2 > b_radius_sq: - return False - - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: - return True - - return False - - @staticmethod - def obb_points(x, y, w, h, angle, points): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - i = 0 - for point in points: - try: - px = point[0] - py = point[1] - except (KeyError, TypeError): - px = point.x - py = point.y - - tx = px - x - ty = py - y - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: - return i - i += 1 - - return -1 - - @staticmethod - def obb_line(x, y, w, h, angle, lx1, ly1, lx2, ly2): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - tx = lx1 - x - ty = ly1 - y - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: - return True - - tx = lx2 - x - ty = ly2 - y - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: - return True - - wc = half_width * costheta - hs = half_height * sintheta - hc = half_height * costheta - ws = half_width * sintheta - p = [ - [x + wc + hs, y + hc - ws], - [x - wc + hs, y + hc + ws], - [x + wc - hs, y - hc - ws], - [x - wc - hs, y - hc + ws], - ] - obb_lines = [ - [p[0][0], p[0][1], p[1][0], p[1][1]], - [p[1][0], p[1][1], p[3][0], p[3][1]], - [p[3][0], p[3][1], p[2][0], p[2][1]], - [p[2][0], p[2][1], p[0][0], p[0][1]] - ] - - if Collide.line_lines(lx1, ly1, lx2, ly2, obb_lines) != -1: - return True - - return False - - @staticmethod - def obb_lines(x, y, w, h, angle, lines): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - wc = half_width * costheta - hs = half_height * sintheta - hc = half_height * costheta - ws = half_width * sintheta - p = [ - [x + wc + hs, y + hc - ws], - [x - wc + hs, y + hc + ws], - [x + wc - hs, y - hc - ws], - [x - wc - hs, y - hc + ws], - ] - obb_lines = [ - [p[0][0], p[0][1], p[1][0], p[1][1]], - [p[1][0], p[1][1], p[3][0], p[3][1]], - [p[3][0], p[3][1], p[2][0], p[2][1]], - [p[2][0], p[2][1], p[0][0], p[0][1]] - ] - - i = 0 - for l in lines: - tx = l[0] - x - ty = l[1] - y - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: - return i - - tx = l[2] - x - ty = l[3] - y - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if rx > -half_width and rx < half_width and ry > -half_height and ry < half_height: - return i - - if Collide.line_lines(l[0], l[1], l[2], l[3], obb_lines) != -1: - return i - - i += 1 - - return -1 - - @staticmethod - def obb_circle(x, y, w, h, angle, cx, cy, radius): - half_width = w / 2 - half_height = h / 2 - tx = cx - x - ty = cy - y - - if tx ** 2 + ty ** 2 > (half_height + half_width + radius) ** 2: - return False - - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if (rx < -half_width - radius - or rx > half_width + radius - or ry < -half_height - radius - or ry > half_height + radius - ): - return False - - if (rx <= half_width and rx >= -half_width) or (ry <= half_height and ry >= -half_height): - return True - - dx = abs(rx) - half_width - dy = abs(ry) - half_height - dist_squared = dx ** 2 + dy ** 2 - if dist_squared > radius ** 2: - return False - - return True - - @staticmethod - def obb_circles(x, y, w, h, angle, circles): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - i = 0 - for circle in circles: - tx = circle[0] - x - ty = circle[1] - y - - rx = tx * costheta - ty * sintheta - ry = ty * costheta + tx * sintheta - - if (rx < -half_width - circle[2] - or rx > half_width + circle[2] - or ry < -half_height - circle[2] - or ry > half_height + circle[2] - ): - i += 1 - continue - - if (rx <= half_width and rx >= -half_width) or (ry <= half_height and ry >= -half_height): - return i - - dx = abs(rx) - half_width - dy = abs(ry) - half_height - dist_squared = dx ** 2 + dy ** 2 - if dist_squared > circle[2] ** 2: - i += 1 - continue - - return i - - return -1 - - @staticmethod - def obb_rect(x, y, w, h, angle, rx, ry, rw, rh): - half_width = w / 2 - half_height = h / 2 - tx = rx - x - ty = ry - y - - if tx ** 2 + ty ** 2 > (half_height + half_width + rw + rh) ** 2: - return False - - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - tx2 = tx * costheta - ty * sintheta - ty2 = ty * costheta + tx * sintheta - - if tx2 > -half_width and tx2 < half_width and ty2 > -half_height and ty2 < half_height: - return True - - wc = half_width * costheta - hs = half_height * sintheta - hc = half_height * costheta - ws = half_width * sintheta - p = [ - [wc + hs, hc - ws], - [-wc + hs, hc + ws], - [wc - hs, -hc - ws], - [-wc - hs, -hc + ws], - ] - obb_lines = [ - [p[0][0], p[0][1], p[1][0], p[1][1]], - [p[1][0], p[1][1], p[3][0], p[3][1]], - [p[3][0], p[3][1], p[2][0], p[2][1]], - [p[2][0], p[2][1], p[0][0], p[0][1]] - ] - h_rw = rw / 2 - h_rh = rh / 2 - rect_lines = [ - [tx - h_rw, ty - h_rh, tx - h_rw, ty + h_rh], - [tx + h_rw, ty - h_rh, tx + h_rw, ty + h_rh], - [tx - h_rw, ty - h_rh, tx + h_rw, ty - h_rh], - [tx - h_rw, ty + h_rh, tx + h_rw, ty + h_rh] - ] - - for obb_p in p: - if obb_p[0] > tx - h_rw and obb_p[0] < tx + h_rw and obb_p[1] > ty - h_rh and obb_p[1] < ty + h_rh: - return True - - for obb_line in obb_lines: - l1x1 = obb_line[0] - l1y1 = obb_line[1] - l1x2 = obb_line[2] - l1y2 = obb_line[3] - l1x2_l1x1 = l1x2 - l1x1 - l1y2_l1y1 = l1y2 - l1y1 - - for rect_line in rect_lines: - l2x1 = rect_line[0] - l2y1 = rect_line[1] - l2x2 = rect_line[2] - l2y2 = rect_line[3] - - determinant = (l2y2 - l2y1) * l1x2_l1x1 - (l2x2 - l2x1) * l1y2_l1y1 - - # Simplify: Parallel lines are never considered to be intersecting - if determinant == 0: - continue - - uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - - (l2y2 - l2y1) * (l1x1 - l2x1)) / determinant - if uA < 0 or uA > 1: - continue - - uB = (l1x2_l1x1 * (l1y1 - l2y1) - l1y2_l1y1 * (l1x1 - l2x1)) / determinant - if uB < 0 or uB > 1: - continue - - return True - - return False - - @staticmethod - def obb_rects(x, y, w, h, angle, rects): - half_width = w / 2 - half_height = h / 2 - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - i = 0 - for rect in rects: - rx = rect[0] - ry = rect[1] - rw = rect[2] - rh = rect[3] - - tx = rx - x - ty = ry - y - - if tx ** 2 + ty ** 2 > (half_height + half_width + rw + rh) ** 2: - i += 1 - continue - - tx2 = tx * costheta - ty * sintheta - ty2 = ty * costheta + tx * sintheta - - if tx2 > -half_width and tx2 < half_width and ty2 > -half_height and ty2 < half_height: - return i - - wc = half_width * costheta - hs = half_height * sintheta - hc = half_height * costheta - ws = half_width * sintheta - p = [ - [wc + hs, hc - ws], - [-wc + hs, hc + ws], - [wc - hs, -hc - ws], - [-wc - hs, -hc + ws], - ] - obb_lines = [ - [p[0][0], p[0][1], p[1][0], p[1][1]], - [p[1][0], p[1][1], p[3][0], p[3][1]], - [p[3][0], p[3][1], p[2][0], p[2][1]], - [p[2][0], p[2][1], p[0][0], p[0][1]] - ] - h_rw = rw / 2 - h_rh = rh / 2 - rect_lines = [ - [tx - h_rw, ty - h_rh, tx - h_rw, ty + h_rh], - [tx + h_rw, ty - h_rh, tx + h_rw, ty + h_rh], - [tx - h_rw, ty - h_rh, tx + h_rw, ty - h_rh], - [tx - h_rw, ty + h_rh, tx + h_rw, ty + h_rh] - ] - - for obb_p in p: - if obb_p[0] > tx - h_rw and obb_p[0] < tx + h_rw and obb_p[1] > ty - h_rh and obb_p[1] < ty + h_rh: - return i - - for obb_line in obb_lines: - l1x1 = obb_line[0] - l1y1 = obb_line[1] - l1x2 = obb_line[2] - l1y2 = obb_line[3] - l1x2_l1x1 = l1x2 - l1x1 - l1y2_l1y1 = l1y2 - l1y1 - - for rect_line in rect_lines: - l2x1 = rect_line[0] - l2y1 = rect_line[1] - l2x2 = rect_line[2] - l2y2 = rect_line[3] - - determinant = (l2y2 - l2y1) * l1x2_l1x1 - (l2x2 - l2x1) * l1y2_l1y1 - - # Simplify: Parallel lines are never considered to be intersecting - if determinant == 0: - continue - - uA = ((l2x2 - l2x1) * (l1y1 - l2y1) - - (l2y2 - l2y1) * (l1x1 - l2x1)) / determinant - if uA < 0 or uA > 1: - continue - - uB = (l1x2_l1x1 * (l1y1 - l2y1) - - l1y2_l1y1 * (l1x1 - l2x1)) / determinant - if uB < 0 or uB > 1: - continue - - return i - - i += 1 - - return -1 - - def obb_obb(x, y, w, h, angle, x2, y2, w2, h2, angle2): - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - - tx2 = x2 - x - ty2 = y2 - y - rx2 = tx2 * costheta - ty2 * sintheta - ry2 = ty2 * costheta + tx2 * sintheta - return Collide.obb_rect(rx2, ry2, w2, h2, angle2 - angle, 0, 0, w, h) - - def obb_obbs(x, y, w, h, angle, obbs): - r_angle = math.radians(angle) - costheta = math.cos(r_angle) - sintheta = math.sin(r_angle) - for obb in obbs: - x2, y2, w2, h2, angle2 = obb - tx2 = x2 - x - ty2 = y2 - y - rx2 = tx2 * costheta - ty2 * sintheta - ry2 = ty2 * costheta + tx2 * sintheta - return Collide.obb_rect(rx2, ry2, w2, h2, angle2 - angle, 0, 0, w, h) - - -class Actor(Actor): - def __init__(self, image: Union[str, pygame.Surface], pos=POS_TOPLEFT, anchor=ANCHOR_CENTER, **kwargs): - self._flip_x = False - self._flip_y = False - self._scale = 1 - self._mask = None - self._images = None - self._image_idx = 0 - self._subrects = None - self._transform_cnt = 0 - self._orig_surfs = {} - self._surfs = {} - self._tp_surf = None - self._last_pos = None - self._animate_counter = 0 - self._animate_run = False - self._radius = None - self._collision_width = None - self._collision_height = None - self.fps = 5 - self.direction = 0 - subrect = kwargs.pop('subrect', None) - image_str = None - if isinstance(image, str): - image_str = image - super().__init__(image_str, pos, anchor, **kwargs) - if isinstance(image, pygame.Surface): - self._orig_surf = image - self._update_pos() - self._subrect = None - if subrect is not None: - self.subrect = subrect - - def distance_to(self, target): - if isinstance(target, Actor): - x, y = target.pos - else: - x, y = target - return distance_to(self.x, self.y, x, y) - - def distance_toXY(self, x, y): - return distance_to(self.x, self.y, x, y) - - def direction_to(self, target): - if isinstance(target, Actor): - x, y = target.pos - else: - x, y = target - return direction_to(self.x, self.y, x, y) - - def direction_toXY(self, x, y): - return direction_to(self.x, self.y, x, y) - - def move_towards(self, target: Union[int, float, Actor, _Coordinate], dist, stop_on_target=True): - if isinstance(target, (int, float)): - direction = target - else: - direction = self.direction_to(target) - if stop_on_target: - target_distance = self.distance_to(target) - if (target_distance < dist) and dist > 0: - dist = target_distance - self.x, self.y = move(self.x, self.y, direction, dist) - - def move_towardsXY(self, x, y, dist): - direction = self.direction_toXY(x, y) - self.x, self.y = move(self.x, self.y, direction, dist) - - def point_towards(self, actor, y=None): - self.angle = self.direction_to(actor) - - def point_towardsXY(self, x, y): - self.angle = direction_to(self.x, self.y, x, y) - - def move_in_direction(self, dist): - self.x, self.y = move(self.x, self.y, self.direction, dist) - - def move_forward(self, dist): - self.x, self.y = move(self.x, self.y, self.angle, dist) - - def move_left(self, dist): - self.x, self.y = move(self.x, self.y, self.angle + 90, dist) - - def move_right(self, dist): - self.x, self.y = move(self.x, self.y, self.angle - 90, dist) - - def move_back(self, dist): - self.x, self.y = move(self.x, self.y, self.angle, -dist) - - @property - def images(self): - return self._images - - @images.setter - def images(self, images): - self._subrects = None - self._images = images - if len(self._images) != 0: - self.image = self._images[0] - - def load_images(self, sheet_name: str, cols: int, rows: int, cnt: int = 0, subrect: pygame.Rect = None): - self._subrects = [None] * cols * rows - self._image_idx = 0 - sheet: pygame.Surface = loaders.images.load(sheet_name) - if subrect is not None: - sheet = sheet.subsurface(subrect) - for col in range(0, cols): - for row in range(0, rows): - width = sheet.get_width() / cols - height = sheet.get_height() / rows - self._subrects[col + row * cols] = (int(col * width), - int(row * height), int(width), int(height)) - if len(self._subrects) != 0: - self.image = sheet_name - self.subrect = self._subrects[0] - - def sel_image(self, newimage: Union[str, int]) -> bool: - try: - if isinstance(newimage, int): - if self._subrects is None and self._images is None: - return False - if self._subrects is not None: - self.subrect = self._subrects[newimage] - else: - self.image = self._images[newimage] - self._image_idx = newimage - return True - else: - self._image_idx = self._images.index(newimage) - self.image = newimage - except: - return False - - def next_image(self) -> int: - if self._subrects is not None: - next_image_idx = (self._image_idx + 1) % len(self._subrects) - self._image_idx = next_image_idx - self.subrect = self._subrects[self._image_idx] - elif (self._images is not None): - if (self.image in self._images): - next_image_idx = (self._images.index( - self.image) + 1) % len(self._images) - self._image_idx = next_image_idx - self.image = self._images[self._image_idx] - else: - self._image_idx = 0 - self.image = self._images[0] - else: - self._image_idx = 0 - return self._image_idx - - def animate(self) -> int: - now = int(time.time() * self.fps) - if self._animate_counter == 0: - self._animate_counter = now - frames_elapsed = now - self._animate_counter - - if frames_elapsed != 0: - self._animate_counter = now - idx = self.next_image() - return idx - else: - return -1 - - @property - def angle(self): - return self._angle - - @angle.setter - def angle(self, angle): - self._angle = angle - self._transform_surf() - self._transform_cnt += 1 - - @property - def scale(self): - return self._scale - - @scale.setter - def scale(self, scale): - self._scale = scale - self._transform_surf() - self._transform_cnt += 1 - - @property - def flip_x(self): - return self._flip_x - - @flip_x.setter - def flip_x(self, flip_x): - self._flip_x = flip_x - self._transform_surf() - self._transform_cnt += 1 - - @property - def flip_y(self): - return self._flip_y - - @flip_y.setter - def flip_y(self, flip_y): - self._flip_y = flip_y - self._transform_surf() - self._transform_cnt += 1 - - @property - def image(self): - return self._image_name - - @image.setter - def image(self, image): - if image is not None: - self._orig_surf = self._surf = loaders.images.load(image) - self._image_name = image - self._orig_surfs[image] = self._orig_surf - else: - self._orig_surf = self._surf = pygame.Surface((1, 1), pygame.SRCALPHA) - self._image_name = '' - self._update_pos() - if image is not None: - if (image not in self._surfs) or (self._surfs[image][1] != self._transform_cnt): - self._transform_surf() - self._surfs[image] = (self._surf, self._transform_cnt) - - @property - def subrect(self): - return self._subrect - - @subrect.setter - def subrect(self, subrect: pygame.Rect): - subr = subrect - if subrect is not None: - subr = pygame.Rect(subrect) - if subr != self._subrect: - self._subrect = subr - if self._subrect is not None: - hashv = hash((subr.x, subr.y, subr.width, subr.height)) - surf_name = self._image_name + str(hashv) - if surf_name not in self._orig_surfs: - self._orig_surfs[surf_name] = loaders.images.load( - self.image).subsurface(subr) - self._orig_surf = self._orig_surfs[surf_name] - self._update_pos() - if (surf_name not in self._surfs) or (self._surfs[surf_name][1] != self._transform_cnt): - self._transform_surf() - self._surfs[surf_name] = (self._surf, self._transform_cnt) - self._surf = self._surfs[surf_name][0] - else: - self._orig_surf = self._surf = loaders.images.load(self.image) - self._update_pos() - self._transform_surf() - - @property - def orig_surf(self): - return self._orig_surf - - @orig_surf.setter - def orig_surf(self, surf: pygame.Surface): - self._orig_surf = self._surf = surf - self._update_pos() - self._transform_surf() - - def recalc(self): - self._surf = self._orig_surf - self._update_pos() - self._transform_surf() - - def _transform_surf(self): - self._surf = self._orig_surf - p = self.pos - - if self._scale != 1: - size = self._orig_surf.get_size() - self._surf = pygame.transform.scale( - self._surf, (int(size[0] * self.scale), int(size[1] * self.scale))) - if self._flip_x: - self._surf = pygame.transform.flip(self._surf, True, False) - if self._flip_y: - self._surf = pygame.transform.flip(self._surf, False, True) - - self._surf = pygame.transform.rotate(self._surf, self._angle) - - self.width, self.height = self._surf.get_size() - w, h = self._orig_surf.get_size() - ax, ay = self._untransformed_anchor - anchor = transform_anchor(ax, ay, w, h, self._angle) - self._anchor = (anchor[0] * self.scale, anchor[1] * self.scale) - - self.pos = p - self._mask = None - - def collidepoint_pixel(self, x, y=0): - if isinstance(x, tuple): - y = x[1] - x = x[0] - if self._mask == None: - self._mask = pygame.mask.from_surface(self._surf) - - xoffset = int(x - self.left) - yoffset = int(y - self.top) - if xoffset < 0 or yoffset < 0: - return 0 - - width, height = self._mask.get_size() - if xoffset >= width or yoffset >= height: - return 0 - - return self._mask.get_at((xoffset, yoffset)) - - def collide_pixel(self, actor): - for a in [self, actor]: - if a._mask == None: - a._mask = pygame.mask.from_surface(a._surf) - - xoffset = int(actor.left - self.left) - yoffset = int(actor.top - self.top) - - return self._mask.overlap(actor._mask, (xoffset, yoffset)) - - def collidelist_pixel(self, actors): - for i in range(len(actors)): - if self.collide_pixel(actors[i]): - return i - return -1 - - def collidelistall_pixel(self, actors): - collided = [] - for i in range(len(actors)): - if self.collide_pixel(actors[i]): - collided.append(i) - return collided - - def _unrotated_size(self): - w = self._orig_surf.get_width() * self.scale - h = self._orig_surf.get_height() * self.scale - return w, h - - @property - def collision_width(self): - if self._collision_width is None: - w, _ = self._unrotated_size() - return w - return self._collision_width - - @collision_width.setter - def collision_width(self, collision_width): - self._collision_width = collision_width - - @property - def collision_height(self): - if self._collision_height is None: - _, h = self._unrotated_size() - return h - return self._collision_height - - @collision_height.setter - def collision_height(self, collision_height): - self._collision_height = collision_height - - def obb_collidepoint(self, x, y): - w, h = self._unrotated_size() - return Collide.obb_point(self.centerx, self.centery, w, h, self._angle, x, y) - - def obb_collidepoints(self, points): - w, h = self._unrotated_size() - return Collide.obb_points(self.centerx, self.centery, w, h, self._angle, points) - - def obb_collideobb(self, actor): - if self._collision_width is None and self._collision_height is None: - x, y = self.centerx, self.centery - else: - x, y = self.x, self.y - - if actor._collision_width is None and actor._collision_height is None: - x2, y2 = actor.centerx, actor.centery - else: - x2, y2 = actor.x, actor.y - - return Collide.obb_obb(x, y, self.collision_width, self.collision_height, self._angle, - x2, y2, actor.collision_width, actor.collision_height, actor._angle) - - @property - def radius(self): - if self._radius is None: - w, h = self._unrotated_size() - self._radius = min(w, h) * .5 - return self._radius - - @radius.setter - def radius(self, radius): - self._radius = radius - - def circle_collidepoints(self, points): - return Collide.circle_points(self.centerx, self.centery, self._radius, points) - - def circle_collidepoint(self, x, y): - return Collide.circle_point(self.centerx, self.centery, self._radius, x, y) - - def circle_collidecircle(self, actor): - return Collide.circle_circle(self.centerx, self.centery, self._radius, actor.centerx, actor.centery, actor._radius) - - def circle_colliderect(self, actor): - return Collide.circle_rect(self.centerx, self.centery, self._radius, actor.centerx, actor.centery, actor.width, actor.height) - - def circle_collideobb(self, actor): - w2, h2 = actor._unrotated_size() - return Collide.obb_circle(actor.centerx, actor.centery, w2, h2, actor.angle, - self.centerx, self.centery, self._radius) - - def draw(self): - game.screen.blit(self._surf, self.topleft) - - def get_rect(self): - return self._rect - - def say_for_sec(self, text, seconds, fgcolor='black', bgcolor='white'): - tsurf, tpos = ptext.drawbox(text, (self.left, self.top - 60, 120, 50), - color=fgcolor, background=bgcolor) - pygame.display.update() - game.time.sleep(seconds) - tsurf.fill(bgcolor) - tsurf.set_alpha(255) # 255 means opaque - game.screen.blit(tsurf, tpos) - pygame.display.update() - - def __align_center_to_anchor(self): - if self.anchor == ('left', 'top'): - return self.topleft - elif self.anchor == ('left', 'middle'): - return self.midleft - elif self.anchor == ('left', 'bottom'): - return self.bottomleft - elif self.anchor == ('right', 'top'): - return self.topright - elif self.anchor == ('right', 'middle'): - return self.midright - elif self.anchor == ('right', 'bottom'): - return self.bottomright - elif self.anchor == ('middle', 'top'): - return self.midtop - elif self.anchor == ('middle', 'bottom'): - return self.midbottom - else: - return self.center - - def brush_init(self, size: Tuple[int, int], thick, color='white'): - self._tp_surf = pygame.Surface((size[0], size[1]), pygame.SRCALPHA) - self._brush_thick = thick - self._brush_color = color - self._tp_surf.set_alpha(255) - - def brush_draw(self): - pos = self.__align_center_to_anchor() - if self._last_pos and self._last_pos != pos: - pygame.draw.line(self._tp_surf, self._brush_color, - self._last_pos, pos, self._brush_thick) - game.screen.blit(self._tp_surf, (0, 0)) - self._last_pos = pos - - def brush_stop(self): - self._last_pos = None - - def brush_clear(self): - self._tp_surf.fill((255, 255, 255, 0)) - - def brush_update(self): - game.screen.blit(self._tp_surf, (0, 0)) diff --git a/mu/resources/pygamezero/player.png b/mu/resources/pygamezero/player.png new file mode 100644 index 000000000..a6c7f14bc Binary files /dev/null and b/mu/resources/pygamezero/player.png differ diff --git a/mu/resources/pygamezero/player_bullet.png b/mu/resources/pygamezero/player_bullet.png new file mode 100644 index 000000000..737e0847e Binary files /dev/null and b/mu/resources/pygamezero/player_bullet.png differ diff --git a/mu/resources/pygamezero/sfx_exp_medium12.wav b/mu/resources/pygamezero/sfx_exp_medium12.wav new file mode 100644 index 000000000..41e6afb70 Binary files /dev/null and b/mu/resources/pygamezero/sfx_exp_medium12.wav differ diff --git a/mu/resources/pygamezero/sfx_sounds_interaction25.wav b/mu/resources/pygamezero/sfx_sounds_interaction25.wav new file mode 100644 index 000000000..41a1d7a1b Binary files /dev/null and b/mu/resources/pygamezero/sfx_sounds_interaction25.wav differ diff --git a/mu/resources/pygamezero/space.jpg b/mu/resources/pygamezero/space.jpg new file mode 100644 index 000000000..70ea3593a Binary files /dev/null and b/mu/resources/pygamezero/space.jpg differ diff --git a/mu/resources/pygamezero/tank_blue.png b/mu/resources/pygamezero/tank_blue.png new file mode 100644 index 000000000..e3d5912dc Binary files /dev/null and b/mu/resources/pygamezero/tank_blue.png differ diff --git a/mu/resources/pygamezero/tank_dark.png b/mu/resources/pygamezero/tank_dark.png new file mode 100644 index 000000000..25b26a4d3 Binary files /dev/null and b/mu/resources/pygamezero/tank_dark.png differ diff --git a/mu/resources/pygamezero/tank_green.png b/mu/resources/pygamezero/tank_green.png new file mode 100644 index 000000000..4c90e4dcd Binary files /dev/null and b/mu/resources/pygamezero/tank_green.png differ diff --git a/mu/resources/pygamezero/tank_red.png b/mu/resources/pygamezero/tank_red.png new file mode 100644 index 000000000..ca3f95b2e Binary files /dev/null and b/mu/resources/pygamezero/tank_red.png differ diff --git a/mu/resources/pygamezero/tank_sand.png b/mu/resources/pygamezero/tank_sand.png new file mode 100644 index 000000000..cf08c3337 Binary files /dev/null and b/mu/resources/pygamezero/tank_sand.png differ diff --git a/mu/resources/pygamezero/treasure_box_o.png b/mu/resources/pygamezero/treasure_box_o.png new file mode 100644 index 000000000..c77e7fc04 Binary files /dev/null and b/mu/resources/pygamezero/treasure_box_o.png differ diff --git a/mu/resources/pygamezero/twinbee.py b/mu/resources/pygamezero/twinbee.py new file mode 100644 index 000000000..ab12adc00 --- /dev/null +++ b/mu/resources/pygamezero/twinbee.py @@ -0,0 +1,149 @@ +from pgzhelper import * +import random + +WIDTH = 800 +HEIGHT = 600 + +backgrounds = [] +background1 = Actor("background1", (WIDTH / 2, HEIGHT / 2)) +backgrounds.append(background1) +background2 = Actor("background2", (WIDTH / 2, (HEIGHT / 2) - HEIGHT)) +backgrounds.append(background2) +player = Actor("player", (400, 500)) + +MAX_BULLETS = 3 +bullets = [] + +enemies = [] +enemy_bullets = [] +explosions = [] + +score = 0 +game_over = False + +music.play('main_theme') + + +def move_player(): + if keyboard.right: + player.x += 5 + if keyboard.left: + player.x -= 5 + if keyboard.up: + player.y -= 5 + if keyboard.down: + player.y += 5 + if player.right > WIDTH: + player.right = WIDTH + if player.left < 0: + player.left = 0 + if player.bottom > HEIGHT: + player.bottom = HEIGHT + if player.top < 0: + player.top = 0 + + +def shoot_bullets(): + if keyboard.space and len(bullets) < MAX_BULLETS: + sounds.sfx_sounds_interaction25.play() + bullet_delay = 5 + bullet = Actor("player_bullet") + bullet.pos = player.pos + bullet.angle = 90 + bullets.append(bullet) + + for bullet in bullets: + bullet.move_forward(15) + if bullet.y < 0: + bullets.remove(bullet) + + +def create_enemies(): + if random.randint(0, 1000) > 980: + enemy = Actor("enemy1_1") + enemy.images = ["enemy1_1", "enemy1_2"] + enemy.fps = 5 + enemy.y = -50 + enemy.x = random.randint(100, WIDTH - 100) + enemy.direction = random.randint(-100, -80) + enemies.append(enemy) + + for enemy in enemies: + enemy.move_in_direction(4) + enemy.animate() + if enemy.top > HEIGHT: + enemies.remove(enemy) + if random.randint(0, 1000) > 990: + bullet = Actor("enemy_bullet") + bullet.pos = enemy.pos + bullet.angle = random.randint(0, 359) + enemy_bullets.append(bullet) + + for bullet in enemy_bullets: + bullet.move_forward(5) + if bullet.x < 0 or bullet.x > WIDTH or bullet.y < 0 or bullet.y > HEIGHT: + enemy_bullets.remove(bullet) + + + +def check_collision(): + global score, game_over + + for bullet in bullets: + enemy_index = bullet.collidelist(enemies) + if enemy_index != -1: + bullets.remove(bullet) + explosion = Actor("explosion1") + explosion.pos = enemies[enemy_index].pos + explosion.images = ["explosion1", "explosion2"] + explosion.fps = 8 + explosion.duration = 15 + explosions.append(explosion) + del enemies[enemy_index] +# enemies.remove(enemies[enemy_index]) + score += 1 + + for explosion in explosions: + explosion.animate() + explosion.duration -= 1 + if explosion.duration == 0: + explosions.remove(explosion) + + if player.collidelist(enemy_bullets) != -1 or player.collidelist(enemies) != -1: + game_over = True + + +def draw_text(): + screen.draw.text("Score " + str(score), (50, 0), color="black", fontsize=30) + if game_over: + screen.draw.text( + "Game over", midbottom=(WIDTH / 2, HEIGHT / 2), color="blue", fontsize=100 + ) + + +def draw(): + for background in backgrounds: + background.draw() + player.draw() + for enemy in enemies: + enemy.draw() + for bullet in bullets: + bullet.draw() + for explosion in explosions: + explosion.draw() + for bullet in enemy_bullets: + bullet.draw() + draw_text() + + +def update(): + for background in backgrounds: + background.y += 3 + if background.top > HEIGHT: + background.y = (HEIGHT / 2) - HEIGHT + + create_enemies() + if game_over == False: + move_player() + shoot_bullets() + check_collision() diff --git a/mu/resources/pygamezero/wall.png b/mu/resources/pygamezero/wall.png new file mode 100644 index 000000000..3251b8247 Binary files /dev/null and b/mu/resources/pygamezero/wall.png differ diff --git a/mu/resources/pygamezero/wall.wav b/mu/resources/pygamezero/wall.wav new file mode 100644 index 000000000..4c10eacd4 Binary files /dev/null and b/mu/resources/pygamezero/wall.wav differ diff --git a/mu/resources/pygamezero/win.wav b/mu/resources/pygamezero/win.wav new file mode 100644 index 000000000..bd2db8e6e Binary files /dev/null and b/mu/resources/pygamezero/win.wav differ diff --git a/mu/virtual_environment.py b/mu/virtual_environment.py index 093a83ade..faf6c565d 100644 --- a/mu/virtual_environment.py +++ b/mu/virtual_environment.py @@ -291,9 +291,20 @@ def run( params.extend(args) if slots.output is None: - result = self.process.run_blocking( - self.executable, params, wait_for_s=wait_for_s - ) + if command == "install": + venv_path = os.path.dirname(os.path.dirname(self.executable)) + exe = self.executable.replace("pip", "uv") + params.remove("--disable-pip-version-check") + result = self.process.run_blocking( + exe, + ["pip"] + params, + wait_for_s=wait_for_s, + VIRTUAL_ENV=venv_path, + ) + else: + result = self.process.run_blocking( + self.executable, params, wait_for_s=wait_for_s + ) logger.debug("Process output: %s", compact(result.strip())) return result else: @@ -886,6 +897,15 @@ def create_venv(self): ), ) ok, output = self.run_subprocess(*args, env=env) + args = filter( + None, + ( + os.path.join(self.path, "bin", "pip"), + "install", + "uv", + ), + ) + ok, output = self.run_subprocess(*args, env=env) if ok: logger.info( "Created virtual environment using %s at %s", @@ -951,6 +971,7 @@ def install_from_zipped_wheels(self, zipped_wheels_filepath): os.path.basename(wheel) ) ) + wheel = f"{os.path.basename(wheel).split('-')[0]}@{wheel}" self.pip.install(wheel, deps=False, index=False) def install_baseline_packages(self): diff --git a/mu/wheels/__init__.py b/mu/wheels/__init__.py index 0f9e20d5d..af4418f72 100644 --- a/mu/wheels/__init__.py +++ b/mu/wheels/__init__.py @@ -37,26 +37,18 @@ class WheelsBuildError(WheelsError): # Any additional elements are passed to `pip` for specific purposes # mode_packages = [ - # pygame is a pgzero dependency, but there is currently an issue where - # pygame versions >=2.1.3 have issues in macOS 10.x, so temporarily for - # Mu release 1.2.1 pin the max version here - # https://github.com/mu-editor/mu/issues/2423 - # Add pyinstller to package user your Pygame Zero game - ("pgzero", ("pgzero>=1.2.1", "pygame<2.1.3", "pyinstaller")), + ("pgzero", ("pgzero>=1.2.1", "pyinstaller")), # Lock Werkzeug to < 3.0.0: import flask fails, otherwise. ("flask", ("flask==2.0.3", "Werkzeug<3.0.0")), # The version of ipykernel here should match to the version used by # qtconsole at the version specified in setup.py - # FIXME: ipykernel max ver added for macOS 10.13 compatibility, min taken - # from qtconsole 4.7.7. This is mirrored in setup.py - ("ipykernel", ("ipykernel>=4.1,<6",)), - # FIXME: ipykernel<6 depends on ipython_genutils, but it isn't explicitly - # declared as a dependency. It also depends on traitlets, which - # incidentally brought ipython_genutils, but in v5.1 it was dropped, so as - # a workaround we need to manually specify it here - ("ipython_genutils", ("ipython_genutils>=0.2.0",)), + # ipykernel max ver added for macOS 10.13 compatibility, min taken + # from setup.py. This is version has to mirror the one from setup.py + ("ipykernel", ("ipykernel>=5.5.6,<6",)), # For Neopia mode - ("neopia", ("neopia>=0.2.3",)), + ("neopia", ("neopia>=0.3.6",)), + # For pgzero's extenstion + ("pgzhelper", ("pgzhelper_rw>=1.0.9",)), ] @@ -69,13 +61,14 @@ def os_compatibility_flags(): an issue to be resolved before doing a Mu release. """ extra_flags = [] - # For macOS the oldest supported version is 10.12 Sierra, as that's the - # oldest version supported by PyQt5 v5.13 + # For macOS the oldest supported version is 10.13 High Sierra, + # as that's the oldest version supported by PyQt5 v5.15 if sys.platform == "darwin": extra_flags.extend( [ - "--platform=macosx_10_12_x86_64", - "--only-binary=:all:", + # Comment out to fix a issue not to be able to install neopia >= 0.3.6 + # "--platform=macosx_10_15_x86_64", + # "--only-binary=:all:", ] ) # At the moment there aren't any additional flags for Windows or Linux diff --git a/setup.py b/setup.py index d3ed9ee51..487ee9c1f 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,7 @@ "flake8 >= 3.8.3", # Clamp click max version to workaround incompatibility with black<22.1.0 "click<=8.0.4", - "black>=19.10b0,<22.1.0;python_version>'3.5'", + "black>=19.10b0,<22.1.0", "platformdirs>=2.0.0,<3.0.0", "semver>=2.8.0", # virtualenv vendors pip, we need at least pip v19.3 to install some @@ -75,6 +75,9 @@ # Needed to resolve an issue with paths in the user virtual environment # "pywin32; sys_platform=='win32'", + # pkg_resources has been removed in Python 3.12, until we move to importlib + # we need it via setuptools: https://github.com/mu-editor/mu/issues/2485 + "setuptools", ] @@ -128,7 +131,7 @@ "mu.modes.api", "mu.wheels", ], - python_requires=">=3.7,<3.12", + python_requires=">=3.7,<3.13", install_requires=install_requires, extras_require=extras_require, package_data={"mu.wheels": ["*.whl", "*.zip"]}, @@ -152,6 +155,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Education", "Topic :: Games/Entertainment", "Topic :: Software Development", diff --git a/tests/interface/test_editor.py b/tests/interface/test_editor.py index daf0208b2..5ed827923 100644 --- a/tests/interface/test_editor.py +++ b/tests/interface/test_editor.py @@ -937,6 +937,7 @@ def test_EditorPane_toggle_comments_handle_crlf_newline(): """ ep = mu.interface.editor.EditorPane(None, "test\r\nline 2\n") ep.hasSelectedText = mock.MagicMock(return_value=False) + ep.setCursorPosition(0, 0) ep.toggle_comments() assert ep.text() == "# test\nline 2\n" assert ep.selectedText() == "# test" diff --git a/update_user_libs.py b/update_user_libs.py new file mode 100644 index 000000000..107a33af2 --- /dev/null +++ b/update_user_libs.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""Update the copy of user libs(pgzhelper, picozero ...) + +User libs is not yet packaged on PyPI; this script exists to mirror it into the +local repository to make it easily installable. + +""" +import json +import base64 +import subprocess +from urllib.parse import urljoin +from urllib.request import build_opener +import sys + +DEST = '' +PGZHELPER_DEST = 'mu/resources/pygamezero/pgzhelper.py' +PICOZERO_DEST = 'mu/resources/pico/picozero.py' +PGZHELPER_REPO_URL = 'https://api.github.com/repos/roboticsware/pgzhelper/' +PICOZERO_REPO_URL = 'https://api.github.com/repos/roboticsware/picozero/' +HEADER = '''""" + +This module is directly copied from + + https://github.com/roboticsware/pgzhelper + +at revision {sha} +and used under CC0. + +""" +# flake8: noqa: E501 +''' + + +# Customise the opener here if you need to +opener = build_opener() + + +def read_json(url): + """Download and decode a JSON resource from the given URL.""" + resp = opener.open(url) + charset = resp.headers.get_content_charset() + data = resp.read().decode(charset) + return json.loads(data) + + +def get_tree(file): + """Download the repository tree, returning a decoded JSON structure.""" + print('Downloading repository tree...') + + if file == 'pgzhelper.py': + REPO_URL = PGZHELPER_REPO_URL + elif file == 'picozero.py': + REPO_URL = PICOZERO_REPO_URL + + url = urljoin(REPO_URL, 'git/trees/HEAD?recursive=1') + return read_json(url) + + +def get_file(file): + """Download the tree state and named file. + + Return a tuple of the current repo version hash and the file's data. + + """ + tree = get_tree(file) + for f in tree['tree']: + if file in f['path']: + break + else: + raise ValueError("Could not find the module to download.") + + url = f['url'] + print('Downloading', file, 'module...') + blob = read_json(url) + data = base64.b64decode(blob['content']).decode('utf8') + return tree['sha'], data + + +def update_local(): + """Download a new copy of the file and write it to DEST. + + Include a header based on the template HEADER. + + """ + global DEST + FILE = sys.argv[1] + if FILE == 'pgzhelper.py': + DEST = PGZHELPER_DEST + elif FILE == 'picozero.py': + DEST = PICOZERO_DEST + + sha, data = get_file(FILE) + header = HEADER.format(sha=sha) + with open(DEST, 'w', encoding='utf8') as f: + f.write(header + data) + print("Updated", FILE, "to revision", sha[:7]) + autopep8() + + +def autopep8(): + """Use autopep8 to fix formatting problems.""" + print("Running autopep8") + subprocess.check_call(['autopep8', '-i', DEST]) + + +if __name__ == '__main__': + update_local() diff --git a/utils/mkapi.py b/utils/mkapi.py index 6ad185b1c..8eacc9940 100755 --- a/utils/mkapi.py +++ b/utils/mkapi.py @@ -2,6 +2,9 @@ """ Takes a JSON representation of an API and emits elements to be inserted into a Python list such that they conform to Scintilla's API description DSL. + +HOWTO: +python mkapi.py """ import sys import json @@ -19,5 +22,5 @@ name = i["name"] args = ", ".join(i["args"]) if i["args"] else "" description = i["description"].replace("\u2013", "--") - content = repr("{}({}) \n{}".format(name, args, description)) + content = repr("{}({}) \n\n{}".format(name, args, description)) print(" _({}),".format(content)) diff --git a/utils/pgzero_api.json b/utils/pgzero_api.json index 9da1eaff4..7614ac83c 100644 --- a/utils/pgzero_api.json +++ b/utils/pgzero_api.json @@ -1,86 +1,4 @@ [ - { - "name": "draw.circle", - "args": [ - "position", - "radius", - "(r, g, b)" - ], - "description": "Draw the outline of a circle." - }, - { - "name": "draw.filled_circle", - "args": [ - "position", - "radius", - "(r, g, b)" - ], - "description": "Draw a filled circle." - }, - { - "name": "draw.filled_rect", - "args": [ - "rect", - "(r, g, b)" - ], - "description": "Draw a filled rectangle. Takes a Rect object. For example, Rect((20, 20), (100, 100))" - }, - { - "name": "draw.line", - "args": [ - "start", - "end", - "(r, g, b)" - ], - "description": "Draw a line from start to end." - }, - { - "name": "draw.rect", - "args": [ - "rect", - "(r, g, b)" - ], - "description": "Draw the outline of a rectangle. Takes a Rect object. For example, Rect((20, 20), (100, 100))" - }, - { - "name": "draw.text", - "args": [ - "text", - "[pos, ]**kwargs" - ], - "description": "Draw text. There’s an extremely rich API for positioning and formatting text; see Pygame Zero Text Formatting for full details." - }, - { - "name": "draw.textbox", - "args": [ - "text", - "rect", - "**kwargs" - ], - "description": "Draw text, sized to fill the given Rect. There’s an extremely rich API for positioning and formatting text; see Pygame Zero Text Formatting for full details." - }, - { - "name": "screen.blit", - "args": [ - "image", - "(left, top)" - ], - "description": "Draw the image to the screen at the given position. blit() accepts either a Surface or a string as its image parameter. If image is a str then the named image will be loaded from the images/ directory." - }, - { - "name": "screen.clear", - "args": [ - null - ], - "description": "Reset the screen to black." - }, - { - "name": "screen.fill", - "args": [ - "(red, green, blue)" - ], - "description": "Fill the screen with a solid color." - }, { "name": "screen.Screen", "args": [ @@ -189,7 +107,7 @@ { "name": "music.set_volume", "args": null, - "description": "set_volume(value) -> None\nset the music volume" + "description": "set_volume(volume) -> None\nset the music volume" }, { "name": "music.stop", @@ -226,11 +144,16 @@ { "name": "keyboard.re", "args": null, - "description": "Support for regular expressions (RE).\n\nThis module provides regular expression matching operations similar to\nthose found in Perl. It supports both 8-bit and Unicode strings; both\nthe pattern and the strings being processed can contain null bytes and\ncharacters outside the US ASCII range.\n\nRegular expressions can contain both special and ordinary characters.\nMost ordinary characters, like \"A\", \"a\", or \"0\", are the simplest\nregular expressions; they simply match themselves. You can\nconcatenate ordinary characters, so last matches the string 'last'.\n\nThe special characters are:\n \".\" Matches any character except a newline.\n \"^\" Matches the start of the string.\n \"$\" Matches the end of the string or just before the newline at\n the end of the string.\n \"*\" Matches 0 or more (greedy) repetitions of the preceding RE.\n Greedy means that it will match as many repetitions as possible.\n \"+\" Matches 1 or more (greedy) repetitions of the preceding RE.\n \"?\" Matches 0 or 1 (greedy) of the preceding RE.\n *?,+?,?? Non-greedy versions of the previous three special characters.\n {m,n} Matches from m to n repetitions of the preceding RE.\n {m,n}? Non-greedy version of the above.\n \"\\\\\" Either escapes special characters or signals a special sequence.\n [] Indicates a set of characters.\n A \"^\" as the first character indicates a complementing set.\n \"|\" A|B, creates an RE that will match either A or B.\n (...) Matches the RE inside the parentheses.\n The contents can be retrieved or matched later in the string.\n (?aiLmsux) Set the A, I, L, M, S, U, or X flag for the RE (see below).\n (?:...) Non-grouping version of regular parentheses.\n (?P...) The substring matched by the group is accessible by name.\n (?P=name) Matches the text matched earlier by the group named name.\n (?#...) A comment; ignored.\n (?=...) Matches if ... matches next, but doesn't consume the string.\n (?!...) Matches if ... doesn't match next.\n (?<=...) Matches if preceded by ... (must be fixed length).\n (?...) The substring matched by the group is accessible by name.\n (?P=name) Matches the text matched earlier by the group named name.\n (?#...) A comment; ignored.\n (?=...) Matches if ... matches next, but doesn't consume the string.\n (?!...) Matches if ... doesn't match next.\n (?<=...) Matches if preceded by ... (must be fixed length).\n (?