From 91a656c3b1d8a3cb3c97dd8e66acd4d3b755a97a Mon Sep 17 00:00:00 2001 From: godardma Date: Fri, 3 Jul 2026 11:44:52 +0200 Subject: [PATCH 1/3] [graphics] solving IPE figure not clearing ( closes #383 ) --- src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp index 38c465bbf..140e1a4b3 100644 --- a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp +++ b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp @@ -190,6 +190,7 @@ void Figure2D_IPE::clear() // clear _color map and layers _colors.clear(); _layers.clear(); + _items.clear(); init_figure(); } From ec5e3d23bc4942c254e2d2bc1ce08aecd7a1006c Mon Sep 17 00:00:00 2001 From: godardma Date: Fri, 3 Jul 2026 14:31:01 +0200 Subject: [PATCH 2/3] [graphics] save function for Figure2D ( closes #377 ) --- doc/manual/manual/visualization/figures.rst | 3 ++- examples/00_graphics/graphic_animation.cpp | 21 ++++++++++++++++++ examples/00_graphics/graphic_animation.py | 22 ++++++++++++++++++- .../graphics/figures/codac2_py_Figure2D.cpp | 4 ++++ src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp | 3 +++ src/graphics/3rd/ipe/codac2_Figure2D_IPE.h | 7 ++++++ .../3rd/vibes/codac2_Figure2D_VIBes.cpp | 5 +++++ .../3rd/vibes/codac2_Figure2D_VIBes.h | 7 ++++++ src/graphics/figures/codac2_Figure2D.cpp | 6 +++++ src/graphics/figures/codac2_Figure2D.h | 18 +++++++++++++++ src/graphics/figures/codac2_OutputFigure2D.h | 7 ++++++ 11 files changed, 101 insertions(+), 2 deletions(-) diff --git a/doc/manual/manual/visualization/figures.rst b/doc/manual/manual/visualization/figures.rst index f146017eb..95a525a95 100644 --- a/doc/manual/manual/visualization/figures.rst +++ b/doc/manual/manual/visualization/figures.rst @@ -156,4 +156,5 @@ VIBes only Some methods are exclusive to the real-time display with VIBes : - center_viewbox : takes two Vector as arguments, the center and radius of each axis -- auto_scale : takes no argument, the figure will be automatically scaled to fit the window \ No newline at end of file +- auto_scale : takes no argument, the figure will be automatically scaled to fit the window +- save : takes the name of the file as argument and saves the current state of the VIBes window to the file. Allowed formats are png, jpg, bmp and svg. \ No newline at end of file diff --git a/examples/00_graphics/graphic_animation.cpp b/examples/00_graphics/graphic_animation.cpp index 1b567d086..6f357e68c 100644 --- a/examples/00_graphics/graphic_animation.cpp +++ b/examples/00_graphics/graphic_animation.cpp @@ -21,4 +21,25 @@ int main() theta += 2.0 * M_PI / (double) steps; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } + + Figure2D fig_save ("Animation with exports",GraphicOutput::VIBES|GraphicOutput::IPE); + fig_save.set_window_properties({600,50},{500,500}); // position, window size + fig_save.set_axes(axis(0,{-0.5,4}), axis(1,{-0.5,1.5})); + + Vector X({0,0,0}); + + ColorMap cmap = ColorMap::rainbow(); + for (double t = 0; t < 2.*PI; t+=PI/20.) + { + fig_save.draw_tank(X, 0.05, cmap.color(t/(2.*PI))); + X+=Vector({0.1*cos(X[2]),0.1*sin(X[2]),0.1*cos(t)}); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if (Interval(PI).inflate(0.1).contains(t)) + { + fig_save.save("animation_tank.png"); + fig_save.save("animation_tank.jpg"); + fig_save.save("animation_tank.bmp"); + fig_save.save("animation_tank.svg"); + } + } } \ No newline at end of file diff --git a/examples/00_graphics/graphic_animation.py b/examples/00_graphics/graphic_animation.py index 69955e528..24dfff9e7 100644 --- a/examples/00_graphics/graphic_animation.py +++ b/examples/00_graphics/graphic_animation.py @@ -1,4 +1,5 @@ from codac import * +import math import time fig = Figure2D("Animation", GraphicOutput.VIBES | GraphicOutput.IPE) @@ -11,4 +12,23 @@ fig.clear() fig.draw_point([5*cos(theta).mid(), 5*sin(theta).mid()], Color.red()) theta += 2*PI / steps - time.sleep(0.1) \ No newline at end of file + time.sleep(0.1) + +fig_save = Figure2D("Animation with exports", GraphicOutput.VIBES | GraphicOutput.IPE) +fig_save.set_window_properties([600,50],[500,500]) # position, window size +fig_save.set_axes(axis(0,[-0.5,4]), axis(1,[-0.5,1.5])) + +X = Vector([0,0,0]) +t = 0 +cmap = ColorMap.rainbow() + +while t <2*PI: + fig_save.draw_tank(X, 0.05, cmap.color(t/(2*PI))) + X+=Vector([0.1*math.cos(X[2]),0.1*math.sin(X[2]),0.1*math.cos(t)]) + time.sleep(0.1) + if (Interval(PI).inflate(0.1).contains(t)): + fig_save.save("animation_tank.png") + fig_save.save("animation_tank.jpg") + fig_save.save("animation_tank.bmp") + fig_save.save("animation_tank.svg") + t += PI/20 \ No newline at end of file diff --git a/python/src/graphics/figures/codac2_py_Figure2D.cpp b/python/src/graphics/figures/codac2_py_Figure2D.cpp index 41b26324c..e78d90819 100644 --- a/python/src/graphics/figures/codac2_py_Figure2D.cpp +++ b/python/src/graphics/figures/codac2_py_Figure2D.cpp @@ -109,6 +109,10 @@ void export_Figure2D(py::module& m) .def("clear", &Figure2D::clear, VOID_FIGURE2D_CLEAR) + + .def("save", &Figure2D::save, + VOID_FIGURE2D_SAVE_CONST_STRING_REF, + "filename"_a) .def("scaled_unit", &Figure2D::scaled_unit, DOUBLE_FIGURE2D_SCALED_UNIT_CONST) diff --git a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp index 140e1a4b3..62a6a94cd 100644 --- a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp +++ b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp @@ -195,6 +195,9 @@ void Figure2D_IPE::clear() init_figure(); } +void Figure2D_IPE::save(const std::string& filename) +{} + std::string ipe_str(const Color& c) { return c.hex_str().substr(1); diff --git a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.h b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.h index 23d0e15f6..ddfeba394 100644 --- a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.h +++ b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.h @@ -72,6 +72,13 @@ namespace codac2 */ void clear(); + /** + * \brief Saves the figure to a file + * + * \param filename Name of the file to save the figure to + */ + void save(const std::string& filename); + /** * \brief Begins a new path in the IPE file * diff --git a/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.cpp b/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.cpp index eb3e40059..aeb59a32b 100644 --- a/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.cpp +++ b/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.cpp @@ -93,6 +93,11 @@ void Figure2D_VIBes::clear() _layers.clear(); } +void Figure2D_VIBes::save(const std::string& filename) +{ + vibes::saveImage(filename, _fig.name()); +} + void Figure2D_VIBes::draw_point(const Vector& c, const StyleProperties& style) { assert(_fig.size() <= c.size()); diff --git a/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.h b/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.h index 04572df9c..f780f544a 100644 --- a/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.h +++ b/src/graphics/3rd/vibes/codac2_Figure2D_VIBes.h @@ -65,6 +65,13 @@ namespace codac2 * \brief Clears the figure */ void clear(); + + /** + * \brief Saves the figure to a file + * + * \param filename Name of the file to save the figure to + */ + void save(const std::string& filename); // Geometric shapes diff --git a/src/graphics/figures/codac2_Figure2D.cpp b/src/graphics/figures/codac2_Figure2D.cpp index 15c9503c8..2658e715b 100644 --- a/src/graphics/figures/codac2_Figure2D.cpp +++ b/src/graphics/figures/codac2_Figure2D.cpp @@ -111,6 +111,12 @@ void Figure2D::clear() output_fig->clear(); } +void Figure2D::save(const std::string& filename) +{ + for(const auto& output_fig : _output_figures) + output_fig->save(filename); +} + double Figure2D::scaled_unit() const { return std::max(_axes[0].limits.diam(),_axes[1].limits.diam()) / _window_size.max_coeff(); diff --git a/src/graphics/figures/codac2_Figure2D.h b/src/graphics/figures/codac2_Figure2D.h index 3b920c734..ec637baa8 100644 --- a/src/graphics/figures/codac2_Figure2D.h +++ b/src/graphics/figures/codac2_Figure2D.h @@ -182,6 +182,13 @@ namespace codac2 */ void clear(); + /** + * \brief Saves the figure to a file + * + * \param filename Name of the file to save the figure to + */ + void save(const std::string& filename); + /** * \brief Getter for the scaling factor of the figure * @@ -715,6 +722,17 @@ namespace codac2 selected_fig()->clear(); } + /** + * \brief Saves the figure to a file + * + * \param filename Name of the file to save the figure to + */ + static void save(const std::string& filename) + { + auto_init(); + selected_fig()->save(filename); + } + // Geometric shapes /** diff --git a/src/graphics/figures/codac2_OutputFigure2D.h b/src/graphics/figures/codac2_OutputFigure2D.h index a83ba57e4..a4157949c 100644 --- a/src/graphics/figures/codac2_OutputFigure2D.h +++ b/src/graphics/figures/codac2_OutputFigure2D.h @@ -73,6 +73,13 @@ namespace codac2 * \brief Clears the figure */ virtual void clear() = 0; + + /** + * \brief Saves the figure to a file + * + * \param filename Name of the file to save the figure to + */ + virtual void save(const std::string& filename) = 0; protected: From 3212d3ad38e8488b41f9aad9d01398b8500f77b0 Mon Sep 17 00:00:00 2001 From: godardma Date: Tue, 7 Jul 2026 16:52:45 +0200 Subject: [PATCH 3/3] [graphics] dot detection for IPE file save --- src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp | 39 ++++++++++---------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp index 62a6a94cd..86313cdf2 100644 --- a/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp +++ b/src/graphics/3rd/ipe/codac2_Figure2D_IPE.cpp @@ -12,11 +12,14 @@ #include "codac2_Figure2D_IPE.h" #include "codac2_math.h" +// TO DELETE +#include + using namespace std; using namespace codac2; Figure2D_IPE::Figure2D_IPE(const Figure2D& fig) - : OutputFigure2D(fig), _f(fig.name() + ".xml"), + : OutputFigure2D(fig), _x_offset(0.03*_fig.axes()[0].limits.diam()), _y_offset(0.03*_fig.axes()[1].limits.diam()) { @@ -25,23 +28,7 @@ Figure2D_IPE::Figure2D_IPE(const Figure2D& fig) Figure2D_IPE::~Figure2D_IPE() { - draw_axes(); - print_header_page(); - _f.close(); - - _f = std::ofstream(_fig.name() + ".xml", std::ofstream::binary | std::ofstream::app); - std::ifstream f_temp_content(_fig.name() + "_tmp.xml", std::ofstream::binary); - - for (const auto& item : _items) - _f << item.second; - - f_temp_content.close(); - std::remove((_fig.name() + "_tmp.xml").c_str()); - _f.close(); - - _f = std::ofstream(_fig.name() + ".xml", std::ofstream::app); - _f << "\n\n"; - _f.close(); + save(_fig.name()+"."); } void Figure2D_IPE::init_figure() @@ -172,6 +159,8 @@ void Figure2D_IPE::update_axes() _ipe_grid_size/(_fig.axes()[0].limits.diam()+_x_offset), _ipe_grid_size/(_fig.axes()[1].limits.diam()+_y_offset) }; + + draw_axes(); } void Figure2D_IPE::update_window_properties() @@ -196,7 +185,19 @@ void Figure2D_IPE::clear() } void Figure2D_IPE::save(const std::string& filename) -{} +{ + std::size_t dot_position = filename.find_last_of('.'); + std::string file_name = filename.substr(0, dot_position); + _f = std::ofstream(file_name + ".xml", std::ofstream::binary | std::ofstream::app); + + print_header_page(); + + for (const auto& item : _items) + _f << item.second; + + _f << "\n\n"; + _f.close(); +} std::string ipe_str(const Color& c) {