### Delete Old Example Folders Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt This function removes previously downloaded example directories to prepare for a fresh download. It targets specific paths within the user's Allplan installation. Use with caution as it permanently deletes files. ```python def delete_folder(): # Removes old example directories before a fresh download for folder in [ os.path.join(USR_PATH, "Library\Examples\PythonParts"), os.path.join(USR_PATH, "PythonPartsExampleScripts"), ]: if os.path.exists(folder): shutil.rmtree(folder) ``` -------------------------------- ### Download Python Parts Examples from GitHub Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt This class handles the synchronous download of Python Part examples from a GitHub repository. It checks for existing downloads, prompts for user consent, and updates the local files. Ensure Allplan is running to use this functionality. ```python class DownloadPythonParts(BaseScriptObject): def __init__(self, _build_ele, script_object_data): super().__init__(script_object_data) self.coord_input.InitFirstElementInput( AllplanIFW.InputStringConvert("Download Python Part Examples")) self.execute_download() # runs synchronously on tool activation self.coord_input.CancelInput() @staticmethod def execute_download(): owner, repo = 'NemetschekAllplan', 'PythonPartsExamples' branch = AllplanSettings.AllplanVersion.MainReleaseName() # e.g. "2026" sha_file = f"{AllplanSettings.AllplanPaths.GetUsrPath()}PythonPartsExamples-sha.txt" old_sha = None # Load previously downloaded commit SHA (if any) if os.path.exists(sha_file): with open(sha_file, encoding="UTF-8") as f: old_sha = f.read() # First-time download: ask for consent if old_sha is None: if AllplanUtil.ShowMessageBox( "Download PythonPart examples from GitHub?\nProceed?", AllplanUtil.MB_YESNO) == AllplanUtil.IDNO: return # Fall back to 'main' branch if release branch doesn't exist if not GithubUtil.check_github_branch_exists(owner, repo, branch): branch = "main" data = GithubUtil.get_last_commit_info(owner, repo, branch) new_sha = data.get("sha", None) # Skip if already up-to-date (unless user forces update) if old_sha and new_sha == old_sha: if AllplanUtil.ShowMessageBox( "Examples appear up-to-date. Force update?", AllplanUtil.MB_YESNO) == AllplanUtil.IDNO: return # Delete old examples, download fresh ZIP, extract GithubUtil.delete_folder() GithubUtil.download_github_repo_as_zip(owner, repo, branch) if new_sha: with open(sha_file, encoding="UTF-8", mode="w") as f: f.write(new_sha) # persist SHA for future up-to-date checks AllplanUtil.ShowMessageBox( "Download completed.\nLibrary -> Private -> Examples -> PythonParts.", AllplanUtil.MB_OK) ``` -------------------------------- ### Install Python Package using pip Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt Installs a PyPI package into Allplan's isolated site-packages directory. Selectable installation targets (USR, STD, ETC) allow for user-specific or system-wide availability. Requires Allplan's bundled Python executable and pip. ```python # Allplan calls on_control_event() when the user clicks "Install" in the palette. HKCU = winreg.HKEY_CURRENT_USER ENV = "Environment" PATH = "PYTHONPATH" def on_control_event(build_ele: BuildingElement, event_id: int) -> bool: prg_path = AllplanSettings.AllplanPaths.GetPrgPath() + "\" package = build_ele.package.value or "" if not package: return False build_ele.package.value = "" # clear the input field immediately # Resolve the installation target directory path = AllplanSettings.AllplanPaths.GetUsrPath() # default: USR if build_ele.installLocation.value == "STD": path = AllplanSettings.AllplanPaths.GetStdPath() if build_ele.installLocation.value == "ETC": path = AllplanSettings.AllplanPaths.GetEtcPath() target_dir = f"{path}PythonParts-site-packages" # Example: install debugpy into USR\PythonParts-site-packages # Equivalent shell command: # C:\Allplan\2026\Prg\Python\Python.exe -m pip install --target # "C:\Users\\Allplan\2026\Usr\PythonParts-site-packages" debugpy --no-cache-dir try: subprocess.check_call([ prg_path + "Python\Python.exe", "-m", "pip", "install", "--target", target_dir, package, "--no-cache-dir" ]) AllplanUtil.ShowMessageBox( f"{package} installed successfully. Log in Trace window.", AllplanUtil.MB_OK) except Exception: AllplanUtil.ShowMessageBox( f"Installation of {package} failed. Error log in Trace window.", AllplanUtil.MB_OK) return True # signals Allplan to refresh the palette ``` -------------------------------- ### Start Python Debugger in Allplan Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt This script attaches the debugpy debugger to the Allplan process, enabling remote debugging from an IDE like VS Code. It checks for debugpy installation and port availability before starting the listener. Use this to debug PythonPart scripts live within Allplan. ```python import importlib import socket from AllplanBaseElements import BaseInteractor from AllplanUtil import AllplanUtil from PythonParts import AllplanSettings from PythonParts.Util import DebugUtil # Allplan invokes create_interactor() when the tool is activated from the task area. # The interactor checks for debugpy, checks port 5678, and shows the palette. # --- Entry point called by Allplan --- def create_interactor(coord_input, _pyp_path, global_str_table_service, build_ele_list, build_ele_composite, control_props_list, _modify_uuid_list): return StartDebugInteractor(coord_input, global_str_table_service, build_ele_list, build_ele_composite, control_props_list) class StartDebugInteractor(BaseInteractor): def __init__(self, coord_input, global_str_table_service, build_ele_list, build_ele_composite, control_props_list): self.build_ele = build_ele_list[0] # Check debugpy is installed; warn and disable button if not try: debugpy = importlib.import_module('debugpy') except ImportError: AllplanUtil.ShowMessageBox( "'debugpy' is not installed!\n\nUse 'Install Python Package' to install it.", AllplanUtil.MB_OK) self.control_props_util.set_enable_condition("StartListenButton", "False") return # Reflect current connection state in the palette if self.is_port_in_use(5678): if debugpy.is_client_connected(): self.build_ele.CurrentState.value = self.build_ele.CONNECTED self.show_prompt("IDE is already connected") else: self.build_ele.CurrentState.value = self.build_ele.LISTENING self.show_prompt("Listening for connection from IDE") else: self.show_prompt("Press 'start listening' button on the palette") def on_control_event(self, event_id: int) -> bool: # Called when the user clicks "Start Listening" on the palette if event_id == self.build_ele.START_LISTEN: if DebugUtil.start_debugger(False): self.build_ele.CurrentState.value = self.build_ele.CONNECTED self.show_prompt("Connected successfully. You can close this tool.") self.palette_service.update_palette(-1, False) else: AllplanUtil.ShowMessageBox("Couldn't connect to client", AllplanUtil.MB_OK) return True return False @staticmethod def is_port_in_use(port: int) -> bool: # Returns True if the port is already bound (debugpy already running) with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: try: s.bind(("localhost", port)) except OSError: return True return False def create_links_to_online_docs(self): # Generates versioned documentation URLs for the current Allplan release allplan_version = AllplanSettings.AllplanVersion.MainReleaseName() if int(allplan_version) > 3000: allplan_version = "WIP" self.build_ele.DebugDocButton.value = ( f"https://pythonparts.allplan.com/{allplan_version}/manual/for_developer/debugging/") self.build_ele.GettingStartedDocButton.value = ( f"https://pythonparts.allplan.com/{allplan_version}/manual/getting_started/") # Corresponding VS Code launch config written by CreateVisualStudioCodeWorkspace: # { # "name": "Attach to Allplan", # "connect": { "port": 5678, "host": "localhost" }, ``` -------------------------------- ### Create VS Code Workspace Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt Generates a .code-workspace file for Visual Studio Code, resolving Allplan installation paths and configuring IntelliSense, Pylint, and debugpy. Ensure Allplan is installed and accessible. ```python class CreateWorkspace: def execute(self): file_name = self.build_ele_list[0].FileButton.value if file_name in {None, "", "Select Folder"}: return False file_name, _ = os.path.splitext(file_name) file_name = file_name + ".code-workspace" # Template with $prg$, $etc$, $usr$, $std$ placeholders text = r'''{ "folders": [ { "name": "PythonPartsFramework", "path": "$etc$\PythonPartsFramework" }, { "name": "PythonPartsExampleScripts", "path": "$usr$\PythonPartsExampleScripts" }, { "name": "PythonParts", "path": "$usr$\Library\Examples\PythonParts" } ], "settings": { "python.languageServer": "Pylance", "pylint.args": ["--rcfile=$etc$PythonPartsFramework\pylintrc"], "python.autoComplete.extraPaths": [ "$prg$", "$etc$PythonPartsFramework\GeneralScripts", "$etc$PythonPartsFramework", "$etc$PythonParts-site-packages", "$usr$PythonParts-site-packages", "$std$PythonParts-site-packages" ], "python.defaultInterpreterPath": "$prg$\Python\python.exe", "python.analysis.stubPath": "$etc$PythonPartsFramework\InterfaceStubs" }, "extensions": { "recommendations": [ "ms-python.python", "ms-python.pylint", "Allplan.PythonPartTools", "redhat.vscode-xml" ] }, "launch": { "version": "0.2.0", "configurations": [{ "name": "Attach to Allplan", "connect": { "port": 5678, "host": "localhost" }, "request": "attach", "type": "debugpy", "justMyCode": false }] } }''' # Resolve Allplan paths at generation time prg_path = AllplanSettings.AllplanPaths.GetPrgPath().replace("\", "\\\\") etc_path = AllplanSettings.AllplanPaths.GetEtcPath().replace("\", "\\\\") usr_path = AllplanSettings.AllplanPaths.GetUsrPath().replace("\", "\\\\") std_path = AllplanSettings.AllplanPaths.GetStdPath().replace("\", "\\\\") text = (text.replace("$prg$", prg_path) .replace("$etc$", etc_path) .replace("$usr$", usr_path) .replace("$std$", std_path)) try: with codecs.open(file_name, "w") as file: file.write(text) AllplanUtil.ShowMessageBox("Workspace created Successfully", AllplanUtil.MB_OK) except Exception: AllplanUtil.ShowMessageBox( "An Error Occurred, please see stack trace for more info.", AllplanUtil.MB_OK) # Result: MyProject.code-workspace — open with "File > Open Workspace from File" in VS Code ``` -------------------------------- ### Check GitHub Branch Existence Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt This utility function checks if a specific branch exists in a GitHub repository. It makes a GET request to the GitHub API and returns `True` if the status code is 200 (OK), indicating the branch exists. ```python def check_github_branch_exists(owner: str, repo: str, branch: str) -> bool: url = f'https://api.github.com/repos/{owner}/{repo}/branches/{branch}' response = requests.get(url) return response.status_code == 200 ``` -------------------------------- ### Get Last Commit Information from GitHub Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt Retrieves the latest commit information for a given repository and branch from the GitHub API. It returns a dictionary containing details such as the commit SHA, author, and date. Ensure proper error handling for the API request. ```python def get_last_commit_info(owner: str, repo: str, branch: str) -> dict: url = f'https://api.github.com/repos/{owner}/{repo}/commits/{branch}' response = requests.get(url) response.raise_for_status() return response.json() # includes "sha", "commit", "author", etc. ``` -------------------------------- ### GitHub Actions Release Pipeline for Allplan Plugins Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt This workflow automates the creation of a distributable `.allep` file and publishes a draft GitHub Release when a git tag is pushed. It runs on `windows-latest` for path compatibility. ```yaml # .github/workflows/release.yml name: Release Allep on: push: tags: - "*" # triggers on any tag push, e.g. v2.0.1 jobs: build: runs-on: windows-latest permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v3 # Zip the four required directories + config files into PythonPartsSDK.allep - name: Create allep file uses: vimtor/action-zip@v1.1 with: files: Library/ PythonPartsScripts/ PythonPartsActionbar/ install-config.yml requirements.in recursive: false dest: PythonPartsSDK.allep # Create a draft release with the .allep as a downloadable asset - name: Create release draft env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | $tag = "${{ github.ref_name }}" gh release create "$tag" --title="$tag" --draft PythonPartsSDK.allep ``` -------------------------------- ### Encrypt PythonPart Script using AllplanUtil.EncryptString Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt Encrypts PythonPart source files (.py) into Allplan's encrypted format (.pye) using the AllplanUtil.EncryptString API. Supports single file or recursive directory encryption. Includes interactive file-browser dialogs for source and target path selection. ```python def on_control_event(build_ele: BuildingElement, event_id: int) -> bool: # event_id 1001: open file picker for a single .py file # event_id 1002: open directory picker for a source folder # event_id 1003: open directory picker for the target output folder # event_id 1010: perform encryption if event_id == 1010: if build_ele.FileOrDirectory.value == 1: # Encrypt a single file encrypt_file(build_ele.File.value, build_ele.TargetDirectory.value) else: # Encrypt an entire directory recursively encrypt_directory(build_ele.Directory.value, build_ele.TargetDirectory.value) BuildingElementService.write_data_to_default_favorite_file([build_ele]) AllplanUtil.ShowMessageBox( "File(s) successfully encrypted, log available in trace window", AllplanUtil.MB_OK) return True def encrypt_file(file_name: str, target_path: str): # Reads a UTF-8 .py source file, encrypts it with Allplan's API, # and writes the result as a .pye file in target_path. with open(file_name, "r", encoding="utf_8_sig") as file: code = file.read() base_name = os.path.basename(file_name) # AllplanUtil.EncryptString performs Allplan's proprietary encryption code = AllplanUtil.EncryptString(code, base_name.rsplit(".", 1)[-1]) base_name = base_name.replace(".py", ".pye") with open(target_path + "\" + base_name, "w", encoding="utf_8") as file: file.write(code) def encrypt_directory(directory_name: str, target_path: str): # Recursively walks directory_name, encrypting each .py file. # Subdirectories are created in target_path as needed. for file_name in glob.glob(directory_name + "\*"): if file_name.endswith("__pycache__"): continue if file_name.endswith(".py"): encrypt_file(file_name, target_path) elif os.path.isdir(file_name): sub_target_path = target_path + "\" + os.path.basename(file_name) if not os.path.exists(sub_target_path): os.makedirs(sub_target_path) encrypt_directory(file_name, sub_target_path) # Input: C:\MyScripts\WallCreator.py # Output: C:\MyScripts\encrypted\WallCreator.pye (Allplan-encrypted content) ``` -------------------------------- ### Reload Module and Dependencies Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt This code shows how to reload a specific module and all of its tracked dependencies in the correct order. This enables hot-reloading workflows during PythonPart development without needing to restart Allplan. Ensure `reloader.enable()` has been called previously. ```python # ... develop and edit MyPythonPartModule.py and its dependencies ... # Reload the module and all its tracked dependencies in correct order reloader.reload(MyPythonPartModule) # MyPythonPartModule's imports are re-executed; transitive deps reload first. ``` -------------------------------- ### Enable Dependency Tracking for Module Reloading Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt This code snippet demonstrates how to enable a dependency-tracking module reloader. Call `reloader.enable()` once at startup or when debugging begins, specifying any modules to exclude from tracking. This is crucial for hot-reloading PythonParts during development. ```python import reloader import MyPythonPartModule # Enable dependency tracking (call once at startup or when debugging begins) reloader.enable(blacklist=["NemAll_Python_Geometry", "NemAll_Python_Utility"]) ``` -------------------------------- ### Download GitHub Repository as ZIP Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt This function downloads a specified GitHub repository as a ZIP archive and extracts its contents. It handles API requests, checks for errors, and extracts files, excluding certain directory structures and markdown files. Ensure the `requests` and `zipfile` libraries are available. ```python def download_github_repo_as_zip(owner: str, repo: str, branch: str = 'main'): url = f'https://github.com/{owner}/{repo}/archive/refs/heads/{branch}.zip' response = requests.get(url) response.raise_for_status() with zipfile.ZipFile(BytesIO(response.content), 'r') as zip_ref: for x, info in zip_ref.NameToInfo.items(): # Skip root folder entry, markdown files, and .git artifacts if (x.count("/") < 2 and x[-1] == "/") or x.endswith(".md") or x.startswith(".git"): continue # Strip the leading repo-name directory component info.filename = "/".join(info.filename.split("/")[1:]) zip_ref.extract(member=info, path=USR_PATH) ``` -------------------------------- ### Disable Reloader and Inspect Dependencies Source: https://context7.com/nemetschekallplan/pythonparts-sdk/llms.txt Use `reloader.disable()` to restore standard builtins.__import__. Inspect `_DEPENDENCIES` to understand module relationships. `reloader.clear_visited()` resets cycle detection before reloading. ```python reloader.disable() # --- Manual dependency inspection --- from reloader import _DEPENDENCIES, _NAME_MODNAME # _DEPENDENCIES maps parent module name -> list of dependent module objects # Example: {"MyPythonPartModule": [, ]} print(_DEPENDENCIES) # clear_visited() resets the cycle-prevention set between reload passes reloader.clear_visited() reloader.reload(MyPythonPartModule) # safe to call again after clearing ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.