Merge branch 'lm_arrange_wipe_tower' of https://github.com/prusa3d/Slic3r
This commit is contained in:
commit
f0da19446b
9 changed files with 246 additions and 111 deletions
|
@ -539,7 +539,7 @@ public:
|
||||||
// 2D shape from top view.
|
// 2D shape from top view.
|
||||||
using ShapeData2D = std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
|
using ShapeData2D = std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
|
||||||
|
|
||||||
ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
|
ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& wti) {
|
||||||
ShapeData2D ret;
|
ShapeData2D ret;
|
||||||
|
|
||||||
// Count all the items on the bin (all the object's instances)
|
// Count all the items on the bin (all the object's instances)
|
||||||
|
@ -600,6 +600,28 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The wipe tower is a separate case (in case there is one), let's duplicate the code
|
||||||
|
if (wti.is_wipe_tower) {
|
||||||
|
Points pts;
|
||||||
|
pts.emplace_back(coord_t(scale_(0.)), coord_t(scale_(0.)));
|
||||||
|
pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(0.)));
|
||||||
|
pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(wti.bb_size(1))));
|
||||||
|
pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(wti.bb_size(1))));
|
||||||
|
pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(0.)));
|
||||||
|
Polygon p(std::move(pts));
|
||||||
|
ClipperLib::Path clpath = Slic3rMultiPoint_to_ClipperPath(p);
|
||||||
|
ClipperLib::Polygon pn;
|
||||||
|
pn.Contour = clpath;
|
||||||
|
// Efficient conversion to item.
|
||||||
|
Item item(std::move(pn));
|
||||||
|
item.rotation(wti.rotation),
|
||||||
|
item.translation({
|
||||||
|
ClipperLib::cInt(wti.pos(0)/SCALING_FACTOR),
|
||||||
|
ClipperLib::cInt(wti.pos(1)/SCALING_FACTOR)
|
||||||
|
});
|
||||||
|
ret.emplace_back(nullptr, item);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,7 +630,8 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
|
||||||
void applyResult(
|
void applyResult(
|
||||||
IndexedPackGroup::value_type& group,
|
IndexedPackGroup::value_type& group,
|
||||||
Coord batch_offset,
|
Coord batch_offset,
|
||||||
ShapeData2D& shapemap)
|
ShapeData2D& shapemap,
|
||||||
|
WipeTowerInfo& wti)
|
||||||
{
|
{
|
||||||
for(auto& r : group) {
|
for(auto& r : group) {
|
||||||
auto idx = r.first; // get the original item index
|
auto idx = r.first; // get the original item index
|
||||||
|
@ -617,18 +640,25 @@ void applyResult(
|
||||||
// Get the model instance from the shapemap using the index
|
// Get the model instance from the shapemap using the index
|
||||||
ModelInstance *inst_ptr = shapemap[idx].first;
|
ModelInstance *inst_ptr = shapemap[idx].first;
|
||||||
|
|
||||||
// Get the transformation data from the item object and scale it
|
// Get the transformation data from the item object and scale it
|
||||||
// appropriately
|
// appropriately
|
||||||
auto off = item.translation();
|
auto off = item.translation();
|
||||||
Radians rot = item.rotation();
|
Radians rot = item.rotation();
|
||||||
|
|
||||||
Vec3d foff(off.X*SCALING_FACTOR + batch_offset,
|
Vec3d foff(off.X*SCALING_FACTOR + batch_offset,
|
||||||
off.Y*SCALING_FACTOR,
|
off.Y*SCALING_FACTOR,
|
||||||
inst_ptr->get_offset()(Z));
|
inst_ptr ? inst_ptr->get_offset()(Z) : 0.);
|
||||||
|
|
||||||
// write the transformation data into the model instance
|
if (inst_ptr) {
|
||||||
inst_ptr->set_rotation(Z, rot);
|
// write the transformation data into the model instance
|
||||||
inst_ptr->set_offset(foff);
|
inst_ptr->set_rotation(Z, rot);
|
||||||
|
inst_ptr->set_offset(foff);
|
||||||
|
}
|
||||||
|
else { // this is the wipe tower - we will modify the struct with the info
|
||||||
|
// and leave it up to the called to actually move the wipe tower
|
||||||
|
wti.pos = Vec2d(foff(0), foff(1));
|
||||||
|
wti.rotation = rot;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -714,6 +744,7 @@ BedShapeHint bedShape(const Polyline &bed) {
|
||||||
// The final client function to arrange the Model. A progress indicator and
|
// The final client function to arrange the Model. A progress indicator and
|
||||||
// a stop predicate can be also be passed to control the process.
|
// a stop predicate can be also be passed to control the process.
|
||||||
bool arrange(Model &model, // The model with the geometries
|
bool arrange(Model &model, // The model with the geometries
|
||||||
|
WipeTowerInfo& wti, // Wipe tower info
|
||||||
coord_t min_obj_distance, // Has to be in scaled (clipper) measure
|
coord_t min_obj_distance, // Has to be in scaled (clipper) measure
|
||||||
const Polyline &bed, // The bed geometry.
|
const Polyline &bed, // The bed geometry.
|
||||||
BedShapeHint bedhint, // Hint about the bed geometry type.
|
BedShapeHint bedhint, // Hint about the bed geometry type.
|
||||||
|
@ -726,7 +757,7 @@ bool arrange(Model &model, // The model with the geometries
|
||||||
bool ret = true;
|
bool ret = true;
|
||||||
|
|
||||||
// Get the 2D projected shapes with their 3D model instance pointers
|
// Get the 2D projected shapes with their 3D model instance pointers
|
||||||
auto shapemap = arr::projectModelFromTop(model);
|
auto shapemap = arr::projectModelFromTop(model, wti);
|
||||||
|
|
||||||
// Copy the references for the shapes only as the arranger expects a
|
// Copy the references for the shapes only as the arranger expects a
|
||||||
// sequence of objects convertible to Item or ClipperPolygon
|
// sequence of objects convertible to Item or ClipperPolygon
|
||||||
|
@ -796,7 +827,7 @@ bool arrange(Model &model, // The model with the geometries
|
||||||
if(result.empty() || stopcondition()) return false;
|
if(result.empty() || stopcondition()) return false;
|
||||||
|
|
||||||
if(first_bin_only) {
|
if(first_bin_only) {
|
||||||
applyResult(result.front(), 0, shapemap);
|
applyResult(result.front(), 0, shapemap, wti);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
const auto STRIDE_PADDING = 1.2;
|
const auto STRIDE_PADDING = 1.2;
|
||||||
|
@ -806,7 +837,7 @@ bool arrange(Model &model, // The model with the geometries
|
||||||
Coord batch_offset = 0;
|
Coord batch_offset = 0;
|
||||||
|
|
||||||
for(auto& group : result) {
|
for(auto& group : result) {
|
||||||
applyResult(group, batch_offset, shapemap);
|
applyResult(group, batch_offset, shapemap, wti);
|
||||||
|
|
||||||
// Only the first pack group can be placed onto the print bed. The
|
// Only the first pack group can be placed onto the print bed. The
|
||||||
// other objects which could not fit will be placed next to the
|
// other objects which could not fit will be placed next to the
|
||||||
|
@ -823,10 +854,11 @@ bool arrange(Model &model, // The model with the geometries
|
||||||
void find_new_position(const Model &model,
|
void find_new_position(const Model &model,
|
||||||
ModelInstancePtrs toadd,
|
ModelInstancePtrs toadd,
|
||||||
coord_t min_obj_distance,
|
coord_t min_obj_distance,
|
||||||
const Polyline &bed)
|
const Polyline &bed,
|
||||||
|
WipeTowerInfo& wti)
|
||||||
{
|
{
|
||||||
// Get the 2D projected shapes with their 3D model instance pointers
|
// Get the 2D projected shapes with their 3D model instance pointers
|
||||||
auto shapemap = arr::projectModelFromTop(model);
|
auto shapemap = arr::projectModelFromTop(model, wti);
|
||||||
|
|
||||||
// Copy the references for the shapes only as the arranger expects a
|
// Copy the references for the shapes only as the arranger expects a
|
||||||
// sequence of objects convertible to Item or ClipperPolygon
|
// sequence of objects convertible to Item or ClipperPolygon
|
||||||
|
|
|
@ -40,6 +40,13 @@ struct BedShapeHint {
|
||||||
|
|
||||||
BedShapeHint bedShape(const Polyline& bed);
|
BedShapeHint bedShape(const Polyline& bed);
|
||||||
|
|
||||||
|
struct WipeTowerInfo {
|
||||||
|
bool is_wipe_tower = false;
|
||||||
|
Vec2d pos;
|
||||||
|
Vec2d bb_size;
|
||||||
|
double rotation;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Arranges the model objects on the screen.
|
* \brief Arranges the model objects on the screen.
|
||||||
*
|
*
|
||||||
|
@ -66,7 +73,9 @@ BedShapeHint bedShape(const Polyline& bed);
|
||||||
* packed. The unsigned argument is the number of items remaining to pack.
|
* packed. The unsigned argument is the number of items remaining to pack.
|
||||||
* \param stopcondition A predicate returning true if abort is needed.
|
* \param stopcondition A predicate returning true if abort is needed.
|
||||||
*/
|
*/
|
||||||
bool arrange(Model &model, coord_t min_obj_distance,
|
bool arrange(Model &model,
|
||||||
|
WipeTowerInfo& wipe_tower_info,
|
||||||
|
coord_t min_obj_distance,
|
||||||
const Slic3r::Polyline& bed,
|
const Slic3r::Polyline& bed,
|
||||||
BedShapeHint bedhint,
|
BedShapeHint bedhint,
|
||||||
bool first_bin_only,
|
bool first_bin_only,
|
||||||
|
@ -78,7 +87,8 @@ bool arrange(Model &model, coord_t min_obj_distance,
|
||||||
void find_new_position(const Model& model,
|
void find_new_position(const Model& model,
|
||||||
ModelInstancePtrs instances_to_add,
|
ModelInstancePtrs instances_to_add,
|
||||||
coord_t min_obj_distance,
|
coord_t min_obj_distance,
|
||||||
const Slic3r::Polyline& bed);
|
const Slic3r::Polyline& bed,
|
||||||
|
WipeTowerInfo& wti);
|
||||||
|
|
||||||
} // arr
|
} // arr
|
||||||
} // Slic3r
|
} // Slic3r
|
||||||
|
|
|
@ -716,12 +716,12 @@ int GLVolumeCollection::load_wipe_tower_preview(
|
||||||
brim_mesh.translate(-brim_width, -brim_width, 0.f);
|
brim_mesh.translate(-brim_width, -brim_width, 0.f);
|
||||||
mesh.merge(brim_mesh);
|
mesh.merge(brim_mesh);
|
||||||
|
|
||||||
mesh.rotate(rotation_angle, &origin_of_rotation); // rotates the box according to the config rotation setting
|
|
||||||
|
|
||||||
this->volumes.emplace_back(new GLVolume(color));
|
this->volumes.emplace_back(new GLVolume(color));
|
||||||
GLVolume &v = *this->volumes.back();
|
GLVolume &v = *this->volumes.back();
|
||||||
v.indexed_vertex_array.load_mesh(mesh, use_VBOs);
|
v.indexed_vertex_array.load_mesh(mesh, use_VBOs);
|
||||||
v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0));
|
v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0));
|
||||||
|
v.set_volume_rotation(Vec3d(0., 0., (M_PI/180.) * rotation_angle));
|
||||||
|
|
||||||
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
|
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
|
||||||
v.bounding_box = v.indexed_vertex_array.bounding_box();
|
v.bounding_box = v.indexed_vertex_array.bounding_box();
|
||||||
v.indexed_vertex_array.finalize_geometry(use_VBOs);
|
v.indexed_vertex_array.finalize_geometry(use_VBOs);
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "slic3r/GUI/GLShader.hpp"
|
#include "slic3r/GUI/GLShader.hpp"
|
||||||
#include "slic3r/GUI/GUI.hpp"
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
#include "slic3r/GUI/PresetBundle.hpp"
|
#include "slic3r/GUI/PresetBundle.hpp"
|
||||||
|
#include "slic3r/GUI/Tab.hpp"
|
||||||
#include "GUI_App.hpp"
|
#include "GUI_App.hpp"
|
||||||
#include "GUI_ObjectList.hpp"
|
#include "GUI_ObjectList.hpp"
|
||||||
#include "GUI_ObjectManipulation.hpp"
|
#include "GUI_ObjectManipulation.hpp"
|
||||||
|
@ -1202,6 +1203,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent);
|
||||||
|
wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>);
|
wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>);
|
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>);
|
||||||
wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
|
wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
|
||||||
|
@ -3115,6 +3117,10 @@ void GLCanvas3D::do_rotate()
|
||||||
for (const GLVolume* v : m_volumes.volumes)
|
for (const GLVolume* v : m_volumes.volumes)
|
||||||
{
|
{
|
||||||
int object_idx = v->object_idx();
|
int object_idx = v->object_idx();
|
||||||
|
if (object_idx == 1000) { // the wipe tower
|
||||||
|
Vec3d offset = v->get_volume_offset();
|
||||||
|
post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2))));
|
||||||
|
}
|
||||||
if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx))
|
if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -3311,6 +3317,38 @@ void GLCanvas3D::update_ui_from_settings()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
arr::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const
|
||||||
|
{
|
||||||
|
arr::WipeTowerInfo wti;
|
||||||
|
for (const GLVolume* vol : m_volumes.volumes) {
|
||||||
|
if (vol->is_wipe_tower) {
|
||||||
|
wti.is_wipe_tower = true;
|
||||||
|
wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"),
|
||||||
|
m_config->opt_float("wipe_tower_y"));
|
||||||
|
wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle");
|
||||||
|
const BoundingBoxf3& bb = vol->bounding_box;
|
||||||
|
wti.bb_size = Vec2d(bb.size()(0), bb.size()(1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wti;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GLCanvas3D::arrange_wipe_tower(const arr::WipeTowerInfo& wti) const
|
||||||
|
{
|
||||||
|
if (wti.is_wipe_tower) {
|
||||||
|
DynamicPrintConfig cfg;
|
||||||
|
cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = wti.pos(0);
|
||||||
|
cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = wti.pos(1);
|
||||||
|
cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = (180./M_PI) * wti.rotation;
|
||||||
|
wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos)
|
Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos)
|
||||||
{
|
{
|
||||||
float z0 = 0.0f;
|
float z0 = 0.0f;
|
||||||
|
@ -4314,6 +4352,7 @@ void GLCanvas3D::_render_selection_sidebar_hints() const
|
||||||
m_shader.stop_using();
|
m_shader.stop_using();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GLCanvas3D::_update_volumes_hover_state() const
|
void GLCanvas3D::_update_volumes_hover_state() const
|
||||||
{
|
{
|
||||||
for (GLVolume* v : m_volumes.volumes)
|
for (GLVolume* v : m_volumes.volumes)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "libslic3r/ModelArrange.hpp"
|
||||||
#include "3DScene.hpp"
|
#include "3DScene.hpp"
|
||||||
#include "GLToolbar.hpp"
|
#include "GLToolbar.hpp"
|
||||||
#include "Event.hpp"
|
#include "Event.hpp"
|
||||||
|
@ -116,6 +117,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent);
|
wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
|
||||||
|
wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>);
|
wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>);
|
wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>);
|
||||||
wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
|
wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
|
||||||
|
@ -608,6 +610,9 @@ public:
|
||||||
int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; }
|
int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; }
|
||||||
int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); }
|
int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); }
|
||||||
|
|
||||||
|
arr::WipeTowerInfo get_wipe_tower_info() const;
|
||||||
|
void arrange_wipe_tower(const arr::WipeTowerInfo& wti) const;
|
||||||
|
|
||||||
// Returns the view ray line, in world coordinate, at the given mouse position.
|
// Returns the view ray line, in world coordinate, at the given mouse position.
|
||||||
Linef3 mouse_ray(const Point& mouse_pos);
|
Linef3 mouse_ray(const Point& mouse_pos);
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,7 @@ protected:
|
||||||
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
|
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
virtual bool on_is_activable(const Selection& selection) const { return !selection.is_wipe_tower(); }
|
virtual bool on_is_activable(const Selection& selection) const { return true; }
|
||||||
virtual void on_enable_grabber(unsigned int id)
|
virtual void on_enable_grabber(unsigned int id)
|
||||||
{
|
{
|
||||||
if ((0 <= id) && (id < 3))
|
if ((0 <= id) && (id < 3))
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "slic3r/GUI/3DScene.hpp"
|
#include "slic3r/GUI/3DScene.hpp"
|
||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||||
|
#include "slic3r/GUI/PresetBundle.hpp"
|
||||||
|
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
#include <wx/glcanvas.h>
|
#include <wx/glcanvas.h>
|
||||||
|
@ -264,8 +265,12 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas)
|
||||||
|
|
||||||
const Selection& selection = canvas.get_selection();
|
const Selection& selection = canvas.get_selection();
|
||||||
|
|
||||||
bool enable_move_z = !selection.is_wipe_tower();
|
bool is_wipe_tower = selection.is_wipe_tower();
|
||||||
enable_grabber(Move, 2, enable_move_z);
|
enable_grabber(Move, 2, !is_wipe_tower);
|
||||||
|
enable_grabber(Move, 2, !is_wipe_tower);
|
||||||
|
enable_grabber(Rotate, 0, !is_wipe_tower);
|
||||||
|
enable_grabber(Rotate, 1, !is_wipe_tower);
|
||||||
|
|
||||||
bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier();
|
bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier();
|
||||||
for (int i = 0; i < 6; ++i)
|
for (int i = 0; i < 6; ++i)
|
||||||
{
|
{
|
||||||
|
@ -290,6 +295,14 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas)
|
||||||
set_flattening_data(nullptr);
|
set_flattening_data(nullptr);
|
||||||
set_sla_support_data(nullptr, selection);
|
set_sla_support_data(nullptr, selection);
|
||||||
}
|
}
|
||||||
|
else if (is_wipe_tower)
|
||||||
|
{
|
||||||
|
DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||||
|
set_scale(Vec3d::Ones());
|
||||||
|
set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast<const ConfigOptionFloat*>(config.option("wipe_tower_rotation_angle"))->value));
|
||||||
|
set_flattening_data(nullptr);
|
||||||
|
set_sla_support_data(nullptr, selection);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
set_scale(Vec3d::Ones());
|
set_scale(Vec3d::Ones());
|
||||||
|
|
|
@ -1314,6 +1314,7 @@ struct Plater::priv
|
||||||
void on_object_select(SimpleEvent&);
|
void on_object_select(SimpleEvent&);
|
||||||
void on_right_click(Vec2dEvent&);
|
void on_right_click(Vec2dEvent&);
|
||||||
void on_wipetower_moved(Vec3dEvent&);
|
void on_wipetower_moved(Vec3dEvent&);
|
||||||
|
void on_wipetower_rotated(Vec3dEvent&);
|
||||||
void on_update_geometry(Vec3dsEvent<2>&);
|
void on_update_geometry(Vec3dsEvent<2>&);
|
||||||
void on_3dcanvas_mouse_dragging_finished(SimpleEvent&);
|
void on_3dcanvas_mouse_dragging_finished(SimpleEvent&);
|
||||||
|
|
||||||
|
@ -1448,6 +1449,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||||
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
|
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this);
|
view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this);
|
||||||
|
view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this);
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event<bool> &evt) { this->sidebar->enable_buttons(evt.data); });
|
view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event<bool> &evt) { this->sidebar->enable_buttons(evt.data); });
|
||||||
|
@ -1455,6 +1457,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this);
|
view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this);
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
|
||||||
view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); });
|
view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); });
|
||||||
|
|
||||||
// 3DScene/Toolbar:
|
// 3DScene/Toolbar:
|
||||||
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
|
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
|
||||||
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
|
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
|
||||||
|
@ -1877,7 +1880,13 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs &mode
|
||||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
Polyline bed; bed.points.reserve(bedpoints.size());
|
||||||
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
||||||
|
|
||||||
arr::find_new_position(model, new_instances, min_obj_distance, bed);
|
arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info();
|
||||||
|
|
||||||
|
arr::find_new_position(model, new_instances, min_obj_distance, bed, wti);
|
||||||
|
|
||||||
|
// it remains to move the wipe tower:
|
||||||
|
view3D->get_canvas3d()->arrange_wipe_tower(wti);
|
||||||
|
|
||||||
#endif /* AUTOPLACEMENT_ON_LOAD */
|
#endif /* AUTOPLACEMENT_ON_LOAD */
|
||||||
|
|
||||||
if (scaled_down) {
|
if (scaled_down) {
|
||||||
|
@ -2134,6 +2143,8 @@ void Plater::priv::arrange()
|
||||||
|
|
||||||
statusfn(0, arrangestr);
|
statusfn(0, arrangestr);
|
||||||
|
|
||||||
|
arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
arr::BedShapeHint hint;
|
arr::BedShapeHint hint;
|
||||||
|
|
||||||
|
@ -2141,6 +2152,7 @@ void Plater::priv::arrange()
|
||||||
hint.type = arr::BedShapeType::WHO_KNOWS;
|
hint.type = arr::BedShapeType::WHO_KNOWS;
|
||||||
|
|
||||||
arr::arrange(model,
|
arr::arrange(model,
|
||||||
|
wti,
|
||||||
min_obj_distance,
|
min_obj_distance,
|
||||||
bed,
|
bed,
|
||||||
hint,
|
hint,
|
||||||
|
@ -2152,6 +2164,9 @@ void Plater::priv::arrange()
|
||||||
"Some geometries may be invalid."));
|
"Some geometries may be invalid."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// it remains to move the wipe tower:
|
||||||
|
view3D->get_canvas3d()->arrange_wipe_tower(wti);
|
||||||
|
|
||||||
statusfn(0, L("Arranging done."));
|
statusfn(0, L("Arranging done."));
|
||||||
statusbar()->set_range(prev_range);
|
statusbar()->set_range(prev_range);
|
||||||
statusbar()->set_cancel_callback(); // remove cancel button
|
statusbar()->set_cancel_callback(); // remove cancel button
|
||||||
|
@ -2254,7 +2269,8 @@ void Plater::priv::sla_optimize_rotation() {
|
||||||
oi->set_rotation(rt);
|
oi->set_rotation(rt);
|
||||||
}
|
}
|
||||||
|
|
||||||
arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed);
|
arr::WipeTowerInfo wti; // useless in SLA context
|
||||||
|
arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed, wti);
|
||||||
|
|
||||||
// Correct the z offset of the object which was corrupted be the rotation
|
// Correct the z offset of the object which was corrupted be the rotation
|
||||||
o->ensure_on_bed();
|
o->ensure_on_bed();
|
||||||
|
@ -2862,6 +2878,15 @@ void Plater::priv::on_wipetower_moved(Vec3dEvent &evt)
|
||||||
wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
|
wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt)
|
||||||
|
{
|
||||||
|
DynamicPrintConfig cfg;
|
||||||
|
cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0);
|
||||||
|
cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = evt.data(1);
|
||||||
|
cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = Geometry::rad2deg(evt.data(2));
|
||||||
|
wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
void Plater::priv::on_update_geometry(Vec3dsEvent<2>&)
|
void Plater::priv::on_update_geometry(Vec3dsEvent<2>&)
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO
|
||||||
|
|
|
@ -499,100 +499,111 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
|
||||||
// Only relative rotation values are allowed in the world coordinate system.
|
// Only relative rotation values are allowed in the world coordinate system.
|
||||||
assert(!transformation_type.world() || transformation_type.relative());
|
assert(!transformation_type.world() || transformation_type.relative());
|
||||||
|
|
||||||
int rot_axis_max = 0;
|
if (!is_wipe_tower()) {
|
||||||
if (rotation.isApprox(Vec3d::Zero()))
|
int rot_axis_max = 0;
|
||||||
{
|
if (rotation.isApprox(Vec3d::Zero()))
|
||||||
for (unsigned int i : m_list)
|
|
||||||
{
|
{
|
||||||
GLVolume &volume = *(*m_volumes)[i];
|
for (unsigned int i : m_list)
|
||||||
if (m_mode == Instance)
|
|
||||||
{
|
|
||||||
volume.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation());
|
|
||||||
volume.set_instance_offset(m_cache.volumes_data[i].get_instance_position());
|
|
||||||
}
|
|
||||||
else if (m_mode == Volume)
|
|
||||||
{
|
|
||||||
volume.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation());
|
|
||||||
volume.set_volume_offset(m_cache.volumes_data[i].get_volume_position());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//FIXME this does not work for absolute rotations (transformation_type.absolute() is true)
|
|
||||||
rotation.cwiseAbs().maxCoeff(&rot_axis_max);
|
|
||||||
|
|
||||||
// For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it.
|
|
||||||
std::vector<int> object_instance_first(m_model->objects.size(), -1);
|
|
||||||
auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) {
|
|
||||||
int first_volume_idx = object_instance_first[volume.object_idx()];
|
|
||||||
if (rot_axis_max != 2 && first_volume_idx != -1) {
|
|
||||||
// Generic rotation, but no rotation around the Z axis.
|
|
||||||
// Always do a local rotation (do not consider the selection to be a rigid body).
|
|
||||||
assert(is_approx(rotation.z(), 0.0));
|
|
||||||
const GLVolume &first_volume = *(*m_volumes)[first_volume_idx];
|
|
||||||
const Vec3d &rotation = first_volume.get_instance_rotation();
|
|
||||||
double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation());
|
|
||||||
volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// extracts rotations from the composed transformation
|
|
||||||
Vec3d new_rotation = transformation_type.world() ?
|
|
||||||
Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) :
|
|
||||||
transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation();
|
|
||||||
if (rot_axis_max == 2 && transformation_type.joint()) {
|
|
||||||
// Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis.
|
|
||||||
Vec3d offset = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, new_rotation(2) - m_cache.volumes_data[i].get_instance_rotation()(2))) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center);
|
|
||||||
volume.set_instance_offset(m_cache.dragging_center + offset);
|
|
||||||
}
|
|
||||||
volume.set_instance_rotation(new_rotation);
|
|
||||||
object_instance_first[volume.object_idx()] = i;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (unsigned int i : m_list)
|
|
||||||
{
|
|
||||||
GLVolume &volume = *(*m_volumes)[i];
|
|
||||||
if (is_single_full_instance())
|
|
||||||
rotate_instance(volume, i);
|
|
||||||
else if (is_single_volume() || is_single_modifier())
|
|
||||||
{
|
|
||||||
if (transformation_type.independent())
|
|
||||||
volume.set_volume_rotation(volume.get_volume_rotation() + rotation);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
|
|
||||||
Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
|
|
||||||
volume.set_volume_rotation(new_rotation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
|
GLVolume &volume = *(*m_volumes)[i];
|
||||||
if (m_mode == Instance)
|
if (m_mode == Instance)
|
||||||
rotate_instance(volume, i);
|
{
|
||||||
|
volume.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation());
|
||||||
|
volume.set_instance_offset(m_cache.volumes_data[i].get_instance_position());
|
||||||
|
}
|
||||||
else if (m_mode == Volume)
|
else if (m_mode == Volume)
|
||||||
{
|
{
|
||||||
// extracts rotations from the composed transformation
|
volume.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation());
|
||||||
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
|
volume.set_volume_offset(m_cache.volumes_data[i].get_volume_position());
|
||||||
Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
|
|
||||||
if (transformation_type.joint())
|
|
||||||
{
|
|
||||||
Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center;
|
|
||||||
Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot);
|
|
||||||
volume.set_volume_offset(local_pivot + offset);
|
|
||||||
}
|
|
||||||
volume.set_volume_rotation(new_rotation);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else { // this is not the wipe tower
|
||||||
|
//FIXME this does not work for absolute rotations (transformation_type.absolute() is true)
|
||||||
|
rotation.cwiseAbs().maxCoeff(&rot_axis_max);
|
||||||
|
|
||||||
#if !DISABLE_INSTANCES_SYNCH
|
// For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it.
|
||||||
if (m_mode == Instance)
|
std::vector<int> object_instance_first(m_model->objects.size(), -1);
|
||||||
synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL);
|
auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) {
|
||||||
else if (m_mode == Volume)
|
int first_volume_idx = object_instance_first[volume.object_idx()];
|
||||||
synchronize_unselected_volumes();
|
if (rot_axis_max != 2 && first_volume_idx != -1) {
|
||||||
#endif // !DISABLE_INSTANCES_SYNCH
|
// Generic rotation, but no rotation around the Z axis.
|
||||||
|
// Always do a local rotation (do not consider the selection to be a rigid body).
|
||||||
|
assert(is_approx(rotation.z(), 0.0));
|
||||||
|
const GLVolume &first_volume = *(*m_volumes)[first_volume_idx];
|
||||||
|
const Vec3d &rotation = first_volume.get_instance_rotation();
|
||||||
|
double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation());
|
||||||
|
volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// extracts rotations from the composed transformation
|
||||||
|
Vec3d new_rotation = transformation_type.world() ?
|
||||||
|
Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) :
|
||||||
|
transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation();
|
||||||
|
if (rot_axis_max == 2 && transformation_type.joint()) {
|
||||||
|
// Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis.
|
||||||
|
Vec3d offset = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, new_rotation(2) - m_cache.volumes_data[i].get_instance_rotation()(2))) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center);
|
||||||
|
volume.set_instance_offset(m_cache.dragging_center + offset);
|
||||||
|
}
|
||||||
|
volume.set_instance_rotation(new_rotation);
|
||||||
|
object_instance_first[volume.object_idx()] = i;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (unsigned int i : m_list)
|
||||||
|
{
|
||||||
|
GLVolume &volume = *(*m_volumes)[i];
|
||||||
|
if (is_single_full_instance())
|
||||||
|
rotate_instance(volume, i);
|
||||||
|
else if (is_single_volume() || is_single_modifier())
|
||||||
|
{
|
||||||
|
if (transformation_type.independent())
|
||||||
|
volume.set_volume_rotation(volume.get_volume_rotation() + rotation);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
|
||||||
|
Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
|
||||||
|
volume.set_volume_rotation(new_rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (m_mode == Instance)
|
||||||
|
rotate_instance(volume, i);
|
||||||
|
else if (m_mode == Volume)
|
||||||
|
{
|
||||||
|
// extracts rotations from the composed transformation
|
||||||
|
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
|
||||||
|
Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
|
||||||
|
if (transformation_type.joint())
|
||||||
|
{
|
||||||
|
Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center;
|
||||||
|
Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot);
|
||||||
|
volume.set_volume_offset(local_pivot + offset);
|
||||||
|
}
|
||||||
|
volume.set_volume_rotation(new_rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !DISABLE_INSTANCES_SYNCH
|
||||||
|
if (m_mode == Instance)
|
||||||
|
synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL);
|
||||||
|
else if (m_mode == Volume)
|
||||||
|
synchronize_unselected_volumes();
|
||||||
|
#endif // !DISABLE_INSTANCES_SYNCH
|
||||||
|
}
|
||||||
|
else { // it's the wipe tower that's selected and being rotated
|
||||||
|
GLVolume& volume = *((*m_volumes)[*m_list.begin()]); // the wipe tower is always alone in the selection
|
||||||
|
|
||||||
|
// make sure the wipe tower rotates around its center, not origin
|
||||||
|
// we can assume that only Z rotation changes
|
||||||
|
Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset();
|
||||||
|
Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0, 0, 1)) * center_local;
|
||||||
|
volume.set_volume_rotation(rotation);
|
||||||
|
volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new);
|
||||||
|
}
|
||||||
|
|
||||||
this->set_bounding_boxes_dirty();
|
this->set_bounding_boxes_dirty();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue