Skip to content

Commit cab2b92

Browse files
Add binding for Controller::draw() and drawTools() (#532)
* Add binding for Controller::draw() and drawTools() So it become possible to draw from python. * Add drawFrames, drawLines, drawPoints and an example * Add drawFrame(Data<std::vector<Rigid3Coord>>) So we can print all the dof of a mechanical object without copy * Cosmetic improvement of Binding_VisualParams * Add binding for DrawTool::drawText * [Sofa.Types] Add RGBAColor binding & tests * FIXUP * Separate DrawTool from VisualParam * Add support for non Vec3d arguments. * Update example and add BaseData support for drawLines * Add enable/disableLighting, drawDisk, drawCircle, drawTriangles, drawQuads * improve example. * add variation on drawTriangle/drawQuads * Reset python version 3.7 * Add documentation
1 parent 462fd5a commit cab2b92

18 files changed

+710
-12
lines changed

bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
// Imports for getCategories
3030
#include <sofa/core/objectmodel/ContextObject.h>
3131
#include <sofa/core/visual/VisualModel.h>
32+
#include <sofa/core/visual/VisualParams.h>
3233
#include <sofa/core/BaseMapping.h>
3334
#include <sofa/core/BehaviorModel.h>
3435
#include <sofa/core/CollisionModel.h>

bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject_doc.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ static auto Class =
4545
root.obj.position.value # Access the position of the object
4646
4747
)";
48+
static auto draw =
49+
R"(
50+
Implement this function to draw an object in the 3D view.
51+
)";
52+
4853
static auto init =
4954
R"(
5055
Initialization method called at graph creation and modification, during top-down traversal.Initialize data.

bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
#include <pybind11/pybind11.h>
2222
#include <pybind11/cast.h>
23-
23+
#include <sofa/core/visual/VisualParams.h>
2424
#include <SofaPython3/Sofa/Core/Binding_Base.h>
2525
#include <SofaPython3/Sofa/Core/Binding_Controller.h>
2626
#include <SofaPython3/Sofa/Core/Binding_Controller_doc.h>
@@ -45,6 +45,13 @@ std::string Controller_Trampoline::getClassName() const
4545
return py::str(py::cast(this).get_type().attr("__name__"));
4646
}
4747

48+
void Controller_Trampoline::draw(const sofa::core::visual::VisualParams* params)
49+
{
50+
PythonEnvironment::executePython(this, [this, params](){
51+
PYBIND11_OVERLOAD(void, Controller, draw, params);
52+
});
53+
}
54+
4855
void Controller_Trampoline::init()
4956
{
5057
PythonEnvironment::executePython(this, [this](){
@@ -131,6 +138,9 @@ void moduleAddController(py::module &m) {
131138

132139
f.def("init", &Controller::init);
133140
f.def("reinit", &Controller::reinit);
141+
f.def("draw", [](Controller& self, sofa::core::visual::VisualParams* params){
142+
self.draw(params);
143+
}, pybind11::return_value_policy::reference);
134144
}
135145

136146

bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Controller.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class Controller_Trampoline : public Controller
4343

4444
void init() override;
4545
void reinit() override;
46+
void draw(const sofa::core::visual::VisualParams* params) override;
47+
4648
void handleEvent(sofa::core::objectmodel::Event* event) override;
4749

4850
std::string getClassName() const override;
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/******************************************************************************
2+
* SOFA, Simulation Open-Framework Architecture *
3+
* (c) 2021 INRIA, USTL, UJF, CNRS, MGH *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: [email protected] *
19+
******************************************************************************/
20+
21+
#include <sofa/type/Quat.h>
22+
#include <pybind11/pybind11.h>
23+
#include <pybind11/pytypes.h>
24+
#include <pybind11/stl.h>
25+
26+
#include <SofaPython3/Sofa/Core/Binding_Base.h>
27+
#include <sofa/core/visual/VisualParams.h>
28+
29+
#include <SofaPython3/Sofa/Core/Binding_DrawTool.h>
30+
#include <SofaPython3/Sofa/Core/Binding_DrawTool_doc.h>
31+
32+
#include <sofa/core/topology/BaseMeshTopology.h>
33+
34+
#include <SofaPython3/PythonFactory.h>
35+
#include <sofa/core/objectmodel/Data.h>
36+
#include <sofa/type/RGBAColor.h>
37+
38+
namespace py { using namespace pybind11; }
39+
using sofa::core::objectmodel::BaseData;
40+
using sofa::core::objectmodel::Data;
41+
using sofa::core::objectmodel::BaseObject;
42+
using sofa::core::visual::VisualParams;
43+
using sofa::core::visual::DrawTool;
44+
45+
namespace sofapython3 {
46+
47+
sofa::type::vector<sofa::type::Vec3> getPoints(const py::array_t<double>& array)
48+
{
49+
py::buffer_info buf = array.request();
50+
51+
if (buf.ndim != 2)
52+
throw std::runtime_error("Invalid argument, expecting an array with ndim=2");
53+
54+
size_t rows = buf.shape[0];
55+
size_t cols = buf.shape[1];
56+
57+
double* ptr = static_cast<double*>(buf.ptr);
58+
59+
std::vector<sofa::type::Vec3d> points;
60+
points.resize(rows);
61+
for (size_t i = 0; i < rows; ++i)
62+
for (size_t j = 0; j < 3; ++j)
63+
points[i][j] = ptr[i * cols + j];
64+
65+
return points;
66+
}
67+
68+
sofa::type::vector<sofa::type::Quatd> getOrientations(const py::array_t<double>& array)
69+
{
70+
py::buffer_info buf = array.request();
71+
72+
if (buf.ndim != 2)
73+
throw std::runtime_error("Invalid argument, expecting an array with ndim=2");
74+
75+
size_t rows = buf.shape[0];
76+
size_t cols = buf.shape[1];
77+
78+
double* ptr = static_cast<double*>(buf.ptr);
79+
80+
std::vector<sofa::type::Quatd> orientations;
81+
orientations.resize(rows);
82+
for (size_t i = 0; i < rows; ++i)
83+
for (size_t j = 0; j < cols; ++j)
84+
orientations[i][j] = ptr[i * cols + j];
85+
86+
return orientations;
87+
}
88+
89+
90+
void moduleAddDrawTool(py::module &m)
91+
{
92+
py::class_<DrawTool> dt(m, "DrawTool", sofapython3::doc::drawtool::baseDrawToolClass);
93+
94+
// Draw points
95+
dt.def("drawPoints", [](DrawTool *self, py::array_t<double> points, float size, sofa::type::RGBAColor& color)
96+
{
97+
self->drawPoints(getPoints(points), size, color);
98+
}, sofapython3::doc::drawtool::drawPoints);
99+
dt.def("drawPoints", [](DrawTool *self, BaseData* dpositions, float size, sofa::type::RGBAColor& color){
100+
auto positions = dynamic_cast<Data<sofa::type::vector<sofa::type::Vec3>>*>(dpositions);
101+
if(!positions)
102+
throw std::runtime_error("Invalid argument, a base data of type vector<Vec3> was expected, got "+dpositions->getValueTypeString());
103+
104+
self->drawPoints(positions->getValue(), size, color);
105+
});
106+
107+
/// Draw lines
108+
dt.def("drawLines", [](DrawTool *self, const py::array_t<double>& positions, const float size, sofa::type::RGBAColor& color){
109+
self->drawLines(getPoints(positions), size, color);
110+
}, sofapython3::doc::drawtool::drawLines);
111+
dt.def("drawLines", [](DrawTool *self, BaseData* dpositions, const float size, sofa::type::RGBAColor& color){
112+
auto positions = dynamic_cast<Data<sofa::type::vector<sofa::type::Vec3>>*>(dpositions);
113+
if(!positions)
114+
throw std::runtime_error("Invalid argument, expecting a vector<Rigid3> or vector<Vec3>, got "+dpositions->getValueTypeString());
115+
116+
self->drawLines(positions->getValue(), size, color);
117+
});
118+
119+
// Draw disk
120+
dt.def("drawDisk", [](DrawTool *self, float radius, double from, double to, int resolution, sofa::type::RGBAColor& color) {
121+
self->drawDisk(radius, from, to, resolution, color);
122+
});
123+
dt.def("drawCircle", [](DrawTool *self, float radius, float lineThickness, int resolution, sofa::type::RGBAColor& color) {
124+
self->drawCircle(radius, lineThickness, resolution, color);
125+
});
126+
127+
// Draw mesh
128+
dt.def("drawTriangles", [](DrawTool *self, py::array_t<double>& positions, sofa::type::RGBAColor& color){
129+
self->drawTriangles(getPoints(positions), color);
130+
}, sofapython3::doc::drawtool::drawTriangles);
131+
dt.def("drawTriangles", [](DrawTool *self, BaseData* dpositions, BaseData* dtriangles, sofa::type::RGBAColor& color){
132+
auto positions = dynamic_cast<Data<sofa::type::vector<sofa::type::Vec3d>>*>(dpositions);
133+
if(!positions)
134+
throw std::runtime_error("Invalid argument, expecting a vector<Rigid3> or vector<Vec3>, got "+dpositions->getValueTypeString());
135+
136+
auto triangles = dynamic_cast<Data<sofa::type::vector<sofa::topology::Triangle>>*>(dtriangles);
137+
if(!triangles)
138+
throw std::runtime_error("Invalid argument, expecting vector<Triangle>, got "+dtriangles->getValueTypeString());
139+
140+
auto& cpos = positions->getValue();
141+
auto& ctris = triangles->getValue();
142+
143+
std::vector<sofa::type::Vec3> tripos;
144+
tripos.resize(ctris.size()*3);
145+
146+
for(auto& ctri : ctris)
147+
{
148+
tripos.emplace_back(cpos[ctri[0]]);
149+
tripos.emplace_back(cpos[ctri[1]]);
150+
tripos.emplace_back(cpos[ctri[2]]);
151+
}
152+
153+
self->drawTriangles(tripos, color);
154+
});
155+
156+
// Draw mesh
157+
dt.def("drawQuads", [](DrawTool *self, py::array_t<double>& positions, sofa::type::RGBAColor& color){
158+
self->drawQuads(getPoints(positions), color);
159+
}, sofapython3::doc::drawtool::drawQuads);
160+
dt.def("drawQuads", [](DrawTool *self, BaseData* dpositions, BaseData* dquads, sofa::type::RGBAColor& color){
161+
auto positions = dynamic_cast<Data<sofa::type::vector<sofa::type::Vec3d>>*>(dpositions);
162+
if(!positions)
163+
throw std::runtime_error("Invalid argument, expecting a vector<Rigid3> or vector<Vec3>, got "+dpositions->getValueTypeString());
164+
165+
auto quads = dynamic_cast<Data<sofa::type::vector<sofa::topology::Quad>>*>(dquads);
166+
if(!quads)
167+
throw std::runtime_error("Invalid argument, expecting vector<Quad>, got "+dquads->getValueTypeString());
168+
169+
auto& cpos = positions->getValue();
170+
auto& ctris = quads->getValue();
171+
172+
std::vector<sofa::type::Vec3> quadpos;
173+
quadpos.resize(ctris.size()*4);
174+
175+
for(auto& ctri : ctris)
176+
{
177+
quadpos.emplace_back(cpos[ctri[0]]);
178+
quadpos.emplace_back(cpos[ctri[1]]);
179+
quadpos.emplace_back(cpos[ctri[2]]);
180+
quadpos.emplace_back(cpos[ctri[3]]);
181+
}
182+
183+
self->drawQuads(quadpos, color);
184+
});
185+
186+
187+
// Draw spheres
188+
dt.def("drawSpheres", [](DrawTool *self, const py::array_t<double>& positions, const std::vector<float>& radius, sofa::type::RGBAColor& color){
189+
self->drawSpheres(getPoints(positions), radius, color);
190+
}, sofapython3::doc::drawtool::drawSpheres);
191+
dt.def("drawSpheres", [](DrawTool *self, BaseData* dpositions, const float radius, sofa::type::RGBAColor& color){
192+
auto positions = dynamic_cast<Data<sofa::type::vector<sofa::type::Vec3>>*>(dpositions);
193+
if(!positions)
194+
throw std::runtime_error("Invalid argument, expecting a vector<Rigid3> or vector<Vec3>, got "+dpositions->getValueTypeString());
195+
self->drawSpheres(positions->getValue(), radius, color);
196+
});
197+
198+
// Draw boundingBox
199+
dt.def("drawBoundingBox", [](DrawTool *self, const std::array<double,4>& min, const std::array<double, 4>& max, double width){
200+
sofa::type::Vec3d cmin { min[0], min[1], min[2] };
201+
sofa::type::Vec3d cmax { max[0], max[1], max[2] };
202+
self->drawBoundingBox( cmin, cmax, width);
203+
}, sofapython3::doc::drawtool::drawBoundingBox);
204+
205+
// Draw frames
206+
dt.def("drawFrames", [](DrawTool* self,
207+
const py::array_t<double>& points,
208+
const py::array_t<double>& orientations,
209+
const std::array<double,3>& size){
210+
auto cpoints = getPoints(points);
211+
auto corientations = getOrientations(orientations);
212+
sofa::type::Vec3 csize {size[0],size[1],size[2]};
213+
for(unsigned int i=0;i<cpoints.size();i++)
214+
{
215+
self->drawFrame(cpoints[i], corientations[i], csize);
216+
}
217+
}, sofapython3::doc::drawtool::drawFrames);
218+
dt.def("drawFrames", [](DrawTool* self, BaseData* dpositions, std::array<double, 3>& size ){
219+
using sofa::defaulttype::Rigid3Types;
220+
using Coord = sofa::defaulttype::Rigid3Types::Coord;
221+
auto positions = dynamic_cast<Data<sofa::type::vector<Coord>>*>(dpositions);
222+
sofa::type::Vec3 csize {size[0],size[1],size[2]};
223+
if(!positions)
224+
throw std::runtime_error("Invalid argument");
225+
226+
for(auto& position : positions->getValue())
227+
{
228+
self->drawFrame(Rigid3Types::getCPos(position),
229+
Rigid3Types::getCRot(position), csize);
230+
}
231+
});
232+
233+
dt.def("enableLighting", [](DrawTool* self){ self->enableLighting(); });
234+
dt.def("disableLighting", [](DrawTool* self){ self->disableLighting(); });
235+
236+
// Draw text
237+
dt.def("drawText", [](DrawTool* self,
238+
const std::array<double,3>& point,
239+
const float size,
240+
const std::string& text,
241+
const sofa::type::RGBAColor& color)
242+
{
243+
self->draw3DText(point, size, color, text.c_str());
244+
}, sofapython3::doc::drawtool::drawText);
245+
246+
// Draw overlay text
247+
dt.def("drawOverlayText", [](DrawTool* self, const std::array<double,2>& point,
248+
int fontSize, char* text, sofa::type::RGBAColor& color){
249+
self->writeOverlayText(point[0],point[1], fontSize, color, text);
250+
}, sofapython3::doc::drawtool::drawOverlayText);
251+
252+
}
253+
254+
} /// namespace sofapython3
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/******************************************************************************
2+
* SOFA, Simulation Open-Framework Architecture *
3+
* (c) 2021 INRIA, USTL, UJF, CNRS, MGH *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: [email protected] *
19+
******************************************************************************/
20+
#pragma once
21+
22+
#include <pybind11/pybind11.h>
23+
24+
namespace sofapython3 {
25+
26+
void moduleAddDrawTool(pybind11::module &m);
27+
28+
} /// namespace sofapython3
29+

0 commit comments

Comments
 (0)