mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-11 16:57:53 -06:00
WIP: Transformation of instances in world coordinate space:
Ulocking the "anisotropic" scaling checkbox will bake the transformation into meshes to allow for scaling in world axes. Optimized and templated the stl_transform functions, now also available for 3x3 matrices. The Canvas3D::reload_scene() now maintains selection even if all volumes of an instance changed their IDs.
This commit is contained in:
parent
f78c3a0f1b
commit
6526a8fcaf
14 changed files with 237 additions and 118 deletions
|
@ -180,8 +180,65 @@ extern void stl_rotate_z(stl_file *stl, float angle);
|
||||||
extern void stl_mirror_xy(stl_file *stl);
|
extern void stl_mirror_xy(stl_file *stl);
|
||||||
extern void stl_mirror_yz(stl_file *stl);
|
extern void stl_mirror_yz(stl_file *stl);
|
||||||
extern void stl_mirror_xz(stl_file *stl);
|
extern void stl_mirror_xz(stl_file *stl);
|
||||||
extern void stl_transform(stl_file *stl, float *trafo3x4);
|
|
||||||
extern void stl_transform(stl_file *stl, const Eigen::Transform<double, 3, Eigen::Affine, Eigen::DontAlign>& t);
|
template<typename T>
|
||||||
|
extern void stl_transform(stl_file *stl, T *trafo3x4)
|
||||||
|
{
|
||||||
|
if (stl->error)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) {
|
||||||
|
stl_facet &face = stl->facet_start[i_face];
|
||||||
|
for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) {
|
||||||
|
stl_vertex &v_dst = face.vertex[i_vertex];
|
||||||
|
stl_vertex v_src = v_dst;
|
||||||
|
v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]);
|
||||||
|
v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]);
|
||||||
|
v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]);
|
||||||
|
}
|
||||||
|
stl_vertex &v_dst = face.normal;
|
||||||
|
stl_vertex v_src = v_dst;
|
||||||
|
v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2));
|
||||||
|
v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2));
|
||||||
|
v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
stl_get_size(stl);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline void stl_transform(stl_file *stl, const Eigen::Transform<T, 3, Eigen::Affine, Eigen::DontAlign>& t)
|
||||||
|
{
|
||||||
|
if (stl->error)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> r = t.matrix().block<3, 3>(0, 0);
|
||||||
|
for (size_t i = 0; i < stl->stats.number_of_facets; ++i) {
|
||||||
|
stl_facet &f = stl->facet_start[i];
|
||||||
|
for (size_t j = 0; j < 3; ++j)
|
||||||
|
f.vertex[j] = (t * f.vertex[j].cast<T>()).cast<float>().eval();
|
||||||
|
f.normal = (r * f.normal.cast<T>()).cast<float>().eval();
|
||||||
|
}
|
||||||
|
|
||||||
|
stl_get_size(stl);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline void stl_transform(stl_file *stl, const Eigen::Matrix<T, 3, 3, Eigen::DontAlign>& m)
|
||||||
|
{
|
||||||
|
if (stl->error)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < stl->stats.number_of_facets; ++i) {
|
||||||
|
stl_facet &f = stl->facet_start[i];
|
||||||
|
for (size_t j = 0; j < 3; ++j)
|
||||||
|
f.vertex[j] = (m * f.vertex[j].cast<T>()).cast<float>().eval();
|
||||||
|
f.normal = (m * f.normal.cast<T>()).cast<float>().eval();
|
||||||
|
}
|
||||||
|
|
||||||
|
stl_get_size(stl);
|
||||||
|
}
|
||||||
|
|
||||||
extern void stl_open_merge(stl_file *stl, char *file);
|
extern void stl_open_merge(stl_file *stl, char *file);
|
||||||
extern void stl_invalidate_shared_vertices(stl_file *stl);
|
extern void stl_invalidate_shared_vertices(stl_file *stl);
|
||||||
extern void stl_generate_shared_vertices(stl_file *stl);
|
extern void stl_generate_shared_vertices(stl_file *stl);
|
||||||
|
|
|
@ -137,65 +137,6 @@ static void calculate_normals(stl_file *stl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stl_transform(stl_file *stl, float *trafo3x4) {
|
|
||||||
int i_face, i_vertex;
|
|
||||||
if (stl->error)
|
|
||||||
return;
|
|
||||||
for (i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) {
|
|
||||||
stl_vertex *vertices = stl->facet_start[i_face].vertex;
|
|
||||||
for (i_vertex = 0; i_vertex < 3; ++ i_vertex) {
|
|
||||||
stl_vertex &v_dst = vertices[i_vertex];
|
|
||||||
stl_vertex v_src = v_dst;
|
|
||||||
v_dst(0) = trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3];
|
|
||||||
v_dst(1) = trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7];
|
|
||||||
v_dst(2) = trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stl_get_size(stl);
|
|
||||||
calculate_normals(stl);
|
|
||||||
}
|
|
||||||
|
|
||||||
void stl_transform(stl_file *stl, const Eigen::Transform<double, 3, Eigen::Affine, Eigen::DontAlign>& t)
|
|
||||||
{
|
|
||||||
if (stl->error)
|
|
||||||
return;
|
|
||||||
|
|
||||||
unsigned int vertices_count = 3 * (unsigned int)stl->stats.number_of_facets;
|
|
||||||
if (vertices_count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Eigen::MatrixXf src_vertices(3, vertices_count);
|
|
||||||
stl_facet* facet_ptr = stl->facet_start;
|
|
||||||
unsigned int v_id = 0;
|
|
||||||
while (facet_ptr < stl->facet_start + stl->stats.number_of_facets)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 3; ++i)
|
|
||||||
{
|
|
||||||
::memcpy((void*)src_vertices.col(v_id).data(), (const void*)&facet_ptr->vertex[i], 3 * sizeof(float));
|
|
||||||
++v_id;
|
|
||||||
}
|
|
||||||
facet_ptr += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Eigen::MatrixXf dst_vertices(3, vertices_count);
|
|
||||||
dst_vertices = t.cast<float>() * src_vertices.colwise().homogeneous();
|
|
||||||
|
|
||||||
facet_ptr = stl->facet_start;
|
|
||||||
v_id = 0;
|
|
||||||
while (facet_ptr < stl->facet_start + stl->stats.number_of_facets)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 3; ++i)
|
|
||||||
{
|
|
||||||
::memcpy((void*)&facet_ptr->vertex[i], (const void*)dst_vertices.col(v_id).data(), 3 * sizeof(float));
|
|
||||||
++v_id;
|
|
||||||
}
|
|
||||||
facet_ptr += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
stl_get_size(stl);
|
|
||||||
calculate_normals(stl);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
stl_rotate_x(stl_file *stl, float angle) {
|
stl_rotate_x(stl_file *stl, float angle) {
|
||||||
int i;
|
int i;
|
||||||
|
|
|
@ -269,6 +269,21 @@ extern Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec
|
||||||
// This should only be called if it is known, that the two rotations only differ in rotation around the Z axis.
|
// This should only be called if it is known, that the two rotations only differ in rotation around the Z axis.
|
||||||
extern double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to);
|
extern double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to);
|
||||||
|
|
||||||
|
// Is the angle close to a multiple of 90 degrees?
|
||||||
|
inline bool is_rotation_ninety_degrees(double a)
|
||||||
|
{
|
||||||
|
a = fmod(std::abs(a), 0.5 * M_PI);
|
||||||
|
if (a > 0.25 * PI)
|
||||||
|
a = 0.5 * PI - a;
|
||||||
|
return a < 0.001;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is the angle close to a multiple of 90 degrees?
|
||||||
|
inline bool is_rotation_ninety_degrees(const Vec3d &rotation)
|
||||||
|
{
|
||||||
|
return is_rotation_ninety_degrees(rotation.x()) && is_rotation_ninety_degrees(rotation.y()) && is_rotation_ninety_degrees(rotation.z());
|
||||||
|
}
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -24,6 +24,19 @@ unsigned int Model::s_auto_extruder_id = 1;
|
||||||
|
|
||||||
size_t ModelBase::s_last_id = 0;
|
size_t ModelBase::s_last_id = 0;
|
||||||
|
|
||||||
|
// Unique object / instance ID for the wipe tower.
|
||||||
|
ModelID wipe_tower_object_id()
|
||||||
|
{
|
||||||
|
static ModelBase mine;
|
||||||
|
return mine.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelID wipe_tower_instance_id()
|
||||||
|
{
|
||||||
|
static ModelBase mine;
|
||||||
|
return mine.id();
|
||||||
|
}
|
||||||
|
|
||||||
Model& Model::assign_copy(const Model &rhs)
|
Model& Model::assign_copy(const Model &rhs)
|
||||||
{
|
{
|
||||||
this->copy_id(rhs);
|
this->copy_id(rhs);
|
||||||
|
@ -1320,6 +1333,58 @@ void ModelObject::repair()
|
||||||
v->mesh.repair();
|
v->mesh.repair();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||||
|
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
|
||||||
|
// This situation is solved by baking in the instance transformation into the mesh vertices.
|
||||||
|
// Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well.
|
||||||
|
void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
|
||||||
|
{
|
||||||
|
assert(instance_idx < this->instances.size());
|
||||||
|
|
||||||
|
const Geometry::Transformation reference_trafo = this->instances[instance_idx]->get_transformation();
|
||||||
|
if (Geometry::is_rotation_ninety_degrees(reference_trafo.get_rotation()))
|
||||||
|
// nothing to do, scaling in the world coordinate space is possible in the representation of Geometry::Transformation.
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool left_handed = reference_trafo.is_left_handed();
|
||||||
|
bool has_mirrorring = ! reference_trafo.get_mirror().isApprox(Vec3d(1., 1., 1.));
|
||||||
|
bool uniform_scaling = std::abs(reference_trafo.get_scaling_factor().x() - reference_trafo.get_scaling_factor().y()) < EPSILON &&
|
||||||
|
std::abs(reference_trafo.get_scaling_factor().x() - reference_trafo.get_scaling_factor().z()) < EPSILON;
|
||||||
|
double new_scaling_factor = uniform_scaling ? reference_trafo.get_scaling_factor().x() : 1.;
|
||||||
|
|
||||||
|
// Adjust the instances.
|
||||||
|
for (size_t i = 0; i < this->instances.size(); ++ i) {
|
||||||
|
ModelInstance &model_instance = *this->instances[i];
|
||||||
|
model_instance.set_rotation(Vec3d(0., 0., Geometry::rotation_diff_z(reference_trafo.get_rotation(), model_instance.get_rotation())));
|
||||||
|
model_instance.set_scaling_factor(Vec3d(new_scaling_factor, new_scaling_factor, new_scaling_factor));
|
||||||
|
model_instance.set_mirror(Vec3d(1., 1., 1.));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust the meshes.
|
||||||
|
// Transformation to be applied to the meshes.
|
||||||
|
Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0);
|
||||||
|
Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix();
|
||||||
|
for (ModelVolume *model_volume : this->volumes) {
|
||||||
|
const Geometry::Transformation volume_trafo = model_volume->get_transformation();
|
||||||
|
bool volume_left_handed = volume_trafo.is_left_handed();
|
||||||
|
bool volume_has_mirrorring = ! volume_trafo.get_mirror().isApprox(Vec3d(1., 1., 1.));
|
||||||
|
bool volume_uniform_scaling = std::abs(volume_trafo.get_scaling_factor().x() - volume_trafo.get_scaling_factor().y()) < EPSILON &&
|
||||||
|
std::abs(volume_trafo.get_scaling_factor().x() - volume_trafo.get_scaling_factor().z()) < EPSILON;
|
||||||
|
double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.;
|
||||||
|
// Transform the mesh.
|
||||||
|
Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0);
|
||||||
|
model_volume->transform_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed);
|
||||||
|
// Reset the rotation, scaling and mirroring.
|
||||||
|
model_volume->set_rotation(Vec3d(0., 0., 0.));
|
||||||
|
model_volume->set_scaling_factor(Vec3d(volume_new_scaling_factor, volume_new_scaling_factor, volume_new_scaling_factor));
|
||||||
|
model_volume->set_mirror(Vec3d(1., 1., 1.));
|
||||||
|
// Move the reference point of the volume to compensate for the change of the instance trafo.
|
||||||
|
model_volume->set_offset(volume_offset_correction * volume_trafo.get_offset());
|
||||||
|
}
|
||||||
|
|
||||||
|
this->invalidate_bounding_box();
|
||||||
|
}
|
||||||
|
|
||||||
double ModelObject::get_min_z() const
|
double ModelObject::get_min_z() const
|
||||||
{
|
{
|
||||||
if (instances.empty())
|
if (instances.empty())
|
||||||
|
@ -1656,6 +1721,22 @@ void ModelVolume::scale_geometry(const Vec3d& versor)
|
||||||
m_convex_hull.scale(versor);
|
m_convex_hull.scale(versor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModelVolume::transform_mesh(const Transform3d &mesh_trafo, bool fix_left_handed)
|
||||||
|
{
|
||||||
|
this->mesh.transform(mesh_trafo, fix_left_handed);
|
||||||
|
this->m_convex_hull.transform(mesh_trafo, fix_left_handed);
|
||||||
|
// Let the rest of the application know that the geometry changed, so the meshes have to be reloaded.
|
||||||
|
this->set_new_unique_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelVolume::transform_mesh(const Matrix3d &matrix, bool fix_left_handed)
|
||||||
|
{
|
||||||
|
this->mesh.transform(matrix, fix_left_handed);
|
||||||
|
this->m_convex_hull.transform(matrix, fix_left_handed);
|
||||||
|
// Let the rest of the application know that the geometry changed, so the meshes have to be reloaded.
|
||||||
|
this->set_new_unique_id();
|
||||||
|
}
|
||||||
|
|
||||||
void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const
|
void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const
|
||||||
{
|
{
|
||||||
mesh->transform(get_matrix(dont_translate));
|
mesh->transform(get_matrix(dont_translate));
|
||||||
|
|
|
@ -54,6 +54,10 @@ struct ModelID
|
||||||
size_t id;
|
size_t id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Unique object / instance ID for the wipe tower.
|
||||||
|
extern ModelID wipe_tower_object_id();
|
||||||
|
extern ModelID wipe_tower_instance_id();
|
||||||
|
|
||||||
// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID
|
// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID
|
||||||
// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject).
|
// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject).
|
||||||
// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ModelBase derived instances
|
// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ModelBase derived instances
|
||||||
|
@ -85,6 +89,9 @@ private:
|
||||||
|
|
||||||
static inline ModelID generate_new_id() { return ModelID(++ s_last_id); }
|
static inline ModelID generate_new_id() { return ModelID(++ s_last_id); }
|
||||||
static size_t s_last_id;
|
static size_t s_last_id;
|
||||||
|
|
||||||
|
friend ModelID wipe_tower_object_id();
|
||||||
|
friend ModelID wipe_tower_instance_id();
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MODELBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \
|
#define MODELBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \
|
||||||
|
@ -265,6 +272,11 @@ public:
|
||||||
ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates
|
ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates
|
||||||
void split(ModelObjectPtrs* new_objects);
|
void split(ModelObjectPtrs* new_objects);
|
||||||
void repair();
|
void repair();
|
||||||
|
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||||
|
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
|
||||||
|
// This situation is solved by baking in the instance transformation into the mesh vertices.
|
||||||
|
// Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well.
|
||||||
|
void bake_xy_rotation_into_meshes(size_t instance_idx);
|
||||||
|
|
||||||
double get_min_z() const;
|
double get_min_z() const;
|
||||||
double get_instance_min_z(size_t instance_idx) const;
|
double get_instance_min_z(size_t instance_idx) const;
|
||||||
|
@ -414,6 +426,8 @@ protected:
|
||||||
|
|
||||||
explicit ModelVolume(const ModelVolume &rhs) = default;
|
explicit ModelVolume(const ModelVolume &rhs) = default;
|
||||||
void set_model_object(ModelObject *model_object) { object = model_object; }
|
void set_model_object(ModelObject *model_object) { object = model_object; }
|
||||||
|
void transform_mesh(const Transform3d& t, bool fix_left_handed);
|
||||||
|
void transform_mesh(const Matrix3d& m, bool fix_left_handed);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Parent object owning this ModelVolume.
|
// Parent object owning this ModelVolume.
|
||||||
|
|
|
@ -40,6 +40,11 @@ typedef std::vector<Vec3crd> Points3;
|
||||||
typedef std::vector<Vec2d> Pointfs;
|
typedef std::vector<Vec2d> Pointfs;
|
||||||
typedef std::vector<Vec3d> Pointf3s;
|
typedef std::vector<Vec3d> Pointf3s;
|
||||||
|
|
||||||
|
typedef Eigen::Matrix<float, 2, 2, Eigen::DontAlign> Matrix2f;
|
||||||
|
typedef Eigen::Matrix<double, 2, 2, Eigen::DontAlign> Matrix2d;
|
||||||
|
typedef Eigen::Matrix<float, 3, 3, Eigen::DontAlign> Matrix3f;
|
||||||
|
typedef Eigen::Matrix<double, 3, 3, Eigen::DontAlign> Matrix3d;
|
||||||
|
|
||||||
typedef Eigen::Transform<float, 2, Eigen::Affine, Eigen::DontAlign> Transform2f;
|
typedef Eigen::Transform<float, 2, Eigen::Affine, Eigen::DontAlign> Transform2f;
|
||||||
typedef Eigen::Transform<double, 2, Eigen::Affine, Eigen::DontAlign> Transform2d;
|
typedef Eigen::Transform<double, 2, Eigen::Affine, Eigen::DontAlign> Transform2d;
|
||||||
typedef Eigen::Transform<float, 3, Eigen::Affine, Eigen::DontAlign> Transform3f;
|
typedef Eigen::Transform<float, 3, Eigen::Affine, Eigen::DontAlign> Transform3f;
|
||||||
|
|
|
@ -330,6 +330,17 @@ void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed)
|
||||||
|
{
|
||||||
|
stl_transform(&stl, m);
|
||||||
|
stl_invalidate_shared_vertices(&stl);
|
||||||
|
if (fix_left_handed && m.determinant() < 0.) {
|
||||||
|
// Left handed transformation is being applied. It is a good idea to flip the faces and their normals.
|
||||||
|
this->repair();
|
||||||
|
stl_reverse_all_facets(&stl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TriangleMesh::align_to_origin()
|
void TriangleMesh::align_to_origin()
|
||||||
{
|
{
|
||||||
this->translate(
|
this->translate(
|
||||||
|
|
|
@ -52,6 +52,7 @@ public:
|
||||||
void mirror_y() { this->mirror(Y); }
|
void mirror_y() { this->mirror(Y); }
|
||||||
void mirror_z() { this->mirror(Z); }
|
void mirror_z() { this->mirror(Z); }
|
||||||
void transform(const Transform3d& t, bool fix_left_handed = false);
|
void transform(const Transform3d& t, bool fix_left_handed = false);
|
||||||
|
void transform(const Matrix3d& t, bool fix_left_handed = false);
|
||||||
void align_to_origin();
|
void align_to_origin();
|
||||||
void rotate(double angle, Point* center);
|
void rotate(double angle, Point* center);
|
||||||
TriangleMeshPtrs split() const;
|
TriangleMeshPtrs split() const;
|
||||||
|
|
|
@ -716,6 +716,8 @@ int GLVolumeCollection::load_wipe_tower_preview(
|
||||||
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);
|
||||||
v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0);
|
v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0);
|
||||||
|
v.geometry_id.first = 0;
|
||||||
|
v.geometry_id.second = wipe_tower_instance_id().id;
|
||||||
v.is_wipe_tower = true;
|
v.is_wipe_tower = true;
|
||||||
v.shader_outside_printer_detection_enabled = ! size_unknown;
|
v.shader_outside_printer_detection_enabled = ! size_unknown;
|
||||||
return int(this->volumes.size() - 1);
|
return int(this->volumes.size() - 1);
|
||||||
|
|
|
@ -1770,6 +1770,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||||
// State of the sla_steps for all SLAPrintObjects.
|
// State of the sla_steps for all SLAPrintObjects.
|
||||||
std::vector<SLASupportState> sla_support_state;
|
std::vector<SLASupportState> sla_support_state;
|
||||||
|
|
||||||
|
std::vector<size_t> instance_ids_selected;
|
||||||
std::vector<size_t> map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1));
|
std::vector<size_t> map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1));
|
||||||
std::vector<GLVolume*> glvolumes_new;
|
std::vector<GLVolume*> glvolumes_new;
|
||||||
glvolumes_new.reserve(m_volumes.volumes.size());
|
glvolumes_new.reserve(m_volumes.volumes.size());
|
||||||
|
@ -1834,6 +1835,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||||
if (it != model_volume_state.end() && it->geometry_id == key.geometry_id)
|
if (it != model_volume_state.end() && it->geometry_id == key.geometry_id)
|
||||||
mvs = &(*it);
|
mvs = &(*it);
|
||||||
}
|
}
|
||||||
|
// Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID.
|
||||||
|
// The wipe tower has its own wipe_tower_instance_id().
|
||||||
|
if (m_selection.contains_volume(volume_id))
|
||||||
|
instance_ids_selected.emplace_back(volume->geometry_id.second);
|
||||||
if (mvs == nullptr || force_full_scene_refresh) {
|
if (mvs == nullptr || force_full_scene_refresh) {
|
||||||
// This GLVolume will be released.
|
// This GLVolume will be released.
|
||||||
if (volume->is_wipe_tower) {
|
if (volume->is_wipe_tower) {
|
||||||
|
@ -1865,6 +1870,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sort_remove_duplicates(instance_ids_selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_reload_delayed)
|
if (m_reload_delayed)
|
||||||
|
@ -2001,6 +2007,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
||||||
|
|
||||||
update_volumes_colors_by_extruder();
|
update_volumes_colors_by_extruder();
|
||||||
// Update selection indices based on the old/new GLVolumeCollection.
|
// Update selection indices based on the old/new GLVolumeCollection.
|
||||||
|
if (m_selection.get_mode() == Selection::Instance)
|
||||||
|
m_selection.instances_changed(instance_ids_selected);
|
||||||
|
else
|
||||||
m_selection.volumes_changed(map_glvolume_old_to_new);
|
m_selection.volumes_changed(map_glvolume_old_to_new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1357,19 +1357,12 @@ Geometry::Transformation volume_to_bed_transformation(const Geometry::Transforma
|
||||||
{
|
{
|
||||||
Geometry::Transformation out;
|
Geometry::Transformation out;
|
||||||
|
|
||||||
// Is the angle close to a multiple of 90 degrees?
|
|
||||||
auto ninety_degrees = [](double a) {
|
|
||||||
a = fmod(std::abs(a), 0.5 * PI);
|
|
||||||
if (a > 0.25 * PI)
|
|
||||||
a = 0.5 * PI - a;
|
|
||||||
return a < 0.001;
|
|
||||||
};
|
|
||||||
if (instance_transformation.is_scaling_uniform()) {
|
if (instance_transformation.is_scaling_uniform()) {
|
||||||
// No need to run the non-linear least squares fitting for uniform scaling.
|
// No need to run the non-linear least squares fitting for uniform scaling.
|
||||||
// Just set the inverse.
|
// Just set the inverse.
|
||||||
out.set_from_transform(instance_transformation.get_matrix(true).inverse());
|
out.set_from_transform(instance_transformation.get_matrix(true).inverse());
|
||||||
}
|
}
|
||||||
else if (ninety_degrees(instance_transformation.get_rotation().x()) && ninety_degrees(instance_transformation.get_rotation().y()) && ninety_degrees(instance_transformation.get_rotation().z()))
|
else if (Geometry::is_rotation_ninety_degrees(instance_transformation.get_rotation()))
|
||||||
{
|
{
|
||||||
// Anisotropic scaling, rotation by multiples of ninety degrees.
|
// Anisotropic scaling, rotation by multiples of ninety degrees.
|
||||||
Eigen::Matrix3d instance_rotation_trafo =
|
Eigen::Matrix3d instance_rotation_trafo =
|
||||||
|
|
|
@ -177,15 +177,19 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
||||||
|
|
||||||
void ObjectManipulation::Show(const bool show)
|
void ObjectManipulation::Show(const bool show)
|
||||||
{
|
{
|
||||||
if (show == IsShown())
|
if (show != IsShown()) {
|
||||||
return;
|
|
||||||
|
|
||||||
m_og->Show(show);
|
m_og->Show(show);
|
||||||
|
|
||||||
if (show && wxGetApp().get_mode() != comSimple) {
|
if (show && wxGetApp().get_mode() != comSimple) {
|
||||||
m_og->get_grid_sizer()->Show(size_t(0), false);
|
m_og->get_grid_sizer()->Show(size_t(0), false);
|
||||||
m_og->get_grid_sizer()->Show(size_t(1), false);
|
m_og->get_grid_sizer()->Show(size_t(1), false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance();
|
||||||
|
m_word_local_combo->Show(show_world_local_combo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ObjectManipulation::IsShown()
|
bool ObjectManipulation::IsShown()
|
||||||
|
@ -201,18 +205,6 @@ void ObjectManipulation::UpdateAndShow(const bool show)
|
||||||
OG_Settings::UpdateAndShow(show);
|
OG_Settings::UpdateAndShow(show);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_rotation_ninety_degrees(const Vec3d &rotation)
|
|
||||||
{
|
|
||||||
// Is the angle close to a multiple of 90 degrees?
|
|
||||||
auto ninety_degrees = [](double a) {
|
|
||||||
a = fmod(std::abs(a), 0.5 * PI);
|
|
||||||
if (a > 0.25 * PI)
|
|
||||||
a = 0.5 * PI - a;
|
|
||||||
return a < 0.001;
|
|
||||||
};
|
|
||||||
return ninety_degrees(rotation.x()) && ninety_degrees(rotation.y()) && ninety_degrees(rotation.z());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ObjectManipulation::update_settings_value(const Selection& selection)
|
void ObjectManipulation::update_settings_value(const Selection& selection)
|
||||||
{
|
{
|
||||||
m_new_move_label_string = L("Position");
|
m_new_move_label_string = L("Position");
|
||||||
|
@ -269,7 +261,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
|
||||||
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
|
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
|
||||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||||
// Is the angle close to a multiple of 90 degrees?
|
// Is the angle close to a multiple of 90 degrees?
|
||||||
if (! is_rotation_ninety_degrees(volume->get_instance_rotation())) {
|
if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) {
|
||||||
// Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling.
|
// Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling.
|
||||||
m_uniform_scale = true;
|
m_uniform_scale = true;
|
||||||
m_lock_bnt->SetLock(true);
|
m_lock_bnt->SetLock(true);
|
||||||
|
@ -573,7 +565,7 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value)
|
||||||
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
|
// all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one
|
||||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||||
// Is the angle close to a multiple of 90 degrees?
|
// Is the angle close to a multiple of 90 degrees?
|
||||||
if (! is_rotation_ninety_degrees(volume->get_instance_rotation())) {
|
if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) {
|
||||||
// Cannot apply scaling in the world coordinate system.
|
// Cannot apply scaling in the world coordinate system.
|
||||||
wxMessageDialog dlg(GUI::wxGetApp().mainframe,
|
wxMessageDialog dlg(GUI::wxGetApp().mainframe,
|
||||||
_(L("Non-uniform scaling of tilted objects is not supported in the World coordinate system.\n"
|
_(L("Non-uniform scaling of tilted objects is not supported in the World coordinate system.\n"
|
||||||
|
@ -586,6 +578,9 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Bake the rotation into the meshes of the object.
|
// Bake the rotation into the meshes of the object.
|
||||||
|
(*wxGetApp().model_objects())[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id);
|
||||||
|
// Update the 3D scene, selections etc.
|
||||||
|
wxGetApp().plater()->update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_uniform_scale = new_value;
|
m_uniform_scale = new_value;
|
||||||
|
|
|
@ -310,43 +310,36 @@ void Selection::clear()
|
||||||
wxGetApp().obj_manipul()->reset_cache();
|
wxGetApp().obj_manipul()->reset_cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the selection based on the new instance IDs.
|
||||||
|
void Selection::instances_changed(const std::vector<size_t> &instance_ids_selected)
|
||||||
|
{
|
||||||
|
assert(m_valid);
|
||||||
|
assert(m_mode == Instance);
|
||||||
|
m_list.clear();
|
||||||
|
for (unsigned int volume_idx = 0; volume_idx < (unsigned int)m_volumes->size(); ++ volume_idx) {
|
||||||
|
const GLVolume *volume = (*m_volumes)[volume_idx];
|
||||||
|
auto it = std::lower_bound(instance_ids_selected.begin(), instance_ids_selected.end(), volume->geometry_id.second);
|
||||||
|
if (it != instance_ids_selected.end() && *it == volume->geometry_id.second)
|
||||||
|
this->do_add_volume(volume_idx);
|
||||||
|
}
|
||||||
|
update_type();
|
||||||
|
m_bounding_box_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the selection based on the map from old indices to new indices after m_volumes changed.
|
// Update the selection based on the map from old indices to new indices after m_volumes changed.
|
||||||
// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances.
|
// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances.
|
||||||
void Selection::volumes_changed(const std::vector<size_t> &map_volume_old_to_new)
|
void Selection::volumes_changed(const std::vector<size_t> &map_volume_old_to_new)
|
||||||
{
|
{
|
||||||
assert(m_valid);
|
assert(m_valid);
|
||||||
|
assert(m_mode == Volume);
|
||||||
// 1) Update the selection set.
|
|
||||||
IndicesList list_new;
|
IndicesList list_new;
|
||||||
std::vector<std::pair<unsigned int, unsigned int>> model_instances;
|
for (unsigned int idx : m_list)
|
||||||
for (unsigned int idx : m_list) {
|
|
||||||
if (map_volume_old_to_new[idx] != size_t(-1)) {
|
if (map_volume_old_to_new[idx] != size_t(-1)) {
|
||||||
unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx];
|
unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx];
|
||||||
|
assert((*m_volumes)[new_idx]->selected);
|
||||||
list_new.insert(new_idx);
|
list_new.insert(new_idx);
|
||||||
if (m_mode == Instance) {
|
|
||||||
// Save the object_idx / instance_idx pair of selected old volumes,
|
|
||||||
// so we may add the newly added volumes of the same object_idx / instance_idx pair
|
|
||||||
// to the selection.
|
|
||||||
const GLVolume *volume = (*m_volumes)[new_idx];
|
|
||||||
model_instances.emplace_back(volume->object_idx(), volume->instance_idx());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
m_list = std::move(list_new);
|
m_list = std::move(list_new);
|
||||||
|
|
||||||
if (!model_instances.empty()) {
|
|
||||||
// Instance selection mode. Add the newly added volumes of the same object_idx / instance_idx pair
|
|
||||||
// to the selection.
|
|
||||||
assert(m_mode == Instance);
|
|
||||||
sort_remove_duplicates(model_instances);
|
|
||||||
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
|
|
||||||
const GLVolume* volume = (*m_volumes)[i];
|
|
||||||
for (const std::pair<int, int> &model_instance : model_instances)
|
|
||||||
if (volume->object_idx() == model_instance.first && volume->instance_idx() == model_instance.second)
|
|
||||||
do_add_volume(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update_type();
|
update_type();
|
||||||
m_bounding_box_dirty = true;
|
m_bounding_box_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,6 +224,8 @@ public:
|
||||||
|
|
||||||
void add_all();
|
void add_all();
|
||||||
|
|
||||||
|
// Update the selection based on the new instance IDs.
|
||||||
|
void instances_changed(const std::vector<size_t> &instance_ids_selected);
|
||||||
// Update the selection based on the map from old indices to new indices after m_volumes changed.
|
// Update the selection based on the map from old indices to new indices after m_volumes changed.
|
||||||
// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances.
|
// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances.
|
||||||
void volumes_changed(const std::vector<size_t> &map_volume_old_to_new);
|
void volumes_changed(const std::vector<size_t> &map_volume_old_to_new);
|
||||||
|
@ -245,7 +247,7 @@ public:
|
||||||
bool is_from_single_instance() const { return get_instance_idx() != -1; }
|
bool is_from_single_instance() const { return get_instance_idx() != -1; }
|
||||||
bool is_from_single_object() const;
|
bool is_from_single_object() const;
|
||||||
|
|
||||||
bool contains_volume(unsigned int volume_idx) const { return std::find(m_list.begin(), m_list.end(), volume_idx) != m_list.end(); }
|
bool contains_volume(unsigned int volume_idx) const { return m_list.find(volume_idx) != m_list.end(); }
|
||||||
bool requires_uniform_scale() const;
|
bool requires_uniform_scale() const;
|
||||||
|
|
||||||
// Returns the the object id if the selection is from a single object, otherwise is -1
|
// Returns the the object id if the selection is from a single object, otherwise is -1
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue