Contribute
This guide explains how to contribute to the COMPAS ShapeOp project, including adding new constraints, forces, or improving the existing codebase.
Setting Up for Development
Fork the repository on GitHub
Clone your fork locally
Set up the development environment as described in Conda Environment
Create a new branch for your feature or bug fix
Binding Existing ShapeOp Constraints
Not all constraints from the ShapeOp C++ library are currently bound to Python in COMPAS ShapeOp. Here’s how to expose an existing ShapeOp constraint:
Identify the Constraint:
Locate the constraint class in the ShapeOp C++ library (typically in
ext/ShapeOp/src/Constraints.h)Understand the parameters required by its constructor and any additional methods it may have
Add a Method to SolverWrapper:
In
src/shapeop.cpp, add a new method to theSolverWrapperclass:
int add_your_constraint(nb::list indices, float weight = 1.0) { if (!is_valid()) { throw std::runtime_error("Invalid solver"); } // Convert Python list to std::vector<int> std::vector<int> ids; for (size_t i = 0; i < len(indices); i++) { ids.push_back(nb::cast<int>(indices[i])); } // Create the constraint using ShapeOp's factory auto constraint = ShapeOp::Constraint::shapeConstraintFactory( "YourConstraintType", ids, weight ); // Additional configuration if needed // constraint->setSpecificParameter(...); // Add to solver and return ID return solver->addConstraint(constraint); }
Expose the Method to Python:
In the
NB_MODULEsection at the bottom ofsrc/shapeop.cpp, add your method:
nb::class_<SolverWrapper>(m, "SolverWrapper") // ... existing methods .def("add_your_constraint", &SolverWrapper::add_your_constraint, nb::arg("indices"), nb::arg("weight") = 1.0) // ... more methods
Add Python Wrapper:
In
src/compas_shapeop/shapeop.py, add a method to theSolverclass:
def add_your_constraint(self, indices, weight=1.0): """Add a your_constraint constraint to the solver. Parameters ---------- indices : list[int] List of point indices to constrain. weight : float, optional Weight of the constraint, by default 1.0 Returns ------- int Constraint ID """ return self._solver.add_your_constraint(indices, weight)
Document in API:
Ensure your method has a proper docstring as shown above
Update API documentation in
docs/api/compas_shapeop.rstif needed
Build and Test:
Rebuild the extension:
pip install -e .Create a simple test example to verify the constraint works
Common ShapeOp constraints you might want to bind include TriangleStrainConstraint, TetrahedronStrainConstraint, AreaConstraint, or VolumeConstraint.
Adding New Constraints
To add a new constraint to COMPAS ShapeOp:
C++ Implementation:
Add the constraint class to
src/shapeop/custom_constraints/The class should inherit from
ShapeOp::ConstraintImplement required methods like
project()andaddConstraint()
Update C++ Binding:
Add a method to the
SolverWrapperclass insrc/shapeop.cppExpose the new method in the Python module definition
Python Wrapper:
Add a corresponding method to the
Solverclass insrc/compas_shapeop/shapeop.pyDocument the method with proper docstrings
Example: Adding a New Constraint
Here’s a simplified example of how to add a new constraint:
Create the C++ constraint implementation in
src/shapeop/custom_constraints/myconstraint.cpp:
#include "ShapeOp/Constraint.h"
namespace ShapeOp {
class MyConstraint : public Constraint {
public:
MyConstraint(const std::vector<int> &idI, Scalar weight)
: Constraint(idI, weight) {}
void project(Matrix3X &positions, Matrix3X &projections) override {
// Implement your constraint logic here
}
static std::shared_ptr<Constraint> create(const std::vector<int> &idI, Scalar weight) {
return std::make_shared<MyConstraint>(idI, weight);
}
};
} // namespace ShapeOp
Add the binding method to
SolverWrapperinsrc/shapeop.cpp:
// Add a new custom constraint
int add_my_constraint(nb::list indices, float weight = 1.0) {
if (!is_valid()) {
throw std::runtime_error("Invalid solver");
}
// Convert Python list to std::vector<int>
std::vector<int> ids;
for (size_t i = 0; i < len(indices); i++) {
int idx = nb::cast<int>(indices[i]);
ids.push_back(idx);
}
// Create the constraint
auto constraint = ShapeOp::MyConstraint::create(ids, weight);
return solver->addConstraint(constraint);
}
Expose the method in the Python module definition:
nb::class_<SolverWrapper>(m, "SolverWrapper")
// ... existing methods
.def("add_my_constraint", &SolverWrapper::add_my_constraint);
Add the Python wrapper in
src/compas_shapeop/shapeop.py:
def add_my_constraint(self, indices, weight=1.0):
"""Add a custom constraint to the solver.
Parameters
----------
indices : list
List of vertex indices to constrain.
weight : float, optional
Weight of the constraint, by default 1.0
Returns
-------
int
The ID of the newly added constraint.
"""
return self._solver.add_my_constraint(indices, weight)
Adding New Forces
The process for adding new forces is similar to adding constraints:
Create a C++ force implementation inheriting from
ShapeOp::ForceAdd binding methods to
SolverWrapperAdd Python wrapper methods to the
Solverclass
Testing Your Contributions
When adding new features, always include tests and examples:
Create an example script in the
examples/directoryAdd documentation for the new feature
Run linting checks with
invoke lintEnsure all existing tests pass with
invoke test
Pull Request Process
Push your changes to your fork
Create a pull request to the main repository
Describe your changes clearly in the PR description
Make sure your code follows the project’s style guidelines
Address any feedback from code reviewers
Style Guidelines
Follow the existing code style
Use clear, descriptive variable and function names
Document all public methods with docstrings
Keep PR scope focused on a single feature or bug fix
Write clean, maintainable code
Documentation
When adding new features, always update the documentation:
Add docstrings to all new methods
Update relevant tutorial sections or create new ones
Include examples that demonstrate the new functionality
Build and check the documentation with
invoke docs
For additional guidance, please reach out to the project maintainers or open an issue on GitHub.