# Contributing to LinkForge Thank you for your interest in contributing to LinkForge! This guide will help you get started. ## Table of Contents - [Code of Conduct](#code-of-conduct) - [Getting Started](#getting-started) - [Development Setup](#development-setup) - [Project Structure](#project-structure) - [Development Workflow](#development-workflow) - [Testing](#testing) - [Code Style](#code-style) - [Submitting Changes](#submitting-changes) - [Release Process](#release-process) - [Technical Considerations](#technical-considerations) - [Maintainer Expectations](#maintainer-expectations) - [Governance](#governance) - [Getting Help](#getting-help) - [Recognition](#recognition) ## Code of Conduct Be respectful, inclusive, and professional. We're all here to build great robotics tools together. ## Getting Started ### Prerequisites - **Python 3.11+** - **Blender 4.2+** - **Git** - **just** (Command Runner) - **macOS**: `brew install just` - **Linux**: `sudo apt install just` (or via `snap` / `cargo`) - **Windows**: `choco install just` or `scoop install just` - **Universal**: `cargo install just` (requires Rust) - **uv** (Python package manager) - Install: `curl -LsSf https://astral.sh/uv/install.sh | sh` ### Fork and Clone ```bash # Fork the repository on GitHub, then: git clone https://github.com/YOUR_USERNAME/linkforge.git cd linkforge ``` ## Development Setup ### 1. Install Dependencies ```bash # Install everything (deps + pre-commit hooks) just install ``` ### 2. Verify Setup ```bash # Run tests to verify everything works just test # Run linter and type checker just check # Build extension just build ``` ## Project Structure ``` linkforge/ ├── core/ # Core Robotics Logic (pip package) │ └── src/linkforge/core/ ├── platforms/ │ └── blender/ # Blender Extension (.zip) ├── tests/ # Test Suite (unit + integration) ├── examples/ # Example URDF/XACRO files └── pyproject.toml # Workspace config ``` See [ARCHITECTURE](https://linkforge.readthedocs.io/en/latest/explanation/ARCHITECTURE.html) for detailed architecture diagrams. ## Development Workflow ```mermaid graph LR A[Fork & Clone] --> B[Feature Branch] B --> C[Code & Test] C --> D[pre-commit Checks] D --> E[Pull Request] E --> F{Review} F -- Fixes --> C F -- Approved --> G[Merge to main] G --> H[Automated Release PR] H --> I[Tag & Release] ``` ### 1. Create a Feature Branch ```bash git checkout -b feature/your-feature-name ``` ### 2. Make Changes - Write code following our [Code Style](#code-style) - Add tests for new functionality - Update documentation as needed ### 3. Run Quality Checks ```bash # Run all tests (Core + Blender) just test # Run specific tiers just test-core # Fast core logic (unit + integration) just test-unit-blender # Mocked platform logic unit tests just test-integration-blender # Full Blender integration tests # Run linter and type checker just check # Fix linting issues automatically just fix ``` ### 4. Live Testing in Blender (Development Mode) LinkForge uses a **Development Junction** system to link your workspace directly into Blender without needing to build and re-install packages. ```bash # 1. Establish the link (Run once) just develop # 2. Open Blender # 3. Edit > Preferences > Extensions # 4. Search for "LinkForge" and Enable it ``` Any changes you make to the `.py` files in your editor will be instantly available in Blender (you only need to restart Blender or reload the extension). ### 5. Testing the Release (Optional) If you want to verify the final user experience (including bundled wheels): ```bash # 1. Build the production ZIPs just build # 2. In Blender: Edit > Preferences > Extensions # 3. Dropdown (⌄) > Install from Disk # 4. Select dist/linkforge-blender-x.x.x-macos_arm64.zip ``` ### 6. Commit Changes ```bash git add . git commit -s -m "feat: add your feature description" ``` **Commit Message Format:** - `feat:` New feature - `fix:` Bug fix - `docs:` Documentation changes - `test:` Test additions/changes - `refactor:` Code refactoring - `perf:` Performance improvements - `chore:` Maintenance tasks ## Testing ### Running Tests LinkForge uses a **Tiered Testing Architecture** to balance speed and fidelity: | Tier | Command | Scope | |---|---|---| | **Core** | `just test-core` | Models, Kinematics, Parsers (Zero-deps) | | **Logic** | `just test-unit-blender` | Blender Adapters & Math (Mocked `bpy`) | | **Integration** | `just test-integration-blender` | Real Scene Interaction (Real Blender) | See [Automated Testing](https://linkforge.readthedocs.io/en/latest/reference/testing/automated_testing.html) for our full strategy and coverage metrics. To run specific tests or categories: ```bash # Run with coverage (Combined report) just coverage # Run specific core test file (manual) uv run pytest tests/unit/core/test_robot.py # Run specific Blender integration test file (manual) uv run python scripts/blender_launcher.py tests/integration/platforms/blender/test_roundtrip.py ``` ### Manual QA (Mandatory) For features involving UI, viewport transforms, or complex exports, automated tests are not enough. Contributors **must** follow and pass the [Manual QA Protocol](https://github.com/arounamounchili/linkforge/blob/main/docs/testing/manual_qa.md) before submitting a PR. - **Objective**: Ensure export validity (URDF/XACRO standards), correct physics calculations, and UI stability across macOS, Windows, and Linux. - **Protocol**: Follow the 7-Phase checklist in `docs/testing/manual_qa.md`. ### Writing Tests #### Unit Test Example ```python def test_link_creation(): """Test creating a link with valid parameters.""" link = Link( name="test_link", visuals=[], collisions=[], inertial=Inertial(mass=1.0, inertia=InertiaTensor.stability_floor()) ) assert link.name == "test_link" assert link.inertial.mass == 1.0 ``` #### Round-Trip Test Example ```python def test_sensor_roundtrip(): """Test sensor origin survives import-export cycle.""" # Create robot with sensor robot = Robot( name="test", initial_links=[Link(name="base_link")], initial_sensors=[ Sensor( name="test_camera", type=SensorType.CAMERA, link_name="base_link", camera_info=CameraInfo(), origin=Transform(xyz=(0.1, 0.0, 0.2)) ) ] ) # Export to URDF generator = URDFGenerator() urdf = generator.generate(robot) # Re-import from linkforge.core import URDFParser robot2 = URDFParser().parse_string(urdf) # Verify sensor origin preserved assert robot2.sensors[0].origin == robot.sensors[0].origin ``` - **Unit Tests** (`tests/unit/`): Test individual functions/classes in isolation, split into `core/` and `platforms/`. - **Integration Tests** (`tests/integration/`): Test full workflows and round-trips, split into `core/` (logic, parsers) and `platforms/` (Blender-specific). > [!TIP] > **Use Central Fixtures**: Always use the `examples_dir` fixture from `tests/conftest.py` when accessing example URDFs. Avoid hardcoding relative paths like `../../examples`. We follow a **Tiered Mocking Strategy** based on the level of interaction: 1. **Platform Logic Tests** (`unit/platforms/`): **Prefer Mocks**. - For math and transformation logic in adapters, use `unittest.mock` to verify inputs/outputs. - This allows for sub-second developer loops without booting Blender. 2. **Integration Tests** (`integration/platforms/`): **Prefer Real Data**. - For scene manipulation and UI operators, use a real (headless) Blender instance. - Use `bpy.ops.object.empty_add()` to create real objects in your test fixtures. - Use the `blender_clean_scene` fixture to ensure environment isolation. 3. **Core Tests** (`unit/core/`): **Zero Dependencies**. - Instantiate real `Robot`, `Link`, or `Joint` objects. - **Mock only for external edge cases**: Filesystem failures or missing optional dependencies. ### Debugging in Blender ```python from linkforge.core import get_logger logger = get_logger(__name__) logger.error(f"Debug: {variable}") # View in Blender Console (Window > Toggle System Console) ``` ## Code Style ### Python Style Guide ### Blender Import Policy (Relative Imports Only) Blender 4.5+ enforces a strict sandbox for extensions. If the extension loads any module from within its own directory, Blender's policy checker strictly demands that the module name does not pollute the global namespace. Because of this, **you must use relative imports** for any intra-package imports inside the `linkforge.blender` extension layer: ```python # Good (Complies with Blender Sandbox) from ..core import RobotBuilder from . import utils # Bad (Will trigger a Blender Policy Violation) from linkforge.core import RobotBuilder ``` Tests and standalone scripts (outside of the Blender runtime) may continue to use absolute imports safely. ### Documentation Standards (Lean Google Style) We follow a **Lean Google Style** for documentation. Every module and class must be documented to provide high-level context and component breakdowns: 1. **Module Headers**: Summarize the purpose and categorize key components (Models, Validators, Utilities). 2. **Concise Summaries**: Focus on *intent* and *physical implications* rather than just restating the signature. 3. **Core Components**: Use a structured list in module docstrings to identify the main actors. ```python """Module title. Brief high-level summary of role in the IR. Core Components: - ComponentA: Brief role description. - ComponentB: Brief role description. """ ``` ### "Physics as Truth" Design Principle LinkForge follows a strict **Physics as Truth** philosophy. The Intermediate Representation (IR) is not just a data structure; it is a physical model that must remain stable and plausible at all times. - **Strict Validation**: All models (Link, Joint, Sensor) must implement `__post_init__` guardrails for physical properties (e.g., non-negative mass, normalized axes, valid LIDAR ranges). - **Orchestrated Checks**: Global consistency (cycles, connectivity, ros2_control limits) is enforced by the `RobotValidator`. - **Fail Fast**: We prefer a `RobotValidationError` at construction time over a physics engine crash at runtime. ```python # Good def parse_float(text: str | None, default: float | None = None) -> float: ... # Bad def parse_float(text, default=None): ... ``` ### Linting Configuration Our project rigorously enforces code quality using `ruff`. The definitive configuration (including line length, target version, and active rule sets) is maintained centrally in `pyproject.toml`. Please refer to the `[tool.ruff.lint]` section in that file for the current active and ignored rules. ### Pre-commit Hooks Pre-commit automatically runs on `git commit`: - `ruff check` - Linting (with auto-fix) - `ruff format` - Code formatting - `conventional-pre-commit` - Enforces conventional commit messages - `codespell` - Catches common typos in code and docs - Standard file checks - trailing whitespace, EOF newline, YAML/TOML/JSON validation, large files, merge conflicts, etc. ## Submitting Changes ### Developer Certificate of Origin (DCO) All contributions to this project must be accompanied by a Developer Certificate of Origin (DCO) sign-off. This ensures that you have the right to submit the code under the project's open-source license. To sign off on a commit, simply use the `-s` flag when committing: `git commit -s -m "Your descriptive commit message"` This will automatically add the following line to your commit message: `Signed-off-by: Your Name ` ### Pull Request Process 1. **Push your branch** ```bash git push origin feature/your-feature-name ``` 2. **Create Pull Request** - Go to GitHub and create a PR - Fill out the PR template - Link any related issues 3. **Check Pull Request Status** - Ensure the CI pipeline passes (GitHub Actions). - Once you create the PR, follow the automated checklist provided in the PR description template. This ensures all standards (tests, linting, quality) are met before maintainer review. 4. **Code Review** - Address review comments - Keep commits clean and logical - Squash commits if requested 5. **Merge** - Maintainer will merge once approved - Your contribution will be in the next release! ### PR Title Format Use conventional commits: - `feat: Add support for new sensor type` - `fix: Correct sensor origin export` - `docs: Update architecture diagrams` - `test: Add round-trip tests for transmissions` ## Release Process LinkForge uses **Release Please** to automate versioning and changelogs. 1. **Automation**: When code is merged into `main`, Release Please will automatically create (or update) a "Release PR". 2. **Versioning**: This PR will contain a version bump in `pyproject.toml`, `blender_manifest.toml`, `CITATION.cff`, and an updated `CHANGELOG.md` based on your commit messages. 3. **Merging**: Once a maintainer merges this Release PR, a GitHub Tag and Release are automatically created. 4. **Distribution**: The `release-please.yml` workflow will then build the extension `.zip` and attach it to the GitHub Release. ### Release Standards To maintain a professional and consistent appearance: - **Tagging**: Always use simple `v` prefixed tags (e.g., `v1.3.0`). This is handled automatically by the configuration. - **Release Titles**: Follow the format `v1.x.x: [Major Highlight]`. For example: `v1.3.0: Performance & Control Refinement`. - **Changelog**: Use the project's custom sections (🚀 Features, 🐞 Bug Fixes, etc.) to keep notes organized. > [!NOTE] > This is why **Conventional Commits** are required. Without them, the release automation cannot determine if a change should bump the MAJOR, MINOR, or PATCH version. ## Technical Considerations To maintain LinkForge's status as a professional-grade **Linter & Bridge**, we prioritize stability in three key areas: ### 1. The Blender Bridge (Foundation) LinkForge must remain compatible with the latest Blender LTS (Long Term Support) and the current stable release. - **Vigilance**: When a new Blender version (e.g., 6.0) enters Beta, we prioritize testing our Blender integration to ensure no API breaking changes affect our users. ### 2. URDF/XACRO Fidelity (Core) Our primary goal is 100% compliance with official specifications. - **Cross-Simulator Support**: We ensure that generated files work seamlessly across Gazebo (Classic & Sim), Webots, Isaac Sim, and MuJoCo. - **Precision**: Physics calculations (inertia tensors) must remain scientifically accurate, as they are the "brain" of the exported robot. ### 3. The ROS 2 Ecosystem (Integration) While LinkForge supports `ros2_control`, it is designed to be distribution-agnostic where possible. - **Compatibility**: We target compatibility with active ROS 2 LTS distributions (like Humble and Jazzy) and Rolling. - **Maintenance**: If the official `ros2_control` XML syntax changes in a newer ROS version, we update our generators to support those changes while maintaining backward compatibility. ### 4. Validation Extensibility (Quality Control) To maintain the highest standards of robot description quality, LinkForge uses a **Modular Validation Registry**. - **The Rule**: Every new core model feature must be accompanied by a corresponding `ValidationCheck` in `core/src/linkforge/core/validation/checks.py`. - **Implementation**: Inherit from `ValidationCheck`, implement the `run()` method, and append your class to `RobotValidator.DEFAULT_CHECKS`. - **Testing**: Add isolated unit tests for your new check in `tests/unit/core/test_validation_checks.py`. ## Maintainer Expectations As a maintainer-driven project, we aim to be transparent about our availability and response times: - **Review Cycle**: Expect an initial response or feedback on PRs within **7 business days**. - **Issue Triage**: New issues are usually triaged during our weekly maintenance window (typically weekends). - **Issue Assignment & Progress**: - If you are interested in an issue, please comment to ask for assignment. - We value momentum. If an assigned issue has no activity (comments or PR) for **14 days**, we will check in. - After **21 days** of inactivity, the issue may be unassigned to allow other contributors to help. - **Public Communication**: We prefer all discussions to happen in public issues or GitHub Discussions to ensure the community benefits from the shared knowledge. - **Decision Making**: For major architectural changes, we follow a consensus-based approach among core maintainers. If you are proposing a large change, please open a Discussion first to get early feedback. ## Governance LinkForge is currently maintained by @arounamounchili and the community. - **Core Maintainers**: Responsible for architectural oversight and final merging. - **Contributors**: Anyone who submits code, documentation, or feedback. - **Recognition**: We follow the [All Contributors](https://allcontributors.org/) specification to recognize all types of contributions. ## Getting Help - **Documentation**: Check the [Official Documentation](https://linkforge.readthedocs.io/) and [Architecture Guide](https://linkforge.readthedocs.io/en/latest/explanation/ARCHITECTURE.html) - **Issues**: Search [existing issues](https://github.com/arounamounchili/linkforge/issues) - **Discussions**: Ask in [GitHub Discussions](https://github.com/arounamounchili/linkforge/discussions) ## Recognition We value every contribution! To keep things simple for maintainers, **contributor recognition is fully automated**: - **`README.md`**: Handled by the `@all-contributors` bot. - **`CHANGELOG.md` & Release Notes**: Automated via `release-please` based on commit messages. - **GitHub Contributors**: Managed automatically by GitHub's platform. ### All Contributors Bot We use the [@all-contributors](https://allcontributors.org/) bot to automatically recognize all types of contributions. **How to get recognized:** If you've contributed (code, docs, ideas, etc.), you can ask the bot to add you by commenting on an issue or pull request: ```text @all-contributors please add @username for ``` Replace `` with one of the [valid contribution types](https://allcontributors.org/docs/en/emoji-key) (e.g., `code`, `doc`, `bug`, etc.). ### Academic Recognition For profound core contributions (e.g., advanced mathematical noise models, novel simulator integrations, or deep physics engine refinements), we may invite you to be listed as a co-author in the `CITATION.cff` file and the official documentation. This ensures your high-level domain expertise is properly attributed in future academic research using LinkForge. *(Note: Standard features, basic sensor additions, and bug fixes are highly valued and will be celebrated via our standard Open Source contributors framework, but they do not automatically qualify for academic co-authorship).* Thank you for contributing to LinkForge!