Merge branch 'et_world_coordinates' into emboss_local

# Conflicts:
#	src/slic3r/GUI/Plater.cpp
This commit is contained in:
Filip Sykala 2021-12-02 13:05:43 +01:00
commit 8699d04dc9
43 changed files with 3502 additions and 409 deletions

View file

@ -175,6 +175,9 @@ void AppConfig::set_defaults()
if (get("show_hints").empty())
set("show_hints", "1");
if (get("allow_ip_resolve").empty())
set("allow_ip_resolve", "1");
#ifdef _WIN32
if (get("use_legacy_3DConnexion").empty())
set("use_legacy_3DConnexion", "0");

View file

@ -69,6 +69,16 @@ add_library(libslic3r STATIC
Fill/FillPlanePath.hpp
Fill/FillLine.cpp
Fill/FillLine.hpp
Fill/FillLightning.cpp
Fill/FillLightning.hpp
Fill/Lightning/DistanceField.cpp
Fill/Lightning/DistanceField.hpp
Fill/Lightning/Generator.cpp
Fill/Lightning/Generator.hpp
Fill/Lightning/Layer.cpp
Fill/Lightning/Layer.hpp
Fill/Lightning/TreeNode.cpp
Fill/Lightning/TreeNode.hpp
Fill/FillRectilinear.cpp
Fill/FillRectilinear.hpp
Flow.cpp

View file

@ -19,6 +19,7 @@
#include "FillLine.hpp"
#include "FillRectilinear.hpp"
#include "FillAdaptive.hpp"
#include "FillLightning.hpp"
// #define INFILL_DEBUG_OUTPUT
@ -45,6 +46,9 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipAdaptiveCubic: return new FillAdaptive::Filler();
case ipSupportCubic: return new FillAdaptive::Filler();
case ipSupportBase: return new FillSupportBase();
#if HAS_LIGHTNING_INFILL
case ipLightning: return new FillLightning::Filler();
#endif // HAS_LIGHTNING_INFILL
default: throw Slic3r::InvalidArgument("unknown type");
}
}

View file

@ -13,10 +13,10 @@
#include "../BoundingBox.hpp"
#include "../Exception.hpp"
#include "../Utils.hpp"
#include "../ExPolygon.hpp"
namespace Slic3r {
class ExPolygon;
class Surface;
enum InfillPattern : int;

View file

@ -0,0 +1,29 @@
#include "../Print.hpp"
#include "FillLightning.hpp"
#include "Lightning/Generator.hpp"
#include "../Surface.hpp"
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <numeric>
namespace Slic3r::FillLightning {
Polylines Filler::fill_surface(const Surface *surface, const FillParams &params)
{
const Layer &layer = generator->getTreesForLayer(this->layer_id);
return layer.convertToLines(to_polygons(surface->expolygon), generator->infilll_extrusion_width());
}
void GeneratorDeleter::operator()(Generator *p) {
delete p;
}
GeneratorPtr build_generator(const PrintObject &print_object)
{
return GeneratorPtr(new Generator(print_object));
}
} // namespace Slic3r::FillAdaptive

View file

@ -0,0 +1,36 @@
#ifndef slic3r_FillLightning_hpp_
#define slic3r_FillLightning_hpp_
#include "FillBase.hpp"
namespace Slic3r {
class PrintObject;
namespace FillLightning {
class Generator;
// To keep the definition of Octree opaque, we have to define a custom deleter.
struct GeneratorDeleter { void operator()(Generator *p); };
using GeneratorPtr = std::unique_ptr<Generator, GeneratorDeleter>;
GeneratorPtr build_generator(const PrintObject &print_object);
class Filler : public Slic3r::Fill
{
public:
~Filler() override = default;
Generator *generator { nullptr };
protected:
Fill* clone() const override { return new Filler(*this); }
// Perform the fill.
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
// Let the G-code export reoder the infill lines.
bool no_sort() const override { return false; }
};
} // namespace FillAdaptive
} // namespace Slic3r
#endif // slic3r_FillLightning_hpp_

View file

@ -3041,5 +3041,40 @@ Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams
return polylines_out;
}
} // namespace Slic3r
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing)
{
ExPolygonWithOffset poly_with_offset(expolygon, 0, 0, 0);
BoundingBox bounding_box = poly_with_offset.bounding_box_src();
std::vector<SegmentedIntersectionLine> segs = slice_region_by_vertical_lines(
poly_with_offset,
(bounding_box.max.x() - bounding_box.min.x() + spacing - 1) / spacing,
bounding_box.min.x(),
spacing);
Points out;
for (const SegmentedIntersectionLine &sil : segs) {
for (size_t i = 0; i < sil.intersections.size(); i += 2) {
coord_t a = sil.intersections[i].pos();
coord_t b = sil.intersections[i + 1].pos();
for (coord_t y = a - (a % spacing) - spacing; y < b; y += spacing)
if (y > a)
out.emplace_back(sil.pos, y);
}
}
return out;
}
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing)
{
Points out;
for (const ExPolygon &expoly : expolygons)
append(out, sample_grid_pattern(expoly, spacing));
return out;
}
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing)
{
return sample_grid_pattern(union_ex(polygons), spacing);
}
} // namespace Slic3r

View file

@ -109,6 +109,10 @@ protected:
float _layer_angle(size_t idx) const override { return 0.f; }
};
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing);
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing);
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing);
} // namespace Slic3r
#endif // slic3r_FillRectilinear_hpp_

View file

@ -0,0 +1,99 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "DistanceField.hpp" //Class we're implementing.
#include "../FillRectilinear.hpp"
#include "../../ClipperUtils.hpp"
namespace Slic3r::FillLightning
{
constexpr coord_t radius_per_cell_size = 6; // The cell-size should be small compared to the radius, but not so small as to be inefficient.
DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang) :
m_cell_size(radius / radius_per_cell_size),
m_supporting_radius(radius)
{
m_supporting_radius2 = double(radius) * double(radius);
// Sample source polygons with a regular grid sampling pattern.
for (const ExPolygon &expoly : union_ex(current_outline)) {
for (const Point &p : sample_grid_pattern(expoly, m_cell_size)) {
// Find a squared distance to the source expolygon boundary.
double d2 = std::numeric_limits<double>::max();
for (size_t icontour = 0; icontour <= expoly.holes.size(); ++ icontour) {
const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1];
if (contour.size() > 2) {
Point prev = contour.points.back();
for (const Point &p2 : contour.points) {
d2 = std::min(d2, Line::distance_to_squared(p, prev, p2));
prev = p2;
}
}
}
m_unsupported_points.emplace_back(p, sqrt(d2));
}
}
m_unsupported_points.sort([&radius](const UnsupportedCell &a, const UnsupportedCell &b) {
constexpr coord_t prime_for_hash = 191;
return std::abs(b.dist_to_boundary - a.dist_to_boundary) > radius ?
a.dist_to_boundary < b.dist_to_boundary :
(PointHash{}(a.loc) % prime_for_hash) < (PointHash{}(b.loc) % prime_for_hash);
});
for (auto it = m_unsupported_points.begin(); it != m_unsupported_points.end(); ++it) {
UnsupportedCell& cell = *it;
m_unsupported_points_grid.emplace(Point{ cell.loc.x() / m_cell_size, cell.loc.y() / m_cell_size }, it);
}
}
void DistanceField::update(const Point& to_node, const Point& added_leaf)
{
Vec2d v = (added_leaf - to_node).cast<double>();
auto l2 = v.squaredNorm();
Vec2d extent = Vec2d(-v.y(), v.x()) * m_supporting_radius / sqrt(l2);
BoundingBox grid;
{
Point diagonal(m_supporting_radius, m_supporting_radius);
Point iextent(extent.cast<coord_t>());
grid = BoundingBox(added_leaf - diagonal, added_leaf + diagonal);
grid.merge(to_node - iextent);
grid.merge(to_node + iextent);
grid.merge(added_leaf - iextent);
grid.merge(added_leaf + iextent);
grid.min /= m_cell_size;
grid.max /= m_cell_size;
}
Point grid_loc;
for (coord_t row = grid.min.y(); row <= grid.max.y(); ++ row) {
grid_loc.y() = row * m_cell_size;
for (coord_t col = grid.min.x(); col <= grid.max.y(); ++ col) {
grid_loc.x() = col * m_cell_size;
// Test inside a circle at the new leaf.
if ((grid_loc - added_leaf).cast<double>().squaredNorm() > m_supporting_radius2) {
// Not inside a circle at the end of the new leaf.
// Test inside a rotated rectangle.
Vec2d vx = (grid_loc - to_node).cast<double>();
double d = v.dot(vx);
if (d >= 0 && d <= l2) {
d = extent.dot(vx);
if (d < -1. || d > 1.)
// Not inside a rotated rectangle.
continue;
}
}
// Inside a circle at the end of the new leaf, or inside a rotated rectangle.
// Remove unsupported leafs at this grid location.
if (auto it = m_unsupported_points_grid.find(grid_loc); it != m_unsupported_points_grid.end()) {
std::list<UnsupportedCell>::iterator& list_it = it->second;
UnsupportedCell& cell = *list_it;
if ((cell.loc - added_leaf).cast<double>().squaredNorm() <= m_supporting_radius2) {
m_unsupported_points.erase(list_it);
m_unsupported_points_grid.erase(it);
}
}
}
}
}
} // namespace Slic3r::FillLightning

View file

@ -0,0 +1,100 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef LIGHTNING_DISTANCE_FIELD_H
#define LIGHTNING_DISTANCE_FIELD_H
#include "../../Point.hpp"
#include "../../Polygon.hpp"
namespace Slic3r::FillLightning
{
/*!
* 2D field that maintains locations which need to be supported for Lightning
* Infill.
*
* This field contains a set of "cells", spaced out in a grid. Each cell
* maintains how far it is removed from the edge, which is used to determine
* how it gets supported by Lightning Infill.
*/
class DistanceField
{
public:
/*!
* Construct a new field to calculate Lightning Infill with.
* \param radius The radius of influence that an infill line is expected to
* support in the layer above.
* \param current_outline The total infill area on this layer.
* \param current_overhang The overhang that needs to be supported on this
* layer.
*/
DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang);
/*!
* Gets the next unsupported location to be supported by a new branch.
* \param p Output variable for the next point to support.
* \return ``true`` if successful, or ``false`` if there are no more points
* to consider.
*/
bool tryGetNextPoint(Point* p) const {
if (m_unsupported_points.empty())
return false;
*p = m_unsupported_points.front().loc;
return true;
}
/*!
* Update the distance field with a newly added branch.
*
* The branch is a line extending from \p to_node to \p added_leaf . This
* function updates the grid cells so that the distance field knows how far
* off it is from being supported by the current pattern. Grid points are
* updated with sampling points spaced out by the supporting radius along
* the line.
* \param to_node The node endpoint of the newly added branch.
* \param added_leaf The location of the leaf of the newly added branch,
* drawing a straight line to the node.
*/
void update(const Point& to_node, const Point& added_leaf);
protected:
/*!
* Spacing between grid points to consider supporting.
*/
coord_t m_cell_size;
/*!
* The radius of the area of the layer above supported by a point on a
* branch of a tree.
*/
coord_t m_supporting_radius;
double m_supporting_radius2;
/*!
* Represents a small discrete area of infill that needs to be supported.
*/
struct UnsupportedCell
{
UnsupportedCell(Point loc, coord_t dist_to_boundary) : loc(loc), dist_to_boundary(dist_to_boundary) {}
// The position of the center of this cell.
Point loc;
// How far this cell is removed from the ``current_outline`` polygon, the edge of the infill area.
coord_t dist_to_boundary;
};
/*!
* Cells which still need to be supported at some point.
*/
std::list<UnsupportedCell> m_unsupported_points;
/*!
* Links the unsupported points to a grid point, so that we can quickly look
* up the cell belonging to a certain position in the grid.
*/
std::unordered_map<Point, std::list<UnsupportedCell>::iterator, PointHash> m_unsupported_points_grid;
};
} // namespace Slic3r::FillLightning
#endif //LIGHTNING_DISTANCE_FIELD_H

View file

@ -0,0 +1,127 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "Generator.hpp"
#include "TreeNode.hpp"
#include "../../ClipperUtils.hpp"
#include "../../Layer.hpp"
#include "../../Print.hpp"
#include "../../Surface.hpp"
/* Possible future tasks/optimizations,etc.:
* - Improve connecting heuristic to favor connecting to shorter trees
* - Change which node of a tree is the root when that would be better in reconnectRoots.
* - (For implementation in Infill classes & elsewhere): Outline offset, infill-overlap & perimeter gaps.
* - Allow for polylines, i.e. merge Tims PR about polyline fixes
* - Unit Tests?
* - Optimization: let the square grid store the closest point on boundary
* - Optimization: only compute the closest dist to / point on boundary for the outer cells and flood-fill the rest
* - Make a pass with Arachne over the output. Somehow.
* - Generate all to-be-supported points at once instead of sequentially: See branch interlocking_gen PolygonUtils::spreadDots (Or work with sparse grids.)
* - Lots of magic values ... to many to parameterize. But are they the best?
* - Move more complex computations from Generator constructor to elsewhere.
*/
namespace Slic3r::FillLightning {
Generator::Generator(const PrintObject &print_object)
{
const PrintConfig &print_config = print_object.print()->config();
const PrintObjectConfig &object_config = print_object.config();
const PrintRegionConfig &region_config = print_object.shared_regions()->all_regions.front()->config();
const std::vector<double> &nozzle_diameters = print_config.nozzle_diameter.values;
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
// const int infill_extruder = region_config.infill_extruder.value;
const double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
// Note: There's not going to be a layer below the first one, so the 'initial layer height' doesn't have to be taken into account.
const double layer_thickness = object_config.layer_height;
m_infill_extrusion_width = scaled<float>(region_config.infill_extrusion_width.percent ? default_infill_extrusion_width * 0.01 * region_config.infill_extrusion_width : region_config.infill_extrusion_width);
m_supporting_radius = scaled<coord_t>(m_infill_extrusion_width * 0.001 / region_config.fill_density);
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
const double lightning_infill_straightening_angle = M_PI / 4; // 45 degrees
m_wall_supporting_radius = layer_thickness * std::tan(lightning_infill_overhang_angle);
m_prune_length = layer_thickness * std::tan(lightning_infill_prune_angle);
m_straightening_max_distance = layer_thickness * std::tan(lightning_infill_straightening_angle);
generateInitialInternalOverhangs(print_object);
generateTrees(print_object);
}
void Generator::generateInitialInternalOverhangs(const PrintObject &print_object)
{
m_overhang_per_layer.resize(print_object.layers().size());
const float infill_wall_offset = - m_infill_extrusion_width;
Polygons infill_area_above;
//Iterate from top to bottom, to subtract the overhang areas above from the overhang areas on the layer below, to get only overhang in the top layer where it is overhanging.
for (int layer_nr = print_object.layers().size() - 1; layer_nr >= 0; layer_nr--) {
Polygons infill_area_here;
for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions())
for (const Surface& surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternal)
append(infill_area_here, offset(surface.expolygon, infill_wall_offset));
//Remove the part of the infill area that is already supported by the walls.
Polygons overhang = diff(offset(infill_area_here, -m_wall_supporting_radius), infill_area_above);
m_overhang_per_layer[layer_nr] = overhang;
infill_area_above = std::move(infill_area_here);
}
}
const Layer& Generator::getTreesForLayer(const size_t& layer_id) const
{
assert(layer_id < m_lightning_layers.size());
return m_lightning_layers[layer_id];
}
void Generator::generateTrees(const PrintObject &print_object)
{
m_lightning_layers.resize(print_object.layers().size());
const coord_t infill_wall_offset = - m_infill_extrusion_width;
std::vector<Polygons> infill_outlines(print_object.layers().size(), Polygons());
// For-each layer from top to bottom:
for (int layer_id = print_object.layers().size() - 1; layer_id >= 0; layer_id--)
for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions())
for (const Surface &surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternal)
append(infill_outlines[layer_id], offset(surface.expolygon, infill_wall_offset));
// For various operations its beneficial to quickly locate nearby features on the polygon:
const size_t top_layer_id = print_object.layers().size() - 1;
EdgeGrid::Grid outlines_locator(get_extents(infill_outlines[top_layer_id]).inflated(SCALED_EPSILON));
outlines_locator.create(infill_outlines[top_layer_id], locator_cell_size);
// For-each layer from top to bottom:
for (int layer_id = top_layer_id; layer_id >= 0; layer_id--)
{
Layer& current_lightning_layer = m_lightning_layers[layer_id];
Polygons& current_outlines = infill_outlines[layer_id];
// register all trees propagated from the previous layer as to-be-reconnected
std::vector<NodeSPtr> to_be_reconnected_tree_roots = current_lightning_layer.tree_roots;
current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
// Initialize trees for next lower layer from the current one.
if (layer_id == 0)
return;
const Polygons& below_outlines = infill_outlines[layer_id - 1];
outlines_locator.set_bbox(get_extents(below_outlines).inflated(SCALED_EPSILON));
outlines_locator.create(below_outlines, locator_cell_size);
std::vector<NodeSPtr>& lower_trees = m_lightning_layers[layer_id - 1].tree_roots;
for (auto& tree : current_lightning_layer.tree_roots)
tree->propagateToNextLayer(lower_trees, below_outlines, outlines_locator, m_prune_length, m_straightening_max_distance, locator_cell_size / 2);
}
}
} // namespace Slic3r::FillLightning

View file

@ -0,0 +1,132 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef LIGHTNING_GENERATOR_H
#define LIGHTNING_GENERATOR_H
#include "Layer.hpp"
#include <functional>
#include <memory>
#include <vector>
namespace Slic3r
{
class PrintObject;
namespace FillLightning
{
/*!
* Generates the Lightning Infill pattern.
*
* The lightning infill pattern is designed to use a minimal amount of material
* to support the top skin of the print, while still printing with reasonably
* consistently flowing lines. It sacrifices strength completely in favour of
* top surface quality and reduced print time / material usage.
*
* Lightning Infill is so named because the patterns it creates resemble a
* forked path with one main path and many small lines on the side. These paths
* grow out from the sides of the model just below where the top surface needs
* to be supported from the inside, so that minimal material is needed.
*
* This pattern is based on a paper called "Ribbed Support Vaults for 3D
* Printing of Hollowed Objects" by Tricard, Claux and Lefebvre:
* https://www.researchgate.net/publication/333808588_Ribbed_Support_Vaults_for_3D_Printing_of_Hollowed_Objects
*/
class Generator // "Just like Nicola used to make!"
{
public:
/*!
* Create a generator to fill a certain mesh with infill.
*
* This generator will pre-compute things in preparation of generating
* Lightning Infill for the infill areas in that mesh. The infill areas must
* already be calculated at this point.
* \param mesh The mesh to generate infill for.
*/
Generator(const PrintObject &print_object);
/*!
* Get a tree of paths generated for a certain layer of the mesh.
*
* This tree represents the paths that must be traced to print the infill.
* \param layer_id The layer number to get the path tree for. This is within
* the range of layers of the mesh (not the global layer numbers).
* \return A tree structure representing paths to print to create the
* Lightning Infill pattern.
*/
const Layer& getTreesForLayer(const size_t& layer_id) const;
float infilll_extrusion_width() const { return m_infill_extrusion_width; }
protected:
/*!
* Calculate the overhangs above the infill areas that need to be supported
* by infill.
*
* Normally, overhangs are only generated for the outside of the model and
* only when support is generated. For this pattern, we also need to
* generate overhang areas for the inside of the model.
*/
void generateInitialInternalOverhangs(const PrintObject &print_object);
/*!
* Calculate the tree structure of all layers.
*/
void generateTrees(const PrintObject &print_object);
float m_infill_extrusion_width;
/*!
* How far each piece of infill can support skin in the layer above.
*/
coord_t m_supporting_radius;
/*!
* How far a wall can support the wall above it. If a wall completely
* supports the wall above it, no infill needs to support that.
*
* This is similar to the overhang distance calculated for support. It is
* determined by the lightning_infill_overhang_angle setting.
*/
coord_t m_wall_supporting_radius;
/*!
* How far each piece of infill can support other infill in the layer above.
*
* This may be different than \ref supporting_radius, because the infill is
* printed with one end floating in mid-air. This endpoint will sag more, so
* an infill line may need to be supported more than a skin line.
*/
coord_t m_prune_length;
/*!
* How far a line may be shifted in order to straighten the line out.
*
* Straightening the line reduces material and time usage and reduces
* accelerations needed to print the pattern. However it makes the infill
* weak if lines are partially suspended next to the line on the previous
* layer.
*/
coord_t m_straightening_max_distance;
/*!
* For each layer, the overhang that needs to be supported by the pattern.
*
* This is generated by \ref generateInitialInternalOverhangs.
*/
std::vector<Polygons> m_overhang_per_layer;
/*!
* For each layer, the generated lightning paths.
*
* This is generated by \ref generateTrees.
*/
std::vector<Layer> m_lightning_layers;
};
} // namespace FillLightning
} // namespace Slic3r
#endif // LIGHTNING_GENERATOR_H

View file

@ -0,0 +1,410 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "Layer.hpp" //The class we're implementing.
#include <iterator> // advance
#include "DistanceField.hpp"
#include "TreeNode.hpp"
#include "../../Geometry.hpp"
namespace Slic3r::FillLightning {
coord_t Layer::getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location)
{
return coord_t((boundary_loc - unsupported_location).cast<double>().norm());
}
Point GroundingLocation::p() const
{
assert(tree_node || boundary_location);
return tree_node ? tree_node->getLocation() : *boundary_location;
}
void Layer::fillLocator(SparseNodeGrid &tree_node_locator)
{
std::function<void(NodeSPtr)> add_node_to_locator_func = [&tree_node_locator](NodeSPtr node) {
tree_node_locator.insert(std::make_pair(Point(node->getLocation().x() / locator_cell_size, node->getLocation().y() / locator_cell_size), node));
};
for (auto& tree : tree_roots)
tree->visitNodes(add_node_to_locator_func);
}
void Layer::generateNewTrees
(
const Polygons& current_overhang,
const Polygons& current_outlines,
const EdgeGrid::Grid& outlines_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
)
{
DistanceField distance_field(supporting_radius, current_outlines, current_overhang);
SparseNodeGrid tree_node_locator;
fillLocator(tree_node_locator);
// Until no more points need to be added to support all:
// Determine next point from tree/outline areas via distance-field
Point unsupported_location;
while (distance_field.tryGetNextPoint(&unsupported_location)) {
GroundingLocation grounding_loc = getBestGroundingLocation(
unsupported_location, current_outlines, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator);
NodeSPtr new_parent;
NodeSPtr new_child;
this->attach(unsupported_location, grounding_loc, new_child, new_parent);
tree_node_locator.insert(std::make_pair(Point(new_child->getLocation().x() / locator_cell_size, new_child->getLocation().y() / locator_cell_size), new_child));
if (new_parent)
tree_node_locator.insert(std::make_pair(Point(new_parent->getLocation().x() / locator_cell_size, new_parent->getLocation().y() / locator_cell_size), new_parent));
// update distance field
distance_field.update(grounding_loc.p(), unsupported_location);
}
}
static bool polygonCollidesWithLineSegment(const Point from, const Point to, const EdgeGrid::Grid &loc_to_line)
{
struct Visitor {
explicit Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
bool operator()(coord_t iy, coord_t ix) {
// Called with a row and colum of the grid cell, which is intersected by a line.
auto cell_data_range = grid.cell_data_range(iy, ix);
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
// End points of the line segment and their vector.
auto segment = grid.segment(*it_contour_and_segment);
if (Geometry::segments_intersect(segment.first, segment.second, line.a, line.b)) {
this->intersect = true;
return false;
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid& grid;
Line line;
bool intersect = false;
} visitor(loc_to_line);
loc_to_line.visit_cells_intersecting_line(from, to, visitor);
return visitor.intersect;
}
GroundingLocation Layer::getBestGroundingLocation
(
const Point& unsupported_location,
const Polygons& current_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius,
const SparseNodeGrid& tree_node_locator,
const NodeSPtr& exclude_tree
)
{
// Closest point on current_outlines to unsupported_location:
Point node_location;
{
double d2 = std::numeric_limits<double>::max();
for (const Polygon &contour : current_outlines)
if (contour.size() > 2) {
Point prev = contour.points.back();
for (const Point &p2 : contour.points) {
if (double d = Line::distance_to_squared(unsupported_location, prev, p2); d < d2) {
d2 = d;
node_location = Geometry::foot_pt({ prev, p2 }, unsupported_location).cast<coord_t>();
}
prev = p2;
}
}
}
const auto within_dist = coord_t((node_location - unsupported_location).cast<double>().norm());
NodeSPtr sub_tree{ nullptr };
coord_t current_dist = getWeightedDistance(node_location, unsupported_location);
if (current_dist >= wall_supporting_radius) { // Only reconnect tree roots to other trees if they are not already close to the outlines.
const coord_t search_radius = std::min(current_dist, within_dist);
BoundingBox region(unsupported_location - Point(search_radius, search_radius), unsupported_location + Point(search_radius + locator_cell_size, search_radius + locator_cell_size));
region.min /= locator_cell_size;
region.max /= locator_cell_size;
Point grid_addr;
for (grid_addr.y() = region.min.y(); grid_addr.y() < region.max.y(); ++ grid_addr.y())
for (grid_addr.x() = region.min.x(); grid_addr.x() < region.max.x(); ++ grid_addr.x()) {
auto it_range = tree_node_locator.equal_range(grid_addr);
for (auto it = it_range.first; it != it_range.second; ++ it) {
auto candidate_sub_tree = it->second.lock();
if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) &&
!(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) &&
!polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) {
const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius);
if (candidate_dist < current_dist) {
current_dist = candidate_dist;
sub_tree = candidate_sub_tree;
}
}
}
}
}
return ! sub_tree ?
GroundingLocation{ nullptr, node_location } :
GroundingLocation{ sub_tree, std::optional<Point>() };
}
bool Layer::attach(
const Point& unsupported_location,
const GroundingLocation& grounding_loc,
NodeSPtr& new_child,
NodeSPtr& new_root)
{
// Update trees & distance fields.
if (grounding_loc.boundary_location) {
new_root = Node::create(grounding_loc.p(), std::make_optional(grounding_loc.p()));
new_child = new_root->addChild(unsupported_location);
tree_roots.push_back(new_root);
return true;
} else {
new_child = grounding_loc.tree_node->addChild(unsupported_location);
return false;
}
}
void Layer::reconnectRoots
(
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
const Polygons& current_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
)
{
constexpr coord_t tree_connecting_ignore_offset = 100;
SparseNodeGrid tree_node_locator;
fillLocator(tree_node_locator);
const coord_t within_max_dist = outline_locator.resolution() * 2;
for (auto root_ptr : to_be_reconnected_tree_roots)
{
auto old_root_it = std::find(tree_roots.begin(), tree_roots.end(), root_ptr);
if (root_ptr->getLastGroundingLocation())
{
const Point& ground_loc = *root_ptr->getLastGroundingLocation();
if (ground_loc != root_ptr->getLocation())
{
Point new_root_pt;
// Find an intersection of the line segment from root_ptr->getLocation() to ground_loc, at within_max_dist from ground_loc.
if (lineSegmentPolygonsIntersection(root_ptr->getLocation(), ground_loc, outline_locator, new_root_pt, within_max_dist)) {
auto new_root = Node::create(new_root_pt, new_root_pt);
root_ptr->addChild(new_root);
new_root->reroot();
tree_node_locator.insert(std::make_pair(Point(new_root->getLocation().x() / locator_cell_size, new_root->getLocation().y() / locator_cell_size), new_root));
*old_root_it = std::move(new_root); // replace old root with new root
continue;
}
}
}
const coord_t tree_connecting_ignore_width = wall_supporting_radius - tree_connecting_ignore_offset; // Ideally, the boundary size in which the valence rule is ignored would be configurable.
GroundingLocation ground =
getBestGroundingLocation
(
root_ptr->getLocation(),
current_outlines,
outline_locator,
supporting_radius,
tree_connecting_ignore_width,
tree_node_locator,
root_ptr
);
if (ground.boundary_location)
{
if (*ground.boundary_location == root_ptr->getLocation())
continue; // Already on the boundary.
auto new_root = Node::create(ground.p(), ground.p());
auto attach_ptr = root_ptr->closestNode(new_root->getLocation());
attach_ptr->reroot();
new_root->addChild(attach_ptr);
tree_node_locator.insert(std::make_pair(new_root->getLocation(), new_root));
*old_root_it = std::move(new_root); // replace old root with new root
}
else
{
assert(ground.tree_node);
assert(ground.tree_node != root_ptr);
assert(!root_ptr->hasOffspring(ground.tree_node));
assert(!ground.tree_node->hasOffspring(root_ptr));
auto attach_ptr = root_ptr->closestNode(ground.tree_node->getLocation());
attach_ptr->reroot();
ground.tree_node->addChild(attach_ptr);
// remove old root
*old_root_it = std::move(tree_roots.back());
tree_roots.pop_back();
}
}
}
/*
* Implementation assumes moving inside, but moving outside should just as well be possible.
*/
static unsigned int moveInside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
{
Point ret = from;
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
unsigned int bestPoly = static_cast<unsigned int>(-1);
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
{
const Polygon &poly = polygons[poly_idx];
if (poly.size() < 2)
continue;
Point p0 = poly[poly.size() - 2];
Point p1 = poly.back();
// because we compare with vSize2 here (no division by zero), we also need to compare by vSize2 inside the loop
// to avoid integer rounding edge cases
bool projected_p_beyond_prev_segment = (p1 - p0).cast<int64_t>().dot((from - p0).cast<int64_t>()) >= (p1 - p0).cast<int64_t>().squaredNorm();
for (const Point& p2 : poly)
{
// X = A + Normal(B-A) * (((B-A) dot (P-A)) / VSize(B-A));
// = A + (B-A) * ((B-A) dot (P-A)) / VSize2(B-A);
// X = P projected on AB
const Point& a = p1;
const Point& b = p2;
const Point& p = from;
Point ab = b - a;
Point ap = p - a;
int64_t ab_length2 = ab.cast<int64_t>().squaredNorm();
if (ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other.
{
p1 = p2; //Skip only one of the points.
continue;
}
int64_t dot_prod = ab.cast<int64_t>().dot(ap.cast<int64_t>());
if (dot_prod <= 0) // x is projected to before ab
{
if (projected_p_beyond_prev_segment)
{ // case which looks like: > .
projected_p_beyond_prev_segment = false;
Point& x = p1;
int64_t dist2 = (x - p).cast<int64_t>().squaredNorm();
if (dist2 < bestDist2)
{
bestDist2 = dist2;
bestPoly = poly_idx;
if (distance == 0) {
ret = x;
} else {
// inward direction irrespective of sign of [distance]
Point inward_dir = perp((ab.cast<double>().normalized() * scaled<double>(10.0) + (p1 - p0).cast<double>().normalized() * scaled<double>(10.0)).eval()).cast<coord_t>();
// MM2INT(10.0) to retain precision for the eventual normalization
ret = x + (inward_dir.cast<double>().normalized() * distance).cast<coord_t>();
is_already_on_correct_side_of_boundary = inward_dir.cast<int64_t>().dot((p - x).cast<int64_t>()) * distance >= 0;
}
}
}
else
{
projected_p_beyond_prev_segment = false;
p0 = p1;
p1 = p2;
continue;
}
}
else if (dot_prod >= ab_length2) // x is projected to beyond ab
{
projected_p_beyond_prev_segment = true;
p0 = p1;
p1 = p2;
continue;
}
else
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
projected_p_beyond_prev_segment = false;
Point x = a + ab * dot_prod / ab_length2;
int64_t dist2 = (p - x).cast<int64_t>().squaredNorm();
if (dist2 < bestDist2)
{
bestDist2 = dist2;
bestPoly = poly_idx;
if (distance == 0) { ret = x; }
else
{
// inward or outward depending on the sign of [distance]
Vec2d inward_dir = perp((ab.cast<double>().normalized() * distance).eval());
ret = x + inward_dir.cast<coord_t>();
is_already_on_correct_side_of_boundary = inward_dir.dot((p - x).cast<double>()) >= 0;
}
}
}
p0 = p1;
p1 = p2;
}
}
if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
{
if (bestDist2 < distance * distance)
{
from = ret;
}
else
{
// from = from; // original point stays unaltered. It is already inside by enough distance
}
return bestPoly;
}
else if (bestDist2 < maxDist2)
{
from = ret;
return bestPoly;
}
return static_cast<unsigned int>(-1);
}
// Returns 'added someting'.
Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const
{
if (tree_roots.empty())
return {};
Polygons result_lines;
for (const auto& tree : tree_roots) {
// If even the furthest location in the tree is inside the polygon, the entire tree must be inside of the polygon.
// (Don't take the root as that may be on the edge and cause rounding errors to register as 'outside'.)
constexpr coord_t epsilon = 5;
Point should_be_inside = tree->getLocation();
moveInside(limit_to_outline, should_be_inside, epsilon, epsilon * epsilon);
if (inside(limit_to_outline, should_be_inside))
tree->convertToPolylines(result_lines, line_width);
}
// TODO: allow for polylines!
Polylines split_lines;
for (Polygon &line : result_lines) {
if (line.size() <= 1)
continue;
Point last = line[0];
for (size_t point_idx = 1; point_idx < line.size(); point_idx++) {
Point here = line[point_idx];
split_lines.push_back({ last, here });
last = here;
}
}
return split_lines;
}
} // namespace Slic3r::Lightning

View file

@ -0,0 +1,88 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef LIGHTNING_LAYER_H
#define LIGHTNING_LAYER_H
#include "../../EdgeGrid.hpp"
#include "../../Polygon.hpp"
#include <memory>
#include <vector>
#include <list>
#include <unordered_map>
#include <optional>
namespace Slic3r::FillLightning
{
class Node;
using NodeSPtr = std::shared_ptr<Node>;
using SparseNodeGrid = std::unordered_multimap<Point, std::weak_ptr<Node>, PointHash>;
struct GroundingLocation
{
NodeSPtr tree_node; //!< not null if the gounding location is on a tree
std::optional<Point> boundary_location; //!< in case the gounding location is on the boundary
Point p() const;
};
/*!
* A layer of the lightning fill.
*
* Contains the trees to be printed and propagated to the next layer below.
*/
class Layer
{
public:
std::vector<NodeSPtr> tree_roots;
void generateNewTrees
(
const Polygons& current_overhang,
const Polygons& current_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
);
/*! Determine & connect to connection point in tree/outline.
* \param min_dist_from_boundary_for_tree If the unsupported point is closer to the boundary than this then don't consider connecting it to a tree
*/
GroundingLocation getBestGroundingLocation
(
const Point& unsupported_location,
const Polygons& current_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius,
const SparseNodeGrid& tree_node_locator,
const NodeSPtr& exclude_tree = nullptr
);
/*!
* \param[out] new_child The new child node introduced
* \param[out] new_root The new root node if one had been made
* \return Whether a new root was added
*/
bool attach(const Point& unsupported_location, const GroundingLocation& ground, NodeSPtr& new_child, NodeSPtr& new_root);
void reconnectRoots
(
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
const Polygons& current_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
);
Polylines convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const;
coord_t getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location);
void fillLocator(SparseNodeGrid& tree_node_locator);
};
} // namespace Slic3r::FillLightning
#endif // LIGHTNING_LAYER_H

View file

@ -0,0 +1,411 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include "TreeNode.hpp"
#include "../../Geometry.hpp"
#include "../../ClipperUtils.hpp"
namespace Slic3r::FillLightning {
coord_t Node::getWeightedDistance(const Point& unsupported_location, const coord_t& supporting_radius) const
{
constexpr coord_t min_valence_for_boost = 0;
constexpr coord_t max_valence_for_boost = 4;
constexpr coord_t valence_boost_multiplier = 4;
const size_t valence = (!m_is_root) + m_children.size();
const coord_t valence_boost = (min_valence_for_boost < valence && valence < max_valence_for_boost) ? valence_boost_multiplier * supporting_radius : 0;
const auto dist_here = coord_t((getLocation() - unsupported_location).cast<double>().norm());
return dist_here - valence_boost;
}
bool Node::hasOffspring(const NodeSPtr& to_be_checked) const
{
if (to_be_checked == shared_from_this())
return true;
for (auto& child_ptr : m_children)
if (child_ptr->hasOffspring(to_be_checked))
return true;
return false;
}
NodeSPtr Node::addChild(const Point& child_loc)
{
assert(m_p != child_loc);
NodeSPtr child = Node::create(child_loc);
return addChild(child);
}
NodeSPtr Node::addChild(NodeSPtr& new_child)
{
assert(new_child != shared_from_this());
//assert(p != new_child->p); // NOTE: No problem for now. Issue to solve later. Maybe even afetr final. Low prio.
m_children.push_back(new_child);
new_child->m_parent = shared_from_this();
new_child->m_is_root = false;
return new_child;
}
void Node::propagateToNextLayer(
std::vector<NodeSPtr>& next_trees,
const Polygons& next_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t prune_distance,
const coord_t smooth_magnitude,
const coord_t max_remove_colinear_dist) const
{
auto tree_below = deepCopy();
tree_below->prune(prune_distance);
tree_below->straighten(smooth_magnitude, max_remove_colinear_dist);
if (tree_below->realign(next_outlines, outline_locator, next_trees))
next_trees.push_back(tree_below);
}
// NOTE: Depth-first, as currently implemented.
// Skips the root (because that has no root itself), but all initial nodes will have the root point anyway.
void Node::visitBranches(const std::function<void(const Point&, const Point&)>& visitor) const
{
for (const auto& node : m_children) {
assert(node->m_parent.lock() == shared_from_this());
visitor(m_p, node->m_p);
node->visitBranches(visitor);
}
}
// NOTE: Depth-first, as currently implemented.
void Node::visitNodes(const std::function<void(NodeSPtr)>& visitor)
{
visitor(shared_from_this());
for (const auto& node : m_children) {
assert(node->m_parent.lock() == shared_from_this());
node->visitNodes(visitor);
}
}
Node::Node(const Point& p, const std::optional<Point>& last_grounding_location /*= std::nullopt*/) :
m_is_root(true), m_p(p), m_last_grounding_location(last_grounding_location)
{}
NodeSPtr Node::deepCopy() const
{
NodeSPtr local_root = Node::create(m_p);
local_root->m_is_root = m_is_root;
if (m_is_root)
{
local_root->m_last_grounding_location = m_last_grounding_location.value_or(m_p);
}
local_root->m_children.reserve(m_children.size());
for (const auto& node : m_children)
{
NodeSPtr child = node->deepCopy();
child->m_parent = local_root;
local_root->m_children.push_back(child);
}
return local_root;
}
void Node::reroot(NodeSPtr new_parent /*= nullptr*/)
{
if (! m_is_root) {
auto old_parent = m_parent.lock();
old_parent->reroot(shared_from_this());
m_children.push_back(old_parent);
}
if (new_parent) {
m_children.erase(std::remove(m_children.begin(), m_children.end(), new_parent), m_children.end());
m_is_root = false;
m_parent = new_parent;
} else {
m_is_root = true;
m_parent.reset();
}
}
NodeSPtr Node::closestNode(const Point& loc)
{
NodeSPtr result = shared_from_this();
auto closest_dist2 = coord_t((m_p - loc).cast<double>().norm());
for (const auto& child : m_children) {
NodeSPtr candidate_node = child->closestNode(loc);
const auto child_dist2 = coord_t((candidate_node->m_p - loc).cast<double>().norm());
if (child_dist2 < closest_dist2) {
closest_dist2 = child_dist2;
result = candidate_node;
}
}
return result;
}
bool inside(const Polygons &polygons, const Point p)
{
int poly_count_inside = 0;
for (const Polygon &poly : polygons) {
const int is_inside_this_poly = ClipperLib::PointInPolygon(p, poly.points);
if (is_inside_this_poly == -1)
return true;
poly_count_inside += is_inside_this_poly;
}
return (poly_count_inside % 2) == 1;
}
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist)
{
struct Visitor {
bool operator()(coord_t iy, coord_t ix) {
// Called with a row and colum of the grid cell, which is intersected by a line.
auto cell_data_range = grid.cell_data_range(iy, ix);
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
// End points of the line segment and their vector.
auto segment = grid.segment(*it_contour_and_segment);
if (Vec2d ip; Geometry::segment_segment_intersection(segment.first.cast<double>(), segment.second.cast<double>(), this->line_a, this->line_b, ip))
if (double d = (this->intersection_pt - this->line_b).squaredNorm(); d < d2min) {
this->d2min = d;
this->intersection_pt = ip;
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid& grid;
Vec2d line_a;
Vec2d line_b;
Vec2d intersection_pt;
double d2min { std::numeric_limits<double>::max() };
} visitor { outline_locator, a.cast<double>(), b.cast<double>() };
outline_locator.visit_cells_intersecting_line(a, b, visitor);
return visitor.d2min < within_max_dist * within_max_dist;
}
bool Node::realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<NodeSPtr>& rerooted_parts)
{
if (outlines.empty())
return false;
if (inside(outlines, m_p)) {
// Only keep children that have an unbroken connection to here, realign will put the rest in rerooted parts due to recursion:
Point coll;
bool reground_me = false;
m_children.erase(std::remove_if(m_children.begin(), m_children.end(), [&](const NodeSPtr &child) {
bool connect_branch = child->realign(outlines, outline_locator, rerooted_parts);
// Find an intersection of the line segment from p to child->p, at maximum outline_locator.resolution() * 2 distance from p.
if (connect_branch && lineSegmentPolygonsIntersection(child->m_p, m_p, outline_locator, coll, outline_locator.resolution() * 2)) {
child->m_last_grounding_location.reset();
child->m_parent.reset();
child->m_is_root = true;
rerooted_parts.push_back(child);
reground_me = true;
connect_branch = false;
}
return ! connect_branch;
}), m_children.end());
if (reground_me)
m_last_grounding_location.reset();
return true;
}
// 'Lift' any decendants out of this tree:
for (auto& child : m_children)
if (child->realign(outlines, outline_locator, rerooted_parts)) {
child->m_last_grounding_location = m_p;
child->m_parent.reset();
child->m_is_root = true;
rerooted_parts.push_back(child);
}
m_children.clear();
return false;
}
void Node::straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist)
{
straighten(magnitude, m_p, 0, max_remove_colinear_dist * max_remove_colinear_dist);
}
Node::RectilinearJunction Node::straighten(
const coord_t magnitude,
const Point& junction_above,
const coord_t accumulated_dist,
const coord_t max_remove_colinear_dist2)
{
constexpr coord_t junction_magnitude_factor_numerator = 3;
constexpr coord_t junction_magnitude_factor_denominator = 4;
const coord_t junction_magnitude = magnitude * junction_magnitude_factor_numerator / junction_magnitude_factor_denominator;
if (m_children.size() == 1)
{
auto child_p = m_children.front();
auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
RectilinearJunction junction_below = child_p->straighten(magnitude, junction_above, accumulated_dist + child_dist, max_remove_colinear_dist2);
coord_t total_dist_to_junction_below = junction_below.total_recti_dist;
Point a = junction_above;
Point b = junction_below.junction_loc;
if (a != b) // should always be true!
{
Point ab = b - a;
Point destination = a + ab * accumulated_dist / std::max(coord_t(1), total_dist_to_junction_below);
if ((destination - m_p).cast<double>().squaredNorm() <= magnitude * magnitude)
m_p = destination;
else
m_p += ((destination - m_p).cast<double>().normalized() * magnitude).cast<coord_t>();
}
{ // remove nodes on linear segments
constexpr coord_t close_enough = 10;
child_p = m_children.front(); //recursive call to straighten might have removed the child
const NodeSPtr& parent_node = m_parent.lock();
if (parent_node &&
(child_p->m_p - parent_node->m_p).cast<double>().squaredNorm() < max_remove_colinear_dist2 &&
Line::distance_to_squared(m_p, parent_node->m_p, child_p->m_p) < close_enough * close_enough) {
child_p->m_parent = m_parent;
for (auto& sibling : parent_node->m_children)
{ // find this node among siblings
if (sibling == shared_from_this())
{
sibling = child_p; // replace this node by child
break;
}
}
}
}
return junction_below;
}
else
{
constexpr coord_t weight = 1000;
Point junction_moving_dir = ((junction_above - m_p).cast<double>().normalized() * weight).cast<coord_t>();
bool prevent_junction_moving = false;
for (auto& child_p : m_children)
{
const auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
RectilinearJunction below = child_p->straighten(magnitude, m_p, child_dist, max_remove_colinear_dist2);
junction_moving_dir += ((below.junction_loc - m_p).cast<double>().normalized() * weight).cast<coord_t>();
if (below.total_recti_dist < magnitude) // TODO: make configurable?
{
prevent_junction_moving = true; // prevent flipflopping in branches due to straightening and junctoin moving clashing
}
}
if (junction_moving_dir != Point(0, 0) && ! m_children.empty() && ! m_is_root && ! prevent_junction_moving)
{
auto junction_moving_dir_len = coord_t(junction_moving_dir.norm());
if (junction_moving_dir_len > junction_magnitude)
{
junction_moving_dir = junction_moving_dir * junction_magnitude / junction_moving_dir_len;
}
m_p += junction_moving_dir;
}
return RectilinearJunction{ accumulated_dist, m_p };
}
}
// Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
coord_t Node::prune(const coord_t& pruning_distance)
{
if (pruning_distance <= 0)
return 0;
coord_t max_distance_pruned = 0;
for (auto child_it = m_children.begin(); child_it != m_children.end(); ) {
auto& child = *child_it;
coord_t dist_pruned_child = child->prune(pruning_distance);
if (dist_pruned_child >= pruning_distance)
{ // pruning is finished for child; dont modify further
max_distance_pruned = std::max(max_distance_pruned, dist_pruned_child);
++child_it;
} else {
const Point a = getLocation();
const Point b = child->getLocation();
const Point ba = a - b;
const auto ab_len = coord_t(ba.cast<double>().norm());
if (dist_pruned_child + ab_len <= pruning_distance) {
// we're still in the process of pruning
assert(child->m_children.empty() && "when pruning away a node all it's children must already have been pruned away");
max_distance_pruned = std::max(max_distance_pruned, dist_pruned_child + ab_len);
child_it = m_children.erase(child_it);
} else {
// pruning stops in between this node and the child
const Point n = b + (ba.cast<double>().normalized() * (pruning_distance - dist_pruned_child)).cast<coord_t>();
assert(std::abs((n - b).cast<double>().norm() + dist_pruned_child - pruning_distance) < 10 && "total pruned distance must be equal to the pruning_distance");
max_distance_pruned = std::max(max_distance_pruned, pruning_distance);
child->setLocation(n);
++child_it;
}
}
}
return max_distance_pruned;
}
void Node::convertToPolylines(Polygons& output, const coord_t line_width) const
{
Polygons result;
output.emplace_back();
convertToPolylines(0, result);
removeJunctionOverlap(result, line_width);
append(output, std::move(result));
}
void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const
{
if (m_children.empty()) {
output[long_line_idx].points.push_back(m_p);
return;
}
size_t first_child_idx = rand() % m_children.size();
m_children[first_child_idx]->convertToPolylines(long_line_idx, output);
output[long_line_idx].points.push_back(m_p);
for (size_t idx_offset = 1; idx_offset < m_children.size(); idx_offset++) {
size_t child_idx = (first_child_idx + idx_offset) % m_children.size();
const Node& child = *m_children[child_idx];
output.emplace_back();
size_t child_line_idx = output.size() - 1;
child.convertToPolylines(child_line_idx, output);
output[child_line_idx].points.emplace_back(m_p);
}
}
void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const
{
const coord_t reduction = line_width / 2; // TODO make configurable?
for (auto poly_it = result_lines.begin(); poly_it != result_lines.end(); ) {
Polygon &polyline = *poly_it;
if (polyline.size() <= 1) {
polyline = std::move(result_lines.back());
result_lines.pop_back();
continue;
}
coord_t to_be_reduced = reduction;
Point a = polyline.back();
for (int point_idx = polyline.size() - 2; point_idx >= 0; point_idx--) {
const Point b = polyline[point_idx];
const Point ab = b - a;
const auto ab_len = coord_t(ab.cast<double>().norm());
if (ab_len >= to_be_reduced) {
polyline.points.back() = a + (ab.cast<double>() * (double(to_be_reduced) / ab_len)).cast<coord_t>();
break;
} else {
to_be_reduced -= ab_len;
polyline.points.pop_back();
}
a = b;
}
if (polyline.size() <= 1) {
polyline = std::move(result_lines.back());
result_lines.pop_back();
} else
++ poly_it;
}
}
} // namespace Slic3r::FillLightning

View file

@ -0,0 +1,275 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef LIGHTNING_TREE_NODE_H
#define LIGHTNING_TREE_NODE_H
#include <functional>
#include <memory>
#include <optional>
#include <vector>
#include "../../EdgeGrid.hpp"
#include "../../Polygon.hpp"
namespace Slic3r::FillLightning
{
constexpr auto locator_cell_size = scaled<coord_t>(4.);
class Node;
using NodeSPtr = std::shared_ptr<Node>;
// NOTE: As written, this struct will only be valid for a single layer, will have to be updated for the next.
// NOTE: Reasons for implementing this with some separate closures:
// - keep clear deliniation during development
// - possibility of multiple distance field strategies
/*!
* A single vertex of a Lightning Tree, the structure that determines the paths
* to be printed to form Lightning Infill.
*
* In essence these vertices are just a position linked to other positions in
* 2D. The nodes have a hierarchical structure of parents and children, forming
* a tree. The class also has some helper functions specific to Lightning Infill
* e.g. to straighten the paths around this node.
*/
class Node : public std::enable_shared_from_this<Node>
{
public:
// Workaround for private/protected constructors and 'make_shared': https://stackoverflow.com/a/27832765
template<typename ...Arg> NodeSPtr static create(Arg&&...arg)
{
struct EnableMakeShared : public Node
{
EnableMakeShared(Arg&&...arg) : Node(std::forward<Arg>(arg)...) {}
};
return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
}
/*!
* Get the position on this layer that this node represents, a vertex of the
* path to print.
* \return The position that this node represents.
*/
const Point& getLocation() const { return m_p; }
/*!
* Change the position on this layer that the node represents.
* \param p The position that the node needs to represent.
*/
void setLocation(const Point& p) { m_p = p; }
/*!
* Construct a new ``Node`` instance and add it as a child of
* this node.
* \param p The location of the new node.
* \return A shared pointer to the new node.
*/
NodeSPtr addChild(const Point& p);
/*!
* Add an existing ``Node`` as a child of this node.
* \param new_child The node that must be added as a child.
* \return Always returns \p new_child.
*/
NodeSPtr addChild(NodeSPtr& new_child);
/*!
* Propagate this node's sub-tree to the next layer.
*
* Creates a copy of this tree, realign it to the new layer boundaries
* \p next_outlines and reduce (i.e. prune and straighten) it. A copy of
* this node and all of its descendant nodes will be added to the
* \p next_trees vector.
* \param next_trees A collection of tree nodes to use for the next layer.
* \param next_outlines The shape of the layer below, to make sure that the
* tree stays within the bounds of the infill area.
* \param prune_distance The maximum distance that a leaf node may be moved
* such that it still supports the current node.
* \param smooth_magnitude The maximum distance that a line may be shifted
* to straighten the tree's paths, such that it still supports the current
* paths.
* \param max_remove_colinear_dist The maximum distance of a line-segment
* from which straightening may remove a colinear point.
*/
void propagateToNextLayer
(
std::vector<NodeSPtr>& next_trees,
const Polygons& next_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t prune_distance,
const coord_t smooth_magnitude,
const coord_t max_remove_colinear_dist
) const;
/*!
* Executes a given function for every line segment in this node's sub-tree.
*
* The function takes two `Point` arguments. These arguments will be filled
* in with the higher-order node (closer to the root) first, and the
* downtree node (closer to the leaves) as the second argument. The segment
* from this node's parent to this node itself is not included.
* The order in which the segments are visited is depth-first.
* \param visitor A function to execute for every branch in the node's sub-
* tree.
*/
void visitBranches(const std::function<void(const Point&, const Point&)>& visitor) const;
/*!
* Execute a given function for every node in this node's sub-tree.
*
* The visitor function takes a node as input. This node is not const, so
* this can be used to change the tree.
* Nodes are visited in depth-first order. This node itself is visited as
* well (pre-order).
* \param visitor A function to execute for every node in this node's sub-
* tree.
*/
void visitNodes(const std::function<void(NodeSPtr)>& visitor);
/*!
* Get a weighted distance from an unsupported point to this node (given the current supporting radius).
*
* When attaching a unsupported location to a node, not all nodes have the same priority.
* (Eucludian) closer nodes are prioritised, but that's not the whole story.
* For instance, we give some nodes a 'valence boost' depending on the nr. of branches.
* \param unsupported_location The (unsuppported) location of which the weighted distance needs to be calculated.
* \param supporting_radius The maximum distance which can be bridged without (infill) supporting it.
* \return The weighted distance.
*/
coord_t getWeightedDistance(const Point& unsupported_location, const coord_t& supporting_radius) const;
/*!
* Returns whether this node is the root of a lightning tree. It is the root
* if it has no parents.
* \return ``true`` if this node is the root (no parents) or ``false`` if it
* is a child node of some other node.
*/
bool isRoot() const { return m_is_root; }
/*!
* Reverse the parent-child relationship all the way to the root, from this node onward.
* This has the effect of 're-rooting' the tree at the current node if no immediate parent is given as argument.
* That is, the current node will become the root, it's (former) parent if any, will become one of it's children.
* This is then recursively bubbled up until it reaches the (former) root, which then will become a leaf.
* \param new_parent The (new) parent-node of the root, useful for recursing or immediately attaching the node to another tree.
*/
void reroot(NodeSPtr new_parent = nullptr);
/*!
* Retrieves the closest node to the specified location.
* \param loc The specified location.
* \result The branch that starts at the position closest to the location within this tree.
*/
NodeSPtr closestNode(const Point& loc);
/*!
* Returns whether the given tree node is a descendant of this node.
*
* If this node itself is given, it is also considered to be a descendant.
* \param to_be_checked A node to find out whether it is a descendant of
* this node.
* \return ``true`` if the given node is a descendant or this node itself,
* or ``false`` if it is not in the sub-tree.
*/
bool hasOffspring(const NodeSPtr& to_be_checked) const;
protected:
Node() = delete; // Don't allow empty contruction
/*!
* Construct a new node, either for insertion in a tree or as root.
* \param p The physical location in the 2D layer that this node represents.
* Connecting other nodes to this node indicates that a line segment should
* be drawn between those two physical positions.
*/
Node(const Point& p, const std::optional<Point>& last_grounding_location = std::nullopt);
/*!
* Copy this node and its entire sub-tree.
* \return The equivalent of this node in the copy (the root of the new sub-
* tree).
*/
NodeSPtr deepCopy() const;
/*! Reconnect trees from the layer above to the new outlines of the lower layer.
* \return Wether or not the root is kept (false is no, true is yes).
*/
bool realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<NodeSPtr>& rerooted_parts);
struct RectilinearJunction
{
coord_t total_recti_dist; //!< rectilinear distance along the tree from the last junction above to the junction below
Point junction_loc; //!< junction location below
};
/*!
* Smoothen the tree to make it a bit more printable, while still supporting
* the trees above.
* \param magnitude The maximum allowed distance to move the node.
* \param max_remove_colinear_dist Maximum distance of the (compound) line-segment from which a co-linear point may be removed.
*/
void straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist);
/*! Recursive part of \ref straighten(.)
* \param junction_above The last seen junction with multiple children above
* \param accumulated_dist The distance along the tree from the last seen junction to this node
* \param max_remove_colinear_dist2 Maximum distance _squared_ of the (compound) line-segment from which a co-linear point may be removed.
* \return the total distance along the tree from the last junction above to the first next junction below and the location of the next junction below
*/
RectilinearJunction straighten(const coord_t magnitude, const Point& junction_above, const coord_t accumulated_dist, const coord_t max_remove_colinear_dist2);
/*! Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
* \return The distance that has been pruned. If less than \p distance, then the whole tree was puned away.
*/
coord_t prune(const coord_t& distance);
public:
/*!
* Convert the tree into polylines
*
* At each junction one line is chosen at random to continue
*
* The lines start at a leaf and end in a junction
*
* \param output all branches in this tree connected into polylines
*/
void convertToPolylines(Polygons& output, const coord_t line_width) const;
/*! If this was ever a direct child of the root, it'll have a previous grounding location.
*
* This needs to be known when roots are reconnected, so that the last (higher) layer is supported by the next one.
*/
const std::optional<Point>& getLastGroundingLocation() const { return m_last_grounding_location; }
protected:
/*!
* Convert the tree into polylines
*
* At each junction one line is chosen at random to continue
*
* The lines start at a leaf and end in a junction
*
* \param long_line a reference to a polyline in \p output which to continue building on in the recursion
* \param output all branches in this tree connected into polylines
*/
void convertToPolylines(size_t long_line_idx, Polygons& output) const;
void removeJunctionOverlap(Polygons& polylines, const coord_t line_width) const;
bool m_is_root;
Point m_p;
std::weak_ptr<Node> m_parent;
std::vector<NodeSPtr> m_children;
std::optional<Point> m_last_grounding_location; //<! The last known grounding location, see 'getLastGroundingLocation()'.
};
bool inside(const Polygons &polygons, const Point p);
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist);
} // namespace Slic3r::FillLightning
#endif // LIGHTNING_TREE_NODE_H

View file

@ -254,7 +254,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
};
std::vector<Intersection> new_intersections = intersections;
if (!intersections.empty() && !start_lines.empty()) {
if (!new_intersections.empty() && !start_lines.empty()) {
size_t cl_start_idx = get_closer(start_lines, new_intersections.front(), start);
if (cl_start_idx != std::numeric_limits<size_t>::max()) {
// If there is any ClosestLine around the start point closer to the Intersection, then replace this Intersection with ClosestLine.
@ -265,11 +265,13 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
// vector of intersections. This allows in some cases when it is more than one around ClosestLine start point chose that one which
// minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then
// use the first one, which is the closest one to the start point.
size_t start_closest_lines_idx = find_closest_line_with_same_boundary_idx(start_lines, intersections, true);
size_t start_closest_lines_idx = find_closest_line_with_same_boundary_idx(start_lines, new_intersections, true);
const ClosestLine &cl_start = (start_closest_lines_idx != std::numeric_limits<size_t>::max()) ? start_lines[start_closest_lines_idx] : start_lines.front();
new_intersections.insert(new_intersections.begin(),{cl_start.border_idx, cl_start.line_idx, cl_start.point, compute_distance(cl_start)});
}
} else if (!intersections.empty() && !end_lines.empty()) {
}
if (!new_intersections.empty() && !end_lines.empty()) {
size_t cl_end_idx = get_closer(end_lines, new_intersections.back(), end);
if (cl_end_idx != std::numeric_limits<size_t>::max()) {
// If there is any ClosestLine around the end point closer to the Intersection, then replace this Intersection with ClosestLine.
@ -280,7 +282,7 @@ static std::vector<Intersection> extend_for_closest_lines(const std::vector<Inte
// vector of intersections. This allows in some cases when it is more than one around ClosestLine end point chose that one which
// minimizes the number of contours (also length of the detour) in result detour. If there doesn't exist any ClosestLine like this, then
// use the first one, which is the closest one to the end point.
size_t end_closest_lines_idx = find_closest_line_with_same_boundary_idx(end_lines, intersections, false);
size_t end_closest_lines_idx = find_closest_line_with_same_boundary_idx(end_lines, new_intersections, false);
const ClosestLine &cl_end = (end_closest_lines_idx != std::numeric_limits<size_t>::max()) ? end_lines[end_closest_lines_idx] : end_lines.front();
new_intersections.push_back({cl_end.border_idx, cl_end.line_idx, cl_end.point, compute_distance(cl_end)});
}

View file

@ -322,7 +322,7 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation,
Vec3d extract_euler_angles(const Eigen::Matrix<double, 3, 3, Eigen::DontAlign>& rotation_matrix)
{
// reference: http://www.gregslabaugh.net/publications/euler.pdf
// reference: http://eecs.qmul.ac.uk/~gslabaugh/publications/euler.pdf
Vec3d angles1 = Vec3d::Zero();
Vec3d angles2 = Vec3d::Zero();
if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5)
@ -640,17 +640,17 @@ Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot
{
return
// From the current coordinate system to world.
Eigen::AngleAxisd(rot_xyz_to(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to(1), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to(0), Vec3d::UnitX()) *
Eigen::AngleAxisd(rot_xyz_to.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to.x(), Vec3d::UnitX()) *
// From world to the initial coordinate system.
Eigen::AngleAxisd(-rot_xyz_from(0), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from(1), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from(2), Vec3d::UnitZ());
Eigen::AngleAxisd(-rot_xyz_from.x(), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from.z(), Vec3d::UnitZ());
}
// This should only be called if it is known, that the two rotations only differ in rotation around the Z axis.
double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)
{
Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to));
Vec3d axis = angle_axis.axis();
double angle = angle_axis.angle();
const Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to));
const Vec3d& axis = angle_axis.axis();
const double angle = angle_axis.angle();
#ifndef NDEBUG
if (std::abs(angle) > 1e-8) {
assert(std::abs(axis.x()) < 1e-8);

View file

@ -148,7 +148,8 @@ public:
return false;
}
void make_perimeters();
void make_fills() { this->make_fills(nullptr, nullptr); };
// Phony version of make_fills() without parameters for Perl integration only.
void make_fills() { this->make_fills(nullptr, nullptr); }
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree);
void make_ironing();

View file

@ -255,7 +255,7 @@ namespace int128 {
// To be used by std::unordered_map, std::unordered_multimap and friends.
struct PointHash {
size_t operator()(const Vec2crd &pt) const {
return std::hash<coord_t>()(pt.x()) ^ std::hash<coord_t>()(pt.y());
return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y());
}
};

View file

@ -107,7 +107,10 @@ static t_config_enum_values s_keys_map_InfillPattern {
{ "archimedeanchords", ipArchimedeanChords },
{ "octagramspiral", ipOctagramSpiral },
{ "adaptivecubic", ipAdaptiveCubic },
{ "supportcubic", ipSupportCubic }
{ "supportcubic", ipSupportCubic },
#if HAS_LIGHTNING_INFILL
{ "lightning", ipLightning }
#endif // HAS_LIGHTNING_INFILL
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern)
@ -1135,6 +1138,9 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("octagramspiral");
def->enum_values.push_back("adaptivecubic");
def->enum_values.push_back("supportcubic");
#if HAS_LIGHTNING_INFILL
def->enum_values.push_back("lightning");
#endif // HAS_LIGHTNING_INFILL
def->enum_labels.push_back(L("Rectilinear"));
def->enum_labels.push_back(L("Aligned Rectilinear"));
def->enum_labels.push_back(L("Grid"));
@ -1151,6 +1157,9 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back(L("Octagram Spiral"));
def->enum_labels.push_back(L("Adaptive Cubic"));
def->enum_labels.push_back(L("Support Cubic"));
#if HAS_LIGHTNING_INFILL
def->enum_labels.push_back(L("Lightning"));
#endif // HAS_LIGHTNING_INFILL
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipStars));
def = this->add("first_layer_acceleration", coFloat);

View file

@ -57,9 +57,15 @@ enum class FuzzySkinType {
All,
};
#define HAS_LIGHTNING_INFILL 0
enum InfillPattern : int {
ipRectilinear, ipMonotonic, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipCount,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase,
#if HAS_LIGHTNING_INFILL
ipLightning,
#endif // HAS_LIGHTNING_INFILL
ipCount,
};
enum class IroningType {

View file

@ -58,4 +58,17 @@
#define ENABLE_ENHANCED_PRINT_VOLUME_FIT (1 && ENABLE_2_4_0_BETA2)
//====================
// 2.5.0.alpha1 techs
//====================
#define ENABLE_2_5_0_ALPHA1 1
// Enable editing volumes transformation in world coordinates and instances in local coordinates
#define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1)
// Enable showing world coordinates of volumes' offset relative to the instance containing them
#define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (0 && ENABLE_WORLD_COORDINATE)
// Enable editing instance coordinates of volumes
#define ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES (1 && ENABLE_WORLD_COORDINATE)
#endif // _prusaslicer_technologies_h_

View file

@ -87,6 +87,8 @@ set(SLIC3R_GUI_SOURCES
GUI/GUI_App.hpp
GUI/GUI_Utils.cpp
GUI/GUI_Utils.hpp
GUI/GUI_Geometry.cpp
GUI/GUI_Geometry.hpp
GUI/I18N.cpp
GUI/I18N.hpp
GUI/MainFrame.cpp

View file

@ -1190,7 +1190,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
vertices.push_back(normal.z());
};
if (prev.type != curr.type || !buffer.paths.back().matches(curr)) {
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) {
buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1);
buffer.paths.back().sub_paths.back().first.position = prev.position;
}
@ -1281,7 +1281,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]);
};
if (prev.type != curr.type || !buffer.paths.back().matches(curr)) {
if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr)) {
buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1);
buffer.paths.back().sub_paths.back().first.position = prev.position;
}

View file

@ -0,0 +1,9 @@
#include "libslic3r/libslic3r.h"
#include "GUI_Geometry.hpp"
namespace Slic3r {
namespace GUI {
} // namespace Slic3r
} // namespace GUI

View file

@ -0,0 +1,80 @@
#ifndef slic3r_GUI_Geometry_hpp_
#define slic3r_GUI_Geometry_hpp_
namespace Slic3r {
namespace GUI {
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
enum class ECoordinatesType : unsigned char
{
World,
Instance,
Local
};
class TransformationType
{
public:
enum Enum {
// Transforming in a world coordinate system
World = 0,
// Transforming in a instance coordinate system
Instance = 1,
// Transforming in a local coordinate system
Local = 2,
// Absolute transformations, allowed in local coordinate system only.
Absolute = 0,
// Relative transformations, allowed in both local and world coordinate system.
Relative = 4,
// For group selection, the transformation is performed as if the group made a single solid body.
Joint = 0,
// For group selection, the transformation is performed on each object independently.
Independent = 8,
World_Relative_Joint = World | Relative | Joint,
World_Relative_Independent = World | Relative | Independent,
Instance_Absolute_Joint = Instance | Absolute | Joint,
Instance_Absolute_Independent = Instance | Absolute | Independent,
Instance_Relative_Joint = Instance | Relative | Joint,
Instance_Relative_Independent = Instance | Relative | Independent,
Local_Absolute_Joint = Local | Absolute | Joint,
Local_Absolute_Independent = Local | Absolute | Independent,
Local_Relative_Joint = Local | Relative | Joint,
Local_Relative_Independent = Local | Relative | Independent,
};
TransformationType() : m_value(World) {}
TransformationType(Enum value) : m_value(value) {}
TransformationType& operator=(Enum value) { m_value = value; return *this; }
Enum operator()() const { return m_value; }
bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; }
void set_world() { this->remove(Instance); this->remove(Local); }
void set_instance() { this->remove(Local); this->add(Instance); }
void set_local() { this->remove(Instance); this->add(Local); }
void set_absolute() { this->remove(Relative); }
void set_relative() { this->add(Relative); }
void set_joint() { this->remove(Independent); }
void set_independent() { this->add(Independent); }
bool world() const { return !this->has(Instance) && !this->has(Local); }
bool instance() const { return this->has(Instance); }
bool local() const { return this->has(Local); }
bool absolute() const { return !this->has(Relative); }
bool relative() const { return this->has(Relative); }
bool joint() const { return !this->has(Independent); }
bool independent() const { return this->has(Independent); }
private:
void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); }
void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); }
Enum m_value;
};
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
} // namespace Slic3r
} // namespace GUI
#endif // slic3r_GUI_Geometry_hpp_

View file

@ -2549,7 +2549,13 @@ void ObjectList::part_selection_changed()
Sidebar& panel = wxGetApp().sidebar();
panel.Freeze();
#if ENABLE_WORLD_COORDINATE
const ManipulationEditor* const editor = wxGetApp().obj_manipul()->get_focused_editor();
const std::string opt_key = (editor != nullptr) ? editor->get_full_opt_name() : "";
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, !opt_key.empty());
#else
wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false);
#endif // ENABLE_WORLD_COORDINATE
wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations);
wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings);
wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers);
@ -3269,7 +3275,11 @@ void ObjectList::update_selections()
return;
sels.Add(m_objects_model->GetItemById(selection.get_object_idx()));
}
#if ENABLE_WORLD_COORDINATE
else if (selection.is_single_volume_or_modifier()) {
#else
else if (selection.is_single_volume() || selection.is_any_modifier()) {
#endif // ENABLE_WORLD_COORDINATE
const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin());
if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx())
return;

View file

@ -52,10 +52,17 @@ static choice_ctrl* create_word_local_combo(wxWindow *parent)
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World));
temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance));
temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local));
temp->Select((int)ECoordinatesType::World);
#else
temp->Append(_L("World coordinates"));
temp->Append(_L("Local coordinates"));
temp->SetSelection(0);
temp->SetValue(temp->GetString(0));
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
temp->SetToolTip(_L("Select coordinate space, in which the transformation will be performed."));
return temp;
@ -81,8 +88,14 @@ void msw_rescale_word_local_combo(choice_ctrl* combo)
// Set rescaled size
combo->SetSize(size);
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World));
combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance));
combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local));
#else
combo->Append(_L("World coordinates"));
combo->Append(_L("Local coordinates"));
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
combo->SetValue(selection);
#else
@ -101,6 +114,7 @@ static void set_font_and_background_style(wxWindow* win, const wxFont& font)
static const wxString axes_color_text[] = { "#990000", "#009900", "#000099" };
static const wxString axes_color_back[] = { "#f5dcdc", "#dcf5dc", "#dcdcf5" };
ObjectManipulation::ObjectManipulation(wxWindow* parent) :
OG_Settings(parent, true)
{
@ -157,7 +171,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
// Add world local combobox
m_word_local_combo = create_word_local_combo(parent);
m_word_local_combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent& evt) {
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
this->set_coordinates_type(evt.GetString());
#else
this->set_world_coordinates(evt.GetSelection() != 1);
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
}), m_word_local_combo->GetId());
// Small trick to correct layouting in different view_mode :
@ -264,7 +282,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
#if ENABLE_WORLD_COORDINATE
if (selection.is_single_volume_or_modifier()) {
#else
if (selection.is_single_volume() || selection.is_single_modifier()) {
#endif // ENABLE_WORLD_COORDINATE
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis));
}
@ -278,7 +300,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
return;
// Update mirroring at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL);
selection.synchronize_unselected_volumes();
// Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_mirror(L("Set Mirror"));
@ -327,28 +349,68 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
#if ENABLE_WORLD_COORDINATE
if (selection.is_single_volume_or_modifier()) {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
const double min_z = get_volume_min_z(*volume);
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (!is_world_coordinates()) {
#else
if (!m_world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ());
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
change_position_value(0, diff.x());
change_position_value(1, diff.y());
change_position_value(2, diff.z());
}
else {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
change_position_value(2, m_cache.position.z() - min_z);
}
#else
if (selection.is_single_volume() || selection.is_single_modifier()) {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
const Geometry::Transformation& instance_trafo = volume->get_instance_transformation();
const Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(*volume));
const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (get_volume_min_z(*volume) * Vec3d::UnitZ());
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
change_position_value(0, diff.x());
change_position_value(1, diff.y());
change_position_value(2, diff.z());
#endif // ENABLE_WORLD_COORDINATE
}
else if (selection.is_single_full_instance()) {
#if ENABLE_WORLD_COORDINATE
const double min_z = selection.get_scaled_instance_bounding_box().min.z();
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (!is_world_coordinates()) {
#else
if (!m_world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ());
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
change_position_value(0, diff.x());
change_position_value(1, diff.y());
change_position_value(2, diff.z());
}
else {
#else
const ModelObjectPtrs& objects = wxGetApp().model().objects;
const int idx = selection.get_object_idx();
if (0 <= idx && idx < static_cast<int>(objects.size())) {
const ModelObject* mo = wxGetApp().model().objects[idx];
const double min_z = mo->bounding_box().min.z();
if (std::abs(min_z) > SINKING_Z_THRESHOLD) {
#endif // ENABLE_WORLD_COORDINATE
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed"));
change_position_value(2, m_cache.position.z() - min_z);
}
#if !ENABLE_WORLD_COORDINATE
}
#endif // !ENABLE_WORLD_COORDINATE
}
});
editors_grid_sizer->Add(m_drop_to_bed_button);
@ -365,7 +427,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
#if ENABLE_WORLD_COORDINATE
if (selection.is_single_volume_or_modifier()) {
#else
if (selection.is_single_volume() || selection.is_single_modifier()) {
#endif // ENABLE_WORLD_COORDINATE
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
volume->set_volume_rotation(Vec3d::Zero());
}
@ -379,7 +445,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
return;
// Update rotation at the GLVolumes.
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL);
selection.synchronize_unselected_volumes();
// Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
canvas->do_rotate(L("Reset Rotation"));
@ -444,8 +510,29 @@ void ObjectManipulation::Show(const bool show)
if (show) {
// Show the "World Coordinates" / "Local Coordintes" Combo in Advanced / Expert mode only.
bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple;
m_word_local_combo->Show(show_world_local_combo);
#if ENABLE_WORLD_COORDINATE
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier());
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (selection.is_single_volume_or_modifier() && m_word_local_combo->GetCount() < 3) {
#ifdef __linux__
m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), 1);
#else
m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), wxNullBitmap, 1);
#endif // __linux__
m_word_local_combo->Select((int)ECoordinatesType::World);
this->set_coordinates_type(m_word_local_combo->GetString(m_word_local_combo->GetSelection()));
}
else if (selection.is_single_full_instance() && m_word_local_combo->GetCount() > 2) {
m_word_local_combo->Delete(1);
m_word_local_combo->Select((int)ECoordinatesType::World);
this->set_coordinates_type(m_word_local_combo->GetString(m_word_local_combo->GetSelection()));
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#else
bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple;
#endif // ENABLE_WORLD_COORDINATE
m_word_local_combo->Show(show_world_local_combo);
m_empty_str->Show(!show_world_local_combo);
}
}
@ -522,33 +609,57 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
m_new_rotate_label_string = L("Rotation");
m_new_scale_label_string = L("Scale factors");
#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (wxGetApp().get_mode() == comSimple)
m_world_coordinates = true;
#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
ObjectList* obj_list = wxGetApp().obj_list();
if (selection.is_single_full_instance()) {
// 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());
#if !ENABLE_WORLD_COORDINATE
m_new_position = volume->get_instance_offset();
#endif // !ENABLE_WORLD_COORDINATE
// Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible.
if (m_world_coordinates && ! m_uniform_scale &&
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (is_world_coordinates() && !m_uniform_scale &&
#else
if (m_world_coordinates && ! m_uniform_scale &&
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
! 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.
m_uniform_scale = true;
m_lock_bnt->SetLock(true);
}
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (is_world_coordinates()) {
#else
if (m_world_coordinates) {
m_new_rotate_label_string = L("Rotate");
m_new_rotation = Vec3d::Zero();
m_new_size = selection.get_scaled_instance_bounding_box().size();
m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.;
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#if ENABLE_WORLD_COORDINATE
m_new_position = volume->get_instance_offset();
#endif // ENABLE_WORLD_COORDINATE
m_new_rotate_label_string = L("Rotate");
#if ENABLE_WORLD_COORDINATE
m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI);
#else
m_new_rotation = Vec3d::Zero();
#endif // ENABLE_WORLD_COORDINATE
m_new_size = selection.get_scaled_instance_bounding_box().size();
m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0;
}
else {
m_new_rotation = volume->get_instance_rotation() * (180. / M_PI);
m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size());
m_new_scale = volume->get_instance_scaling_factor() * 100.;
#if ENABLE_WORLD_COORDINATE
m_new_position = Vec3d::Zero();
m_new_rotation = Vec3d::Zero();
#else
m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI);
#endif // ENABLE_WORLD_COORDINATE
m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size());
m_new_scale = volume->get_instance_scaling_factor() * 100.0;
}
m_new_enabled = true;
@ -557,19 +668,61 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
const BoundingBoxf3& box = selection.get_bounding_box();
m_new_position = box.center();
m_new_rotation = Vec3d::Zero();
m_new_scale = Vec3d(100., 100., 100.);
m_new_scale = Vec3d(100.0, 100.0, 100.0);
m_new_size = box.size();
m_new_rotate_label_string = L("Rotate");
m_new_scale_label_string = L("Scale");
m_new_enabled = true;
}
#if ENABLE_WORLD_COORDINATE
else if (selection.is_single_volume_or_modifier()) {
#else
else if (selection.is_single_modifier() || selection.is_single_volume()) {
#endif // ENABLE_WORLD_COORDINATE
// the selection contains a single volume
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (is_world_coordinates()) {
#else
if (m_world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const Geometry::Transformation trafo(volume->world_matrix());
#if ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET
const Vec3d offset = trafo.get_offset() - volume->get_instance_offset();
#else
const Vec3d& offset = trafo.get_offset();
#endif // ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET
// const Vec3d& mirror = trafo.get_mirror();
m_new_position = offset;
m_new_rotation = trafo.get_rotation() * (180.0 / M_PI);
m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size();
m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0;
}
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else if (is_local_coordinates()) {
m_new_position = Vec3d::Zero();
m_new_rotation = Vec3d::Zero();
m_new_scale = volume->get_volume_scaling_factor() * 100.0;
m_new_size = volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size());
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else {
#endif // ENABLE_WORLD_COORDINATE
m_new_position = volume->get_volume_offset();
m_new_rotation = volume->get_volume_rotation() * (180. / M_PI);
m_new_scale = volume->get_volume_scaling_factor() * 100.;
m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()));
m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI);
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size();
m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0;
#else
m_new_scale = volume->get_volume_scaling_factor() * 100.0;
m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()));
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#if ENABLE_WORLD_COORDINATE
}
#endif // ENABLE_WORLD_COORDINATE
m_new_enabled = true;
}
else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) {
@ -635,9 +788,38 @@ void ObjectManipulation::update_if_dirty()
update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation);
}
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
Selection::EUniformScaleRequiredReason reason;
if (selection.requires_uniform_scale(&reason)) {
#else
if (selection.requires_uniform_scale()) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
m_lock_bnt->SetLock(true);
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
wxString tooltip;
if (selection.is_single_volume_or_modifier()) {
if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance)
tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the instance local axes");
else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_World)
tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the printer axes");
}
else if (selection.is_single_full_instance()) {
if (reason == Selection::EUniformScaleRequiredReason::InstanceNotAxisAligned_World)
tooltip = _L("You cannot use non-uniform scaling mode for instances non aligned with the printer axes");
else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance)
tooltip = _L("You cannot use non-uniform scaling mode for instances containing non locally axis-aligned parts");
}
else
tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection");
m_lock_bnt->SetToolTip(tooltip);
#else
m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection or non axis-aligned objects/parts"));
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#else
m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection"));
#endif // ENABLE_WORLD_COORDINATE
m_lock_bnt->disable();
}
else {
@ -646,11 +828,13 @@ void ObjectManipulation::update_if_dirty()
m_lock_bnt->enable();
}
{
#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
{
int new_selection = m_world_coordinates ? 0 : 1;
if (m_word_local_combo->GetSelection() != new_selection)
m_word_local_combo->SetSelection(new_selection);
}
#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (m_new_enabled)
m_og->enable();
@ -678,16 +862,27 @@ void ObjectManipulation::update_reset_buttons_visibility()
bool show_scale = false;
bool show_drop_to_bed = false;
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (m_coordinates_type != ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) {
#else
if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
Vec3d rotation = Vec3d::Zero();
Vec3d scale = Vec3d::Ones();
#else
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
Vec3d rotation;
Vec3d scale;
double min_z = 0.;
#endif // ENABLE_WORLD_COORDINATE
double min_z = 0.0;
if (selection.is_single_full_instance()) {
rotation = volume->get_instance_rotation();
scale = volume->get_instance_scaling_factor();
min_z = wxGetApp().model().objects[volume->composite_id.object_id]->bounding_box().min.z();
min_z = selection.get_scaled_instance_bounding_box().min.z();
}
else {
rotation = volume->get_volume_rotation();
@ -696,7 +891,11 @@ void ObjectManipulation::update_reset_buttons_visibility()
}
show_rotation = !rotation.isApprox(Vec3d::Zero());
show_scale = !scale.isApprox(Vec3d::Ones());
#if ENABLE_WORLD_COORDINATE
show_drop_to_bed = std::abs(min_z) > EPSILON;
#else
show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD;
#endif // ENABLE_WORLD_COORDINATE
}
wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] {
@ -726,8 +925,16 @@ void ObjectManipulation::update_mirror_buttons_visibility()
Selection& selection = canvas->get_selection();
std::array<MirrorButtonState, 3> new_states = {mbHidden, mbHidden, mbHidden};
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (is_local_coordinates()) {
#else
if (!m_world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#if ENABLE_WORLD_COORDINATE
if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) {
#else
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
#endif // ENABLE_WORLD_COORDINATE
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
Vec3d mirror;
@ -792,6 +999,19 @@ void ObjectManipulation::update_warning_icon_state(const MeshErrorsInfo& warning
m_fix_throught_netfab_bitmap->SetToolTip(tooltip);
}
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type)
{
switch (type)
{
case ECoordinatesType::World: { return _L("World coordinates"); }
case ECoordinatesType::Instance: { return _L("Instance coordinates"); }
case ECoordinatesType::Local: { return _L("Local coordinates"); }
default: { assert(false); return _L("Unknown"); }
}
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
void ObjectManipulation::reset_settings_value()
{
m_new_position = Vec3d::Zero();
@ -815,7 +1035,15 @@ void ObjectManipulation::change_position_value(int axis, double value)
auto canvas = wxGetApp().plater()->canvas3D();
Selection& selection = canvas->get_selection();
selection.start_dragging();
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
selection.translate(position - m_cache.position, get_coordinates_type());
#else
selection.translate(position - m_cache.position, !m_world_coordinates);
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#else
selection.translate(position - m_cache.position, selection.requires_local_axes());
#endif // ENABLE_WORLD_COORDINATE
selection.stop_dragging();
canvas->do_move(L("Set Position"));
@ -836,6 +1064,20 @@ void ObjectManipulation::change_rotation_value(int axis, double value)
Selection& selection = canvas->get_selection();
TransformationType transformation_type(TransformationType::World_Relative_Joint);
#if ENABLE_WORLD_COORDINATE
if (selection.is_single_full_instance())
transformation_type.set_independent();
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (!is_world_coordinates()) {
#else
if (!m_world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
//FIXME Selection::rotate() does not process absolute rotations correctly: It does not recognize the axis index, which was changed.
// transformation_type.set_absolute();
transformation_type.set_local();
}
#else
if (selection.is_single_full_instance() || selection.requires_local_axes())
transformation_type.set_independent();
if (selection.is_single_full_instance() && ! m_world_coordinates) {
@ -843,6 +1085,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value)
// transformation_type.set_absolute();
transformation_type.set_local();
}
#endif // ENABLE_WORLD_COORDINATE
selection.start_dragging();
selection.rotate(
@ -889,7 +1132,11 @@ void ObjectManipulation::change_size_value(int axis, double value)
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
Vec3d ref_size = m_cache.size;
#if ENABLE_WORLD_COORDINATE
if (selection.is_single_volume_or_modifier()) {
#else
if (selection.is_single_volume() || selection.is_single_modifier()) {
#endif // ENABLE_WORLD_COORDINATE
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
const Vec3d local_size = size.cwiseQuotient(v->get_instance_scaling_factor());
const Vec3d local_ref_size = v->bounding_box().size().cwiseProduct(v->get_volume_scaling_factor());
@ -899,7 +1146,11 @@ void ObjectManipulation::change_size_value(int axis, double value)
ref_size = Vec3d::Ones();
}
else if (selection.is_single_full_instance())
ref_size = m_world_coordinates ?
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
ref_size = is_world_coordinates() ?
#else
ref_size = m_world_coordinates ?
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
selection.get_unscaled_instance_bounding_box().size() :
wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size();
@ -913,8 +1164,36 @@ void ObjectManipulation::change_size_value(int axis, double value)
void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const
{
Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
#if !ENABLE_WORLD_COORDINATE
Vec3d scaling_factor = scale;
#endif // !ENABLE_WORLD_COORDINATE
#if ENABLE_WORLD_COORDINATE
TransformationType transformation_type;
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (!is_world_coordinates())
#else
if (!m_world_coordinates)
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
transformation_type.set_local();
bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale();
Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale;
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (!uniform_scale && is_world_coordinates()) {
#else
if (!uniform_scale && m_world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (selection.is_single_full_instance())
scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs();
else if (selection.is_single_volume_or_modifier()) {
const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse();
const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse();
scaling_factor = (mv * mi * scaling_factor).cwiseAbs();
}
}
#else
TransformationType transformation_type(TransformationType::World_Relative_Joint);
if (selection.is_single_full_instance()) {
transformation_type.set_absolute();
@ -924,6 +1203,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const
if (m_uniform_scale || selection.requires_uniform_scale())
scaling_factor = scale(axis) * Vec3d::Ones();
#endif // ENABLE_WORLD_COORDINATE
selection.start_dragging();
selection.scale(scaling_factor, transformation_type);
@ -968,7 +1248,11 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double
void ObjectManipulation::set_uniform_scaling(const bool new_value)
{
const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection();
if (selection.is_single_full_instance() && m_world_coordinates && !new_value) {
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (selection.is_single_full_instance() && is_world_coordinates() && !new_value) {
#else
if (selection.is_single_full_instance() && m_world_coordinates && !new_value) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
// Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible.
// 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());
@ -1000,6 +1284,43 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value)
m_uniform_scale = new_value;
}
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
void ObjectManipulation::set_coordinates_type(ECoordinatesType type)
{
if (wxGetApp().get_mode() == comSimple)
type = ECoordinatesType::World;
if (m_coordinates_type == type)
return;
m_coordinates_type = type;
this->UpdateAndShow(true);
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
canvas->get_gizmos_manager().update_data();
canvas->set_as_dirty();
canvas->request_extra_frame();
}
#else
void ObjectManipulation::set_world_coordinates(const bool world_coordinates)
{
m_world_coordinates = world_coordinates;
this->UpdateAndShow(true);
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
canvas->get_gizmos_manager().update_data();
canvas->set_as_dirty();
canvas->request_extra_frame();
}
bool ObjectManipulation::get_world_coordinates() const
{
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
return wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()) ?
m_world_coordinates : true;
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#endif // ENABLE_WORLD_COORDINATE
void ObjectManipulation::msw_rescale()
{
const int em = wxGetApp().em_unit();
@ -1063,6 +1384,19 @@ void ObjectManipulation::sys_color_changed()
m_mirror_buttons[id].first->msw_rescale();
}
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
void ObjectManipulation::set_coordinates_type(const wxString& type_string)
{
ECoordinatesType type = ECoordinatesType::World;
if (type_string == coordinate_type_str(ECoordinatesType::Instance))
type = ECoordinatesType::Instance;
else if (type_string == coordinate_type_str(ECoordinatesType::Local))
type = ECoordinatesType::Local;
this->set_coordinates_type(type);
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
static const char axes[] = { 'x', 'y', 'z' };
ManipulationEditor::ManipulationEditor(ObjectManipulation* parent,
const std::string& opt_key,

View file

@ -5,6 +5,9 @@
#include "GUI_ObjectSettings.hpp"
#include "GUI_ObjectList.hpp"
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#include "GUI_Geometry.hpp"
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#include "libslic3r/Point.hpp"
#include <float.h>
@ -57,6 +60,10 @@ public:
void set_value(const wxString& new_value);
void kill_focus(ObjectManipulation *parent);
#if ENABLE_WORLD_COORDINATE
const std::string& get_full_opt_name() const { return m_full_opt_name; }
#endif // ENABLE_WORLD_COORDINATE
private:
double get_value();
};
@ -144,18 +151,27 @@ private:
Vec3d m_new_size;
bool m_new_enabled {true};
bool m_uniform_scale {true};
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
ECoordinatesType m_coordinates_type{ ECoordinatesType::World };
#else
// Does the object manipulation panel work in World or Local coordinates?
bool m_world_coordinates = true;
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
LockButton* m_lock_bnt{ nullptr };
choice_ctrl* m_word_local_combo { nullptr };
ScalableBitmap m_manifold_warning_bmp;
wxStaticBitmap* m_fix_throught_netfab_bitmap;
#if ENABLE_WORLD_COORDINATE
// Currently focused editor (nullptr if none)
ManipulationEditor* m_focused_editor{ nullptr };
#else
#ifndef __APPLE__
// Currently focused editor (nullptr if none)
ManipulationEditor* m_focused_editor {nullptr};
#endif // __APPLE__
#endif // ENABLE_WORLD_COORDINATE
wxFlexGridSizer* m_main_grid_sizer;
wxFlexGridSizer* m_labels_grid_sizer;
@ -182,9 +198,23 @@ public:
void set_uniform_scaling(const bool uniform_scale);
bool get_uniform_scaling() const { return m_uniform_scale; }
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
void set_coordinates_type(ECoordinatesType type);
ECoordinatesType get_coordinates_type() const { return m_coordinates_type; }
bool is_world_coordinates() const { return m_coordinates_type == ECoordinatesType::World; }
bool is_instance_coordinates() const { return m_coordinates_type == ECoordinatesType::Instance; }
bool is_local_coordinates() const { return m_coordinates_type == ECoordinatesType::Local; }
#else
// Does the object manipulation panel work in World or Local coordinates?
void set_world_coordinates(const bool world_coordinates);
bool get_world_coordinates() const;
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#else
// Does the object manipulation panel work in World or Local coordinates?
void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); }
bool get_world_coordinates() const { return m_world_coordinates; }
#endif // ENABLE_WORLD_COORDINATE
void reset_cache() { m_cache.reset(); }
#ifndef __APPLE__
@ -200,11 +230,23 @@ public:
void sys_color_changed();
void on_change(const std::string& opt_key, int axis, double new_value);
void set_focused_editor(ManipulationEditor* focused_editor) {
#if ENABLE_WORLD_COORDINATE
m_focused_editor = focused_editor;
#else
#ifndef __APPLE__
m_focused_editor = focused_editor;
#endif // __APPLE__
#endif // ENABLE_WORLD_COORDINATE
}
#if ENABLE_WORLD_COORDINATE
ManipulationEditor* get_focused_editor() { return m_focused_editor; }
#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
static wxString coordinate_type_str(ECoordinatesType type);
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
private:
void reset_settings_value();
void update_settings_value(const Selection& selection);
@ -220,6 +262,10 @@ private:
void change_scale_value(int axis, double value);
void change_size_value(int axis, double value);
void do_scale(int axis, const Vec3d &scale) const;
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
void set_coordinates_type(const wxString& type_string);
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
};
}}

View file

@ -205,8 +205,7 @@ void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const
for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) {
if (m_grabbers[i].enabled) {
std::array<float, 4> color = picking_color_component(i);
m_grabbers[i].color = color;
m_grabbers[i].color = picking_color_component(i);
m_grabbers[i].render_for_picking(mean_size);
}
}

View file

@ -2,6 +2,9 @@
#include "GLGizmoMove.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#if ENABLE_WORLD_COORDINATE
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#endif // ENABLE_WORLD_COORDINATE
#include <GL/glew.h>
@ -14,29 +17,35 @@ const double GLGizmoMove3D::Offset = 10.0;
GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_displacement(Vec3d::Zero())
, m_snap_step(1.0)
, m_starting_drag_position(Vec3d::Zero())
, m_starting_box_center(Vec3d::Zero())
, m_starting_box_bottom_center(Vec3d::Zero())
{
m_vbo_cone.init_from(its_make_cone(1., 1., 2*PI/36));
}
std::string GLGizmoMove3D::get_tooltip() const
{
#if ENABLE_WORLD_COORDINATE
if (m_hover_id == 0)
return "X: " + format(m_displacement.x(), 2);
else if (m_hover_id == 1)
return "Y: " + format(m_displacement.y(), 2);
else if (m_hover_id == 2)
return "Z: " + format(m_displacement.z(), 2);
else
return "";
#else
const Selection& selection = m_parent.get_selection();
bool show_position = selection.is_single_full_instance();
const bool show_position = selection.is_single_full_instance();
const Vec3d& position = selection.get_bounding_box().center();
if (m_hover_id == 0 || m_grabbers[0].dragging)
return "X: " + format(show_position ? position(0) : m_displacement(0), 2);
return "X: " + format(show_position ? position.x() : m_displacement.x(), 2);
else if (m_hover_id == 1 || m_grabbers[1].dragging)
return "Y: " + format(show_position ? position(1) : m_displacement(1), 2);
return "Y: " + format(show_position ? position.y() : m_displacement.y(), 2);
else if (m_hover_id == 2 || m_grabbers[2].dragging)
return "Z: " + format(show_position ? position(2) : m_displacement(2), 2);
return "Z: " + format(show_position ? position.z() : m_displacement.z(), 2);
else
return "";
#endif // ENABLE_WORLD_COORDINATE
}
bool GLGizmoMove3D::on_init()
@ -64,11 +73,35 @@ void GLGizmoMove3D::on_start_dragging()
{
if (m_hover_id != -1) {
m_displacement = Vec3d::Zero();
#if ENABLE_WORLD_COORDINATE
const Selection& selection = m_parent.get_selection();
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
if (coordinates_type == ECoordinatesType::World)
#else
if (wxGetApp().obj_manipul()->get_world_coordinates())
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
m_starting_drag_position = m_center + m_grabbers[m_hover_id].center;
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center;
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center;
}
m_starting_box_center = m_center;
m_starting_box_bottom_center = m_center;
m_starting_box_bottom_center.z() = m_bounding_box.min.z();
#else
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_starting_drag_position = m_grabbers[m_hover_id].center;
m_starting_box_center = box.center();
m_starting_box_bottom_center = box.center();
m_starting_box_bottom_center(2) = box.min(2);
m_starting_box_bottom_center.z() = box.min.z();
#endif // ENABLE_WORLD_COORDINATE
}
}
@ -89,11 +122,30 @@ void GLGizmoMove3D::on_update(const UpdateData& data)
void GLGizmoMove3D::on_render()
{
const Selection& selection = m_parent.get_selection();
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
#if ENABLE_WORLD_COORDINATE
glsafe(::glPushMatrix());
calc_selection_box_and_center();
transform_to_local(m_parent.get_selection());
const Vec3d zero = Vec3d::Zero();
const Vec3d half_box_size = 0.5 * m_bounding_box.size();
// x axis
m_grabbers[0].center = { half_box_size.x() + Offset, 0.0, 0.0 };
m_grabbers[0].color = AXES_COLOR[0];
// y axis
m_grabbers[1].center = { 0.0, half_box_size.y() + Offset, 0.0 };
m_grabbers[1].color = AXES_COLOR[1];
// z axis
m_grabbers[2].center = { 0.0, 0.0, half_box_size.z() + Offset };
m_grabbers[2].color = AXES_COLOR[2];
#else
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
const Vec3d& center = box.center();
@ -108,6 +160,7 @@ void GLGizmoMove3D::on_render()
// z axis
m_grabbers[2].center = { center.x(), center.y(), box.max.z() + Offset };
m_grabbers[2].color = AXES_COLOR[2];
#endif // ENABLE_WORLD_COORDINATE
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
@ -117,24 +170,40 @@ void GLGizmoMove3D::on_render()
if (m_grabbers[i].enabled) {
glsafe(::glColor4fv(AXES_COLOR[i].data()));
::glBegin(GL_LINES);
#if ENABLE_WORLD_COORDINATE
::glVertex3dv(zero.data());
#else
::glVertex3dv(center.data());
#endif // ENABLE_WORLD_COORDINATE
::glVertex3dv(m_grabbers[i].center.data());
glsafe(::glEnd());
}
}
// draw grabbers
#if ENABLE_WORLD_COORDINATE
render_grabbers(m_bounding_box);
for (unsigned int i = 0; i < 3; ++i) {
if (m_grabbers[i].enabled)
render_grabber_extension((Axis)i, m_bounding_box, false);
}
#else
render_grabbers(box);
for (unsigned int i = 0; i < 3; ++i) {
if (m_grabbers[i].enabled)
render_grabber_extension((Axis)i, box, false);
}
#endif // ENABLE_WORLD_COORDINATE
}
else {
// draw axis
glsafe(::glColor4fv(AXES_COLOR[m_hover_id].data()));
::glBegin(GL_LINES);
#if ENABLE_WORLD_COORDINATE
::glVertex3dv(zero.data());
#else
::glVertex3dv(center.data());
#endif // ENABLE_WORLD_COORDINATE
::glVertex3dv(m_grabbers[m_hover_id].center.data());
glsafe(::glEnd());
@ -143,40 +212,63 @@ void GLGizmoMove3D::on_render()
shader->start_using();
shader->set_uniform("emission_factor", 0.1f);
// draw grabber
float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0);
#if ENABLE_WORLD_COORDINATE
const Vec3d box_size = m_bounding_box.size();
#else
const Vec3d box_size = box.size();
#endif // ENABLE_WORLD_COORDINATE
const float mean_size = (float)((box_size.x() + box_size.y() + box_size.z()) / 3.0);
m_grabbers[m_hover_id].render(true, mean_size);
shader->stop_using();
}
#if ENABLE_WORLD_COORDINATE
render_grabber_extension((Axis)m_hover_id, m_bounding_box, false);
#else
render_grabber_extension((Axis)m_hover_id, box, false);
#endif // ENABLE_WORLD_COORDINATE
}
#if ENABLE_WORLD_COORDINATE
glsafe(::glPopMatrix());
#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoMove3D::on_render_for_picking()
{
glsafe(::glDisable(GL_DEPTH_TEST));
#if ENABLE_WORLD_COORDINATE
glsafe(::glPushMatrix());
transform_to_local(m_parent.get_selection());
render_grabbers_for_picking(m_bounding_box);
render_grabber_extension(X, m_bounding_box, true);
render_grabber_extension(Y, m_bounding_box, true);
render_grabber_extension(Z, m_bounding_box, true);
glsafe(::glPopMatrix());
#else
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
render_grabbers_for_picking(box);
render_grabber_extension(X, box, true);
render_grabber_extension(Y, box, true);
render_grabber_extension(Z, box, true);
#endif // ENABLE_WORLD_COORDINATE
}
double GLGizmoMove3D::calc_projection(const UpdateData& data) const
{
double projection = 0.0;
Vec3d starting_vec = m_starting_drag_position - m_starting_box_center;
double len_starting_vec = starting_vec.norm();
const Vec3d starting_vec = m_starting_drag_position - m_starting_box_center;
const double len_starting_vec = starting_vec.norm();
if (len_starting_vec != 0.0) {
Vec3d mouse_dir = data.mouse_ray.unit_vector();
const Vec3d mouse_dir = data.mouse_ray.unit_vector();
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
// in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
const Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
// vector from the starting position to the found intersection
Vec3d inters_vec = inters - m_starting_drag_position;
const Vec3d inters_vec = inters - m_starting_drag_position;
// finds projection of the vector along the staring direction
projection = inters_vec.dot(starting_vec.normalized());
@ -190,8 +282,9 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const
void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const
{
float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0);
double size = m_dragging ? (double)m_grabbers[axis].get_dragging_half_size(mean_size) : (double)m_grabbers[axis].get_half_size(mean_size);
const Vec3d box_size = box.size();
const float mean_size = (float)((box_size.x() + box_size.y() + box_size.z()) / 3.0);
const double size = m_dragging ? (double)m_grabbers[axis].get_dragging_half_size(mean_size) : (double)m_grabbers[axis].get_half_size(mean_size);
std::array<float, 4> color = m_grabbers[axis].color;
if (!picking && m_hover_id != -1) {
@ -227,6 +320,58 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box
shader->stop_using();
}
#if ENABLE_WORLD_COORDINATE
void GLGizmoMove3D::transform_to_local(const Selection& selection) const
{
glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z()));
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true);
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
#else
if (!wxGetApp().obj_manipul()->get_world_coordinates()) {
const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
}
void GLGizmoMove3D::calc_selection_box_and_center()
{
const Selection& selection = m_parent.get_selection();
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
if (coordinates_type == ECoordinatesType::World) {
#else
if (wxGetApp().obj_manipul()->get_world_coordinates()) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
m_bounding_box = selection.get_bounding_box();
m_center = m_bounding_box.center();
}
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true));
m_center = v.world_matrix() * m_bounding_box.center();
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else {
m_bounding_box.reset();
const Selection::IndicesList& ids = selection.get_volume_idxs();
for (unsigned int id : ids) {
const GLVolume& v = *selection.get_volume(id);
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
}
m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true));
m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center();
}
}
#endif // ENABLE_WORLD_COORDINATE
} // namespace GUI

View file

@ -11,14 +11,15 @@ class GLGizmoMove3D : public GLGizmoBase
{
static const double Offset;
Vec3d m_displacement;
double m_snap_step;
Vec3d m_starting_drag_position;
Vec3d m_starting_box_center;
Vec3d m_starting_box_bottom_center;
Vec3d m_displacement{ Vec3d::Zero() };
#if ENABLE_WORLD_COORDINATE
Vec3d m_center{ Vec3d::Zero() };
BoundingBoxf3 m_bounding_box;
#endif // ENABLE_WORLD_COORDINATE
double m_snap_step{ 1.0 };
Vec3d m_starting_drag_position{ Vec3d::Zero() };
Vec3d m_starting_box_center{ Vec3d::Zero() };
Vec3d m_starting_box_bottom_center{ Vec3d::Zero() };
GLModel m_vbo_cone;
public:
@ -45,6 +46,10 @@ protected:
private:
double calc_projection(const UpdateData& data) const;
void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const;
#if ENABLE_WORLD_COORDINATE
void transform_to_local(const Selection& selection) const;
void calc_selection_box_and_center();
#endif // ENABLE_WORLD_COORDINATE
};

View file

@ -2,15 +2,18 @@
#include "GLGizmoRotate.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include <GL/glew.h>
#if ENABLE_WORLD_COORDINATE
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#endif // ENABLE_WORLD_COORDINATE
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp"
#include <GL/glew.h>
namespace Slic3r {
namespace GUI {
@ -29,13 +32,6 @@ const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius
GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis)
: GLGizmoBase(parent, "", -1)
, m_axis(axis)
, m_angle(0.0)
, m_center(0.0, 0.0, 0.0)
, m_radius(0.0f)
, m_snap_coarse_in_radius(0.0f)
, m_snap_coarse_out_radius(0.0f)
, m_snap_fine_in_radius(0.0f)
, m_snap_fine_out_radius(0.0f)
{
}
@ -49,6 +45,10 @@ GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other)
, m_snap_coarse_out_radius(other.m_snap_coarse_out_radius)
, m_snap_fine_in_radius(other.m_snap_fine_in_radius)
, m_snap_fine_out_radius(other.m_snap_fine_out_radius)
#if ENABLE_WORLD_COORDINATE
, m_bounding_box(other.m_bounding_box)
, m_orient_matrix(other.m_orient_matrix)
#endif // ENABLE_WORLD_COORDINATE
{
}
@ -81,6 +81,9 @@ bool GLGizmoRotate::on_init()
void GLGizmoRotate::on_start_dragging()
{
#if ENABLE_WORLD_COORDINATE
init_data_from_selection(m_parent.get_selection());
#else
const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box();
m_center = box.center();
m_radius = Offset + box.radius();
@ -88,33 +91,31 @@ void GLGizmoRotate::on_start_dragging()
m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
m_snap_fine_in_radius = m_radius;
m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth;
#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoRotate::on_update(const UpdateData& data)
{
Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection()));
const Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection()));
Vec2d orig_dir = Vec2d::UnitX();
Vec2d new_dir = mouse_pos.normalized();
const Vec2d orig_dir = Vec2d::UnitX();
const Vec2d new_dir = mouse_pos.normalized();
double theta = ::acos(std::clamp(new_dir.dot(orig_dir), -1.0, 1.0));
if (cross2(orig_dir, new_dir) < 0.0)
theta = 2.0 * (double)PI - theta;
double len = mouse_pos.norm();
const double len = mouse_pos.norm();
// snap to coarse snap region
if ((m_snap_coarse_in_radius <= len) && (len <= m_snap_coarse_out_radius))
{
double step = 2.0 * (double)PI / (double)SnapRegionsCount;
if (m_snap_coarse_in_radius <= len && len <= m_snap_coarse_out_radius) {
const double step = 2.0 * (double)PI / (double)SnapRegionsCount;
theta = step * (double)std::round(theta / step);
}
else
{
else {
// snap to fine snap region (scale)
if ((m_snap_fine_in_radius <= len) && (len <= m_snap_fine_out_radius))
{
double step = 2.0 * (double)PI / (double)ScaleStepsCount;
if (m_snap_fine_in_radius <= len && len <= m_snap_fine_out_radius) {
const double step = 2.0 * (double)PI / (double)ScaleStepsCount;
theta = step * (double)std::round(theta / step);
}
}
@ -131,15 +132,21 @@ void GLGizmoRotate::on_render()
return;
const Selection& selection = m_parent.get_selection();
#if !ENABLE_WORLD_COORDINATE
const BoundingBoxf3& box = selection.get_bounding_box();
#endif // !ENABLE_WORLD_COORDINATE
if (m_hover_id != 0 && !m_grabbers[0].dragging) {
#if ENABLE_WORLD_COORDINATE
init_data_from_selection(selection);
#else
m_center = box.center();
m_radius = Offset + box.radius();
m_snap_coarse_in_radius = m_radius / 3.0f;
m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
m_snap_fine_in_radius = m_radius;
m_snap_fine_out_radius = m_radius * (1.0f + ScaleLongTooth);
#endif // ENABLE_WORLD_COORDINATE
}
glsafe(::glEnable(GL_DEPTH_TEST));
@ -163,8 +170,13 @@ void GLGizmoRotate::on_render()
if (m_hover_id != -1)
render_angle();
#if ENABLE_WORLD_COORDINATE
render_grabber(m_bounding_box);
render_grabber_extension(m_bounding_box, false);
#else
render_grabber(box);
render_grabber_extension(box, false);
#endif // ENABLE_WORLD_COORDINATE
glsafe(::glPopMatrix());
}
@ -179,13 +191,74 @@ void GLGizmoRotate::on_render_for_picking()
transform_to_local(selection);
#if ENABLE_WORLD_COORDINATE
render_grabbers_for_picking(m_bounding_box);
render_grabber_extension(m_bounding_box, true);
#else
const BoundingBoxf3& box = selection.get_bounding_box();
render_grabbers_for_picking(box);
render_grabber_extension(box, true);
#endif // ENABLE_WORLD_COORDINATE
glsafe(::glPopMatrix());
}
#if ENABLE_WORLD_COORDINATE
void GLGizmoRotate::init_data_from_selection(const Selection& selection)
{
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
if (coordinates_type == ECoordinatesType::World) {
#else
if (wxGetApp().obj_manipul()->get_world_coordinates()) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
m_bounding_box = selection.get_bounding_box();
m_center = m_bounding_box.center();
}
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true));
m_center = v.world_matrix() * m_bounding_box.center();
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else {
m_bounding_box.reset();
const Selection::IndicesList& ids = selection.get_volume_idxs();
for (unsigned int id : ids) {
const GLVolume& v = *selection.get_volume(id);
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
}
m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true));
m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center();
}
m_radius = Offset + m_bounding_box.radius();
m_snap_coarse_in_radius = m_radius / 3.0f;
m_snap_coarse_out_radius = 2.0f * m_snap_coarse_in_radius;
m_snap_fine_in_radius = m_radius;
m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth;
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (coordinates_type == ECoordinatesType::World)
m_orient_matrix = Transform3d::Identity();
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true);
}
else {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true);
}
#else
if (wxGetApp().obj_manipul()->get_world_coordinates())
m_orient_matrix = Transform3d::Identity();
else
m_orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true);
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
}
#endif // ENABLE_WORLD_COORDINATE
void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit)
{
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA)
@ -219,12 +292,11 @@ void GLGizmoRotate3D::load_rotoptimize_state()
void GLGizmoRotate::render_circle() const
{
::glBegin(GL_LINE_LOOP);
for (unsigned int i = 0; i < ScaleStepsCount; ++i)
{
float angle = (float)i * ScaleStepRad;
float x = ::cos(angle) * m_radius;
float y = ::sin(angle) * m_radius;
float z = 0.0f;
for (unsigned int i = 0; i < ScaleStepsCount; ++i) {
const float angle = (float)i * ScaleStepRad;
const float x = ::cos(angle) * m_radius;
const float y = ::sin(angle) * m_radius;
const float z = 0.0f;
::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z);
}
glsafe(::glEnd());
@ -232,21 +304,20 @@ void GLGizmoRotate::render_circle() const
void GLGizmoRotate::render_scale() const
{
float out_radius_long = m_snap_fine_out_radius;
float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth);
const float out_radius_long = m_snap_fine_out_radius;
const float out_radius_short = m_radius * (1.0f + 0.5f * ScaleLongTooth);
::glBegin(GL_LINES);
for (unsigned int i = 0; i < ScaleStepsCount; ++i)
{
float angle = (float)i * ScaleStepRad;
float cosa = ::cos(angle);
float sina = ::sin(angle);
float in_x = cosa * m_radius;
float in_y = sina * m_radius;
float in_z = 0.0f;
float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short;
float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short;
float out_z = 0.0f;
for (unsigned int i = 0; i < ScaleStepsCount; ++i) {
const float angle = (float)i * ScaleStepRad;
const float cosa = ::cos(angle);
const float sina = ::sin(angle);
const float in_x = cosa * m_radius;
const float in_y = sina * m_radius;
const float in_z = 0.0f;
const float out_x = (i % ScaleLongEvery == 0) ? cosa * out_radius_long : cosa * out_radius_short;
const float out_y = (i % ScaleLongEvery == 0) ? sina * out_radius_long : sina * out_radius_short;
const float out_z = 0.0f;
::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z);
::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z);
}
@ -255,23 +326,22 @@ void GLGizmoRotate::render_scale() const
void GLGizmoRotate::render_snap_radii() const
{
float step = 2.0f * (float)PI / (float)SnapRegionsCount;
const float step = 2.0f * (float)PI / (float)SnapRegionsCount;
float in_radius = m_radius / 3.0f;
float out_radius = 2.0f * in_radius;
const float in_radius = m_radius / 3.0f;
const float out_radius = 2.0f * in_radius;
::glBegin(GL_LINES);
for (unsigned int i = 0; i < SnapRegionsCount; ++i)
{
float angle = (float)i * step;
float cosa = ::cos(angle);
float sina = ::sin(angle);
float in_x = cosa * in_radius;
float in_y = sina * in_radius;
float in_z = 0.0f;
float out_x = cosa * out_radius;
float out_y = sina * out_radius;
float out_z = 0.0f;
for (unsigned int i = 0; i < SnapRegionsCount; ++i) {
const float angle = (float)i * step;
const float cosa = ::cos(angle);
const float sina = ::sin(angle);
const float in_x = cosa * in_radius;
const float in_y = sina * in_radius;
const float in_z = 0.0f;
const float out_x = cosa * out_radius;
const float out_y = sina * out_radius;
const float out_z = 0.0f;
::glVertex3f((GLfloat)in_x, (GLfloat)in_y, (GLfloat)in_z);
::glVertex3f((GLfloat)out_x, (GLfloat)out_y, (GLfloat)out_z);
}
@ -288,16 +358,15 @@ void GLGizmoRotate::render_reference_radius() const
void GLGizmoRotate::render_angle() const
{
float step_angle = (float)m_angle / AngleResolution;
float ex_radius = m_radius * (1.0f + GrabberOffset);
const float step_angle = (float)m_angle / AngleResolution;
const float ex_radius = m_radius * (1.0f + GrabberOffset);
::glBegin(GL_LINE_STRIP);
for (unsigned int i = 0; i <= AngleResolution; ++i)
{
float angle = (float)i * step_angle;
float x = ::cos(angle) * ex_radius;
float y = ::sin(angle) * ex_radius;
float z = 0.0f;
for (unsigned int i = 0; i <= AngleResolution; ++i) {
const float angle = (float)i * step_angle;
const float x = ::cos(angle) * ex_radius;
const float y = ::sin(angle) * ex_radius;
const float z = 0.0f;
::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z);
}
glsafe(::glEnd());
@ -305,9 +374,9 @@ void GLGizmoRotate::render_angle() const
void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const
{
double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset);
const double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset);
m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0);
m_grabbers[0].angles(2) = m_angle;
m_grabbers[0].angles.z() = m_angle;
glsafe(::glColor4fv((m_hover_id != -1) ? m_drag_color.data() : m_highlight_color.data()));
@ -322,8 +391,8 @@ void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const
void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) const
{
float mean_size = (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0);
double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size(mean_size) : (double)m_grabbers[0].get_half_size(mean_size);
const float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0);
const double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size(mean_size) : (double)m_grabbers[0].get_half_size(mean_size);
std::array<float, 4> color = m_grabbers[0].color;
if (!picking && m_hover_id != -1) {
@ -365,12 +434,16 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick
void GLGizmoRotate::transform_to_local(const Selection& selection) const
{
glsafe(::glTranslated(m_center(0), m_center(1), m_center(2)));
glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z()));
#if ENABLE_WORLD_COORDINATE
glsafe(::glMultMatrixd(m_orient_matrix.data()));
#else
if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) {
Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true);
const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
#endif // ENABLE_WORLD_COORDINATE
switch (m_axis)
{
@ -423,8 +496,12 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons
}
}
#if ENABLE_WORLD_COORDINATE
m = m * m_orient_matrix.inverse();
#else
if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes())
m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse();
#endif // ENABLE_WORLD_COORDINATE
m.translate(-m_center);
@ -473,13 +550,13 @@ bool GLGizmoRotate3D::on_is_activable() const
void GLGizmoRotate3D::on_start_dragging()
{
if ((0 <= m_hover_id) && (m_hover_id < 3))
if (0 <= m_hover_id && m_hover_id < 3)
m_gizmos[m_hover_id].start_dragging();
}
void GLGizmoRotate3D::on_stop_dragging()
{
if ((0 <= m_hover_id) && (m_hover_id < 3))
if (0 <= m_hover_id && m_hover_id < 3)
m_gizmos[m_hover_id].stop_dragging();
}

View file

@ -30,15 +30,17 @@ public:
private:
Axis m_axis;
double m_angle;
mutable Vec3d m_center;
mutable float m_radius;
mutable float m_snap_coarse_in_radius;
mutable float m_snap_coarse_out_radius;
mutable float m_snap_fine_in_radius;
mutable float m_snap_fine_out_radius;
double m_angle{ 0.0 };
Vec3d m_center{ Vec3d::Zero() };
float m_radius{ 0.0 };
float m_snap_coarse_in_radius{ 0.0 };
float m_snap_coarse_out_radius{ 0.0 };
float m_snap_fine_in_radius{ 0.0 };
float m_snap_fine_out_radius{ 0.0 };
#if ENABLE_WORLD_COORDINATE
BoundingBoxf3 m_bounding_box;
Transform3d m_orient_matrix{ Transform3d::Identity() };
#endif // ENABLE_WORLD_COORDINATE
public:
GLGizmoRotate(GLCanvas3D& parent, Axis axis);
@ -70,6 +72,10 @@ private:
void transform_to_local(const Selection& selection) const;
// returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const;
#if ENABLE_WORLD_COORDINATE
void init_data_from_selection(const Selection& selection);
#endif // ENABLE_WORLD_COORDINATE
};
class GLGizmoRotate3D : public GLGizmoBase
@ -80,7 +86,7 @@ public:
GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); }
void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); }
void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation.x()); m_gizmos[Y].set_angle(rotation.y()); m_gizmos[Z].set_angle(rotation.z()); }
std::string get_tooltip() const override
{

View file

@ -2,6 +2,9 @@
#include "GLGizmoScale.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#if ENABLE_WORLD_COORDINATE
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#endif // ENABLE_WORLD_COORDINATE
#include <GL/glew.h>
@ -11,13 +14,10 @@ namespace Slic3r {
namespace GUI {
const float GLGizmoScale3D::Offset = 5.0f;
const double GLGizmoScale3D::Offset = 5.0;
GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_scale(Vec3d::Ones())
, m_offset(Vec3d::Zero())
, m_snap_step(0.05)
{
}
@ -25,27 +25,28 @@ std::string GLGizmoScale3D::get_tooltip() const
{
const Selection& selection = m_parent.get_selection();
bool single_instance = selection.is_single_full_instance();
bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
Vec3f scale = 100.0f * Vec3f::Ones();
if (single_instance)
scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast<float>();
else if (single_volume)
scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast<float>();
Vec3d scale = 100.0 * Vec3d::Ones();
if (selection.is_single_full_instance())
scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor();
#if ENABLE_WORLD_COORDINATE
else if (selection.is_single_volume_or_modifier())
#else
else if (selection.is_single_modifier() || selection.is_single_volume())
#endif // ENABLE_WORLD_COORDINATE
scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor();
if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging)
return "X: " + format(scale(0), 4) + "%";
return "X: " + format(scale.x(), 4) + "%";
else if (m_hover_id == 2 || m_hover_id == 3 || m_grabbers[2].dragging || m_grabbers[3].dragging)
return "Y: " + format(scale(1), 4) + "%";
return "Y: " + format(scale.y(), 4) + "%";
else if (m_hover_id == 4 || m_hover_id == 5 || m_grabbers[4].dragging || m_grabbers[5].dragging)
return "Z: " + format(scale(2), 4) + "%";
return "Z: " + format(scale.z(), 4) + "%";
else if (m_hover_id == 6 || m_hover_id == 7 || m_hover_id == 8 || m_hover_id == 9 ||
m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging)
{
std::string tooltip = "X: " + format(scale(0), 4) + "%\n";
tooltip += "Y: " + format(scale(1), 4) + "%\n";
tooltip += "Z: " + format(scale(2), 4) + "%";
std::string tooltip = "X: " + format(scale.x(), 4) + "%\n";
tooltip += "Y: " + format(scale.y(), 4) + "%\n";
tooltip += "Z: " + format(scale.z(), 4) + "%";
return tooltip;
}
else
@ -54,20 +55,21 @@ std::string GLGizmoScale3D::get_tooltip() const
bool GLGizmoScale3D::on_init()
{
for (int i = 0; i < 10; ++i)
{
for (int i = 0; i < 10; ++i) {
m_grabbers.push_back(Grabber());
}
#if !ENABLE_WORLD_COORDINATE
double half_pi = 0.5 * (double)PI;
// x axis
m_grabbers[0].angles(1) = half_pi;
m_grabbers[1].angles(1) = half_pi;
m_grabbers[0].angles.y() = half_pi;
m_grabbers[1].angles.y() = half_pi;
// y axis
m_grabbers[2].angles(0) = half_pi;
m_grabbers[3].angles(0) = half_pi;
m_grabbers[2].angles.x() = half_pi;
m_grabbers[3].angles.x() = half_pi;
#endif // !ENABLE_WORLD_COORDINATE
m_shortcut_key = WXK_CONTROL_S;
@ -87,29 +89,35 @@ bool GLGizmoScale3D::on_is_activable() const
void GLGizmoScale3D::on_start_dragging()
{
if (m_hover_id != -1)
{
m_starting.drag_position = m_grabbers[m_hover_id].center;
if (m_hover_id != -1) {
m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL);
m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box();
#if ENABLE_WORLD_COORDINATE
m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center;
m_starting.box = m_bounding_box;
m_starting.center = m_center;
m_starting.instance_center = m_instance_center;
#else
m_starting.drag_position = m_grabbers[m_hover_id].center;
m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_bounding_box : m_parent.get_selection().get_bounding_box();
const Vec3d& center = m_starting.box.center();
m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max(0), center(1), center(2));
m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min(0), center(1), center(2));
m_starting.pivots[2] = m_transform * Vec3d(center(0), m_starting.box.max(1), center(2));
m_starting.pivots[3] = m_transform * Vec3d(center(0), m_starting.box.min(1), center(2));
m_starting.pivots[4] = m_transform * Vec3d(center(0), center(1), m_starting.box.max(2));
m_starting.pivots[5] = m_transform * Vec3d(center(0), center(1), m_starting.box.min(2));
const Vec3d center = m_starting.box.center();
m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z());
m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z());
m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z());
m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z());
m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z());
m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z());
#endif // ENABLE_WORLD_COORDINATE
}
}
void GLGizmoScale3D::on_update(const UpdateData& data)
{
if ((m_hover_id == 0) || (m_hover_id == 1))
if (m_hover_id == 0 || m_hover_id == 1)
do_scale_along_axis(X, data);
else if ((m_hover_id == 2) || (m_hover_id == 3))
else if (m_hover_id == 2 || m_hover_id == 3)
do_scale_along_axis(Y, data);
else if ((m_hover_id == 4) || (m_hover_id == 5))
else if (m_hover_id == 4 || m_hover_id == 5)
do_scale_along_axis(Z, data);
else if (m_hover_id >= 6)
do_scale_uniform(data);
@ -119,78 +127,175 @@ void GLGizmoScale3D::on_render()
{
const Selection& selection = m_parent.get_selection();
bool single_instance = selection.is_single_full_instance();
bool single_volume = selection.is_single_modifier() || selection.is_single_volume();
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_DEPTH_TEST));
m_box.reset();
m_bounding_box.reset();
#if ENABLE_WORLD_COORDINATE
m_grabbers_transform = Transform3d::Identity();
m_center = Vec3d::Zero();
m_instance_center = Vec3d::Zero();
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) {
#else
bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates();
if (selection.is_single_full_instance() && !world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#else
m_transform = Transform3d::Identity();
// Transforms grabbers' offsets to world refefence system
Transform3d offsets_transform = Transform3d::Identity();
m_offsets_transform = Transform3d::Identity();
Vec3d angles = Vec3d::Zero();
if (single_instance) {
if (selection.is_single_full_instance()) {
#endif // ENABLE_WORLD_COORDINATE
// calculate bounding box in instance local reference system
const Selection::IndicesList& idxs = selection.get_volume_idxs();
for (unsigned int idx : idxs) {
const GLVolume* vol = selection.get_volume(idx);
m_box.merge(vol->bounding_box().transformed(vol->get_volume_transformation().get_matrix()));
const GLVolume& v = *selection.get_volume(idx);
#if ENABLE_WORLD_COORDINATE
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
#else
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()));
#endif // ENABLE_WORLD_COORDINATE
}
#if ENABLE_WORLD_COORDINATE
m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true));
#endif // ENABLE_WORLD_COORDINATE
// gets transform from first selected volume
const GLVolume* v = selection.get_volume(*idxs.begin());
m_transform = v->get_instance_transformation().get_matrix();
const GLVolume& v = *selection.get_volume(*idxs.begin());
#if ENABLE_WORLD_COORDINATE
m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center());
m_center = selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center();
m_instance_center = v.get_instance_offset();
#else
m_transform = v.get_instance_transformation().get_matrix();
// gets angles from first selected volume
angles = v->get_instance_rotation();
angles = v.get_instance_rotation();
// consider rotation+mirror only components of the transform for offsets
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror());
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror());
m_offsets_transform = offsets_transform;
#endif // ENABLE_WORLD_COORDINATE
}
else if (single_volume) {
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
m_box = v->bounding_box();
m_transform = v->world_matrix();
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) {
#else
else if (selection.is_single_volume_or_modifier() && !world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#else
else if (selection.is_single_modifier() || selection.is_single_volume()) {
#endif // ENABLE_WORLD_COORDINATE
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, false, false, true)));
Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true));
#else
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)));
Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true) * v.get_volume_transformation().get_matrix(true, false, true));
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
trafo.set_offset(v.world_matrix().translation());
m_grabbers_transform = trafo.get_matrix();
m_center = v.world_matrix() * m_bounding_box.center();
m_instance_center = m_center;
}
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)));
Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true));
trafo.set_offset(v.world_matrix().translation());
m_grabbers_transform = trafo.get_matrix();
m_center = v.world_matrix() * m_bounding_box.center();
m_instance_center = m_center;
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else {
m_bounding_box = selection.get_bounding_box();
m_grabbers_transform = Geometry::assemble_transform(m_bounding_box.center());
m_center = m_bounding_box.center();
m_instance_center = selection.is_single_full_instance() ? selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_offset() : m_center;
}
#else
m_bounding_box = v.bounding_box();
m_transform = v.world_matrix();
angles = Geometry::extract_euler_angles(m_transform);
// consider rotation+mirror only components of the transform for offsets
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror());
m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v->get_volume_rotation(), Vec3d::Ones(), v->get_volume_mirror());
offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror());
m_offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation(), Vec3d::Ones(), v.get_volume_mirror());
}
else
m_box = selection.get_bounding_box();
m_bounding_box = selection.get_bounding_box();
const Vec3d& center = m_box.center();
Vec3d offset_x = offsets_transform * Vec3d((double)Offset, 0.0, 0.0);
Vec3d offset_y = offsets_transform * Vec3d(0.0, (double)Offset, 0.0);
Vec3d offset_z = offsets_transform * Vec3d(0.0, 0.0, (double)Offset);
Vec3d offset_x = offsets_transform * (Offset * Vec3d::UnitX());
Vec3d offset_y = offsets_transform * (Offset * Vec3d::UnitY());
Vec3d offset_z = offsets_transform * (Offset * Vec3d::UnitZ());
bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL));
bool ctrl_down = m_dragging && m_starting.ctrl_down || !m_dragging && wxGetKeyState(WXK_CONTROL);
#endif // ENABLE_WORLD_COORDINATE
#if ENABLE_WORLD_COORDINATE
// x axis
m_grabbers[0].center = m_transform * Vec3d(m_box.min(0), center(1), center(2)) - offset_x;
m_grabbers[0].color = (ctrl_down && (m_hover_id == 1)) ? CONSTRAINED_COLOR : AXES_COLOR[0];
m_grabbers[1].center = m_transform * Vec3d(m_box.max(0), center(1), center(2)) + offset_x;
m_grabbers[1].color = (ctrl_down && (m_hover_id == 0)) ? CONSTRAINED_COLOR : AXES_COLOR[0];
const Vec3d box_half_size = 0.5 * m_bounding_box.size();
bool use_constrain = wxGetKeyState(WXK_CONTROL) && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier());
m_grabbers[0].center = { -(box_half_size.x() + Offset), 0.0, 0.0 };
m_grabbers[0].color = (use_constrain && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0];
m_grabbers[1].center = { box_half_size.x() + Offset, 0.0, 0.0 };
m_grabbers[1].color = (use_constrain && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0];
// y axis
m_grabbers[2].center = m_transform * Vec3d(center(0), m_box.min(1), center(2)) - offset_y;
m_grabbers[2].color = (ctrl_down && (m_hover_id == 3)) ? CONSTRAINED_COLOR : AXES_COLOR[1];
m_grabbers[3].center = m_transform * Vec3d(center(0), m_box.max(1), center(2)) + offset_y;
m_grabbers[3].color = (ctrl_down && (m_hover_id == 2)) ? CONSTRAINED_COLOR : AXES_COLOR[1];
m_grabbers[2].center = { 0.0, -(box_half_size.y() + Offset), 0.0 };
m_grabbers[2].color = (use_constrain && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1];
m_grabbers[3].center = { 0.0, box_half_size.y() + Offset, 0.0 };
m_grabbers[3].color = (use_constrain && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1];
// z axis
m_grabbers[4].center = m_transform * Vec3d(center(0), center(1), m_box.min(2)) - offset_z;
m_grabbers[4].color = (ctrl_down && (m_hover_id == 5)) ? CONSTRAINED_COLOR : AXES_COLOR[2];
m_grabbers[5].center = m_transform * Vec3d(center(0), center(1), m_box.max(2)) + offset_z;
m_grabbers[5].color = (ctrl_down && (m_hover_id == 4)) ? CONSTRAINED_COLOR : AXES_COLOR[2];
m_grabbers[4].center = { 0.0, 0.0, -(box_half_size.z() + Offset) };
m_grabbers[4].color = (use_constrain && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2];
m_grabbers[5].center = { 0.0, 0.0, box_half_size.z() + Offset };
m_grabbers[5].color = (use_constrain && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2];
// uniform
m_grabbers[6].center = m_transform * Vec3d(m_box.min(0), m_box.min(1), center(2)) - offset_x - offset_y;
m_grabbers[7].center = m_transform * Vec3d(m_box.max(0), m_box.min(1), center(2)) + offset_x - offset_y;
m_grabbers[8].center = m_transform * Vec3d(m_box.max(0), m_box.max(1), center(2)) + offset_x + offset_y;
m_grabbers[9].center = m_transform * Vec3d(m_box.min(0), m_box.max(1), center(2)) - offset_x + offset_y;
m_grabbers[6].center = { -(box_half_size.x() + Offset), -(box_half_size.y() + Offset), 0.0 };
m_grabbers[6].color = (use_constrain && m_hover_id == 8) ? CONSTRAINED_COLOR : m_highlight_color;
m_grabbers[7].center = { box_half_size.x() + Offset, -(box_half_size.y() + Offset), 0.0 };
m_grabbers[7].color = (use_constrain && m_hover_id == 9) ? CONSTRAINED_COLOR : m_highlight_color;
m_grabbers[8].center = { box_half_size.x() + Offset, box_half_size.y() + Offset, 0.0 };
m_grabbers[8].color = (use_constrain && m_hover_id == 6) ? CONSTRAINED_COLOR : m_highlight_color;
m_grabbers[9].center = { -(box_half_size.x() + Offset), box_half_size.y() + Offset, 0.0 };
m_grabbers[9].color = (use_constrain && m_hover_id == 7) ? CONSTRAINED_COLOR : m_highlight_color;
#else
// x axis
const Vec3d center = m_bounding_box.center();
m_grabbers[0].center = m_transform * Vec3d(m_bounding_box.min.x(), center.y(), center.z()) - offset_x;
m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0];
m_grabbers[1].center = m_transform * Vec3d(m_bounding_box.max.x(), center.y(), center.z()) + offset_x;
m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0];
// y axis
m_grabbers[2].center = m_transform * Vec3d(center.x(), m_bounding_box.min.y(), center.z()) - offset_y;
m_grabbers[2].color = (ctrl_down && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1];
m_grabbers[3].center = m_transform * Vec3d(center.x(), m_bounding_box.max.y(), center.z()) + offset_y;
m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1];
// z axis
m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_bounding_box.min.z()) - offset_z;
m_grabbers[4].color = (ctrl_down && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2];
m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_bounding_box.max.z()) + offset_z;
m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2];
// uniform
m_grabbers[6].center = m_transform * Vec3d(m_bounding_box.min.x(), m_bounding_box.min.y(), center.z()) - offset_x - offset_y;
m_grabbers[7].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.min.y(), center.z()) + offset_x - offset_y;
m_grabbers[8].center = m_transform * Vec3d(m_bounding_box.max.x(), m_bounding_box.max.y(), center.z()) + offset_x + offset_y;
m_grabbers[9].center = m_transform * Vec3d(m_bounding_box.min.x(), m_bounding_box.max.y(), center.z()) - offset_x + offset_y;
for (int i = 6; i < 10; ++i) {
m_grabbers[i].color = m_highlight_color;
}
@ -199,12 +304,18 @@ void GLGizmoScale3D::on_render()
for (int i = 0; i < 10; ++i) {
m_grabbers[i].angles = angles;
}
#endif // ENABLE_WORLD_COORDINATE
glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f));
#if ENABLE_WORLD_COORDINATE
glsafe(::glPushMatrix());
transform_to_local(selection);
float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0);
#else
const BoundingBoxf3& selection_box = selection.get_bounding_box();
float grabber_mean_size = (float)((selection_box.size()(0) + selection_box.size()(1) + selection_box.size()(2)) / 3.0);
float grabber_mean_size = (float)((selection_box.size().x() + selection_box.size().y() + selection_box.size().z()) / 3.0);
#endif // ENABLE_WORLD_COORDINATE
if (m_hover_id == -1) {
// draw connections
@ -230,7 +341,7 @@ void GLGizmoScale3D::on_render()
}
else if (m_hover_id == 0 || m_hover_id == 1) {
// draw connection
glsafe(::glColor4fv(m_grabbers[0].color.data()));
glsafe(::glColor4fv(AXES_COLOR[0].data()));
render_grabbers_connection(0, 1);
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
@ -245,7 +356,7 @@ void GLGizmoScale3D::on_render()
}
else if (m_hover_id == 2 || m_hover_id == 3) {
// draw connection
glsafe(::glColor4fv(m_grabbers[2].color.data()));
glsafe(::glColor4fv(AXES_COLOR[1].data()));
render_grabbers_connection(2, 3);
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
@ -260,7 +371,7 @@ void GLGizmoScale3D::on_render()
}
else if (m_hover_id == 4 || m_hover_id == 5) {
// draw connection
glsafe(::glColor4fv(m_grabbers[4].color.data()));
glsafe(::glColor4fv(AXES_COLOR[2].data()));
render_grabbers_connection(4, 5);
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
@ -292,19 +403,29 @@ void GLGizmoScale3D::on_render()
shader->stop_using();
}
}
#if ENABLE_WORLD_COORDINATE
glsafe(::glPopMatrix());
#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoScale3D::on_render_for_picking()
{
glsafe(::glDisable(GL_DEPTH_TEST));
#if ENABLE_WORLD_COORDINATE
glsafe(::glPushMatrix());
transform_to_local(m_parent.get_selection());
render_grabbers_for_picking(m_bounding_box);
glsafe(::glPopMatrix());
#else
render_grabbers_for_picking(m_parent.get_selection().get_bounding_box());
#endif // ENABLE_WORLD_COORDINATE
}
void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const
{
unsigned int grabbers_count = (unsigned int)m_grabbers.size();
if ((id_1 < grabbers_count) && (id_2 < grabbers_count))
{
if (id_1 < grabbers_count && id_2 < grabbers_count) {
::glBegin(GL_LINES);
::glVertex3dv(m_grabbers[id_1].center.data());
::glVertex3dv(m_grabbers[id_2].center.data());
@ -314,16 +435,96 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int
void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data)
{
#if ENABLE_WORLD_COORDINATE
double ratio = calc_ratio(data);
if (ratio > 0.0)
{
if (ratio > 0.0) {
Vec3d curr_scale = m_scale;
Vec3d starting_scale = m_starting.scale;
const Selection& selection = m_parent.get_selection();
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
if (coordinates_type == ECoordinatesType::World) {
#else
const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates();
if (world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (selection.is_single_full_instance()) {
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation());
curr_scale = (m * curr_scale).cwiseAbs();
starting_scale = (m * starting_scale).cwiseAbs();
}
else if (selection.is_single_volume_or_modifier()) {
const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation());
const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation());
const Transform3d m = mi * mv;
curr_scale = (m * curr_scale).cwiseAbs();
starting_scale = (m * starting_scale).cwiseAbs();
}
}
curr_scale(axis) = starting_scale(axis) * ratio;
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (coordinates_type == ECoordinatesType::World) {
#else
if (world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (selection.is_single_full_instance())
m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs();
else if (selection.is_single_volume_or_modifier()) {
const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse();
const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse();
m_scale = (mv * mi * curr_scale).cwiseAbs();
}
else
m_scale = curr_scale;
}
else
m_scale = curr_scale;
#else
const double ratio = calc_ratio(data);
if (ratio > 0.0) {
m_scale(axis) = m_starting.scale(axis) * ratio;
if (m_starting.ctrl_down)
{
#endif // ENABLE_WORLD_COORDINATE
if (m_starting.ctrl_down) {
#if ENABLE_WORLD_COORDINATE
double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis);
#else
double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis);
#endif // ENABLE_WORLD_COORDINATE
if (m_hover_id == 2 * axis)
local_offset *= -1.0;
#if ENABLE_WORLD_COORDINATE
Vec3d center_offset = m_starting.instance_center - m_starting.center;
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (selection.is_single_full_instance() && coordinates_type != ECoordinatesType::World) {
#else
if (selection.is_single_full_instance() && !world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse();
center_offset = m * center_offset;
}
local_offset += (ratio - 1.0) * center_offset(axis);
switch (axis)
{
case X: { m_offset = local_offset * Vec3d::UnitX(); break; }
case Y: { m_offset = local_offset * Vec3d::UnitY(); break; }
case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; }
default: { m_offset = Vec3d::Zero(); break; }
}
#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (selection.is_single_volume_or_modifier() && !world_coordinates) {
const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation());
const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse();
m_offset = mv * mi * m_offset;
}
#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#else
Vec3d local_offset_vec;
switch (axis)
{
@ -334,6 +535,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data)
}
m_offset = m_offsets_transform * local_offset_vec;
#endif // ENABLE_WORLD_COORDINATE
}
else
m_offset = Vec3d::Zero();
@ -342,10 +544,45 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data)
void GLGizmoScale3D::do_scale_uniform(const UpdateData& data)
{
double ratio = calc_ratio(data);
if (ratio > 0.0)
{
const double ratio = calc_ratio(data);
if (ratio > 0.0) {
m_scale = m_starting.scale * ratio;
#if ENABLE_WORLD_COORDINATE
if (m_starting.ctrl_down) {
m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size();
if (m_hover_id == 6 || m_hover_id == 9)
m_offset.x() *= -1.0;
if (m_hover_id == 6 || m_hover_id == 7)
m_offset.y() *= -1.0;
const Selection& selection = m_parent.get_selection();
#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates();
#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
Vec3d center_offset = m_starting.instance_center - m_starting.center;
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) {
#else
if (selection.is_single_full_instance() && !world_coordinates) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse();
center_offset = m * center_offset;
}
m_offset += (ratio - 1.0) * center_offset;
#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (selection.is_single_volume_or_modifier() && !world_coordinates) {
const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation());
const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse();
m_offset = mv * mi * m_offset;
}
#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
}
else
#endif // ENABLE_WORLD_COORDINATE
m_offset = Vec3d::Zero();
}
}
@ -354,23 +591,27 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const
{
double ratio = 0.0;
Vec3d pivot = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_starting.pivots[m_hover_id] : m_starting.box.center();
#if ENABLE_WORLD_COORDINATE
const Vec3d starting_vec = m_starting.drag_position - m_starting.center;
#else
const Vec3d pivot = (m_starting.ctrl_down && m_hover_id < 6) ? m_starting.pivots[m_hover_id] : m_starting.box.center();
const Vec3d starting_vec = m_starting.drag_position - pivot;
#endif // ENABLE_WORLD_COORDINATE
Vec3d starting_vec = m_starting.drag_position - pivot;
double len_starting_vec = starting_vec.norm();
if (len_starting_vec != 0.0)
{
Vec3d mouse_dir = data.mouse_ray.unit_vector();
const double len_starting_vec = starting_vec.norm();
if (len_starting_vec != 0.0) {
const Vec3d mouse_dir = data.mouse_ray.unit_vector();
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
// in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
Vec3d inters = data.mouse_ray.a + (m_starting.drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
const Vec3d inters = data.mouse_ray.a + (m_starting.drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
// vector from the starting position to the found intersection
Vec3d inters_vec = inters - m_starting.drag_position;
const Vec3d inters_vec = inters - m_starting.drag_position;
// finds projection of the vector along the staring direction
double proj = inters_vec.dot(starting_vec.normalized());
const double proj = inters_vec.dot(starting_vec.normalized());
ratio = (len_starting_vec + proj) / len_starting_vec;
}
@ -381,5 +622,27 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const
return ratio;
}
#if ENABLE_WORLD_COORDINATE
void GLGizmoScale3D::transform_to_local(const Selection& selection) const
{
glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z()));
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
#else
if (!wxGetApp().obj_manipul()->get_world_coordinates()) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true);
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates())
#else
if (selection.is_single_volume_or_modifier())
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
orient_matrix = orient_matrix * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
}
#endif // ENABLE_WORLD_COORDINATE
} // namespace GUI
} // namespace Slic3r

View file

@ -5,32 +5,41 @@
#include "libslic3r/BoundingBox.hpp"
namespace Slic3r {
namespace GUI {
class GLGizmoScale3D : public GLGizmoBase
{
static const float Offset;
static const double Offset;
struct StartingData
{
Vec3d scale;
Vec3d drag_position;
bool ctrl_down{ false };
Vec3d scale{ Vec3d::Ones() };
Vec3d drag_position{ Vec3d::Zero() };
#if ENABLE_WORLD_COORDINATE
Vec3d center{ Vec3d::Zero() };
Vec3d instance_center{ Vec3d::Zero() };
#endif // ENABLE_WORLD_COORDINATE
BoundingBoxf3 box;
Vec3d pivots[6];
bool ctrl_down;
StartingData() : scale(Vec3d::Ones()), drag_position(Vec3d::Zero()), ctrl_down(false) { for (int i = 0; i < 5; ++i) { pivots[i] = Vec3d::Zero(); } }
#if !ENABLE_WORLD_COORDINATE
std::array<Vec3d, 6> pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() };
#endif // !ENABLE_WORLD_COORDINATE
};
mutable BoundingBoxf3 m_box;
mutable Transform3d m_transform;
BoundingBoxf3 m_bounding_box;
#if ENABLE_WORLD_COORDINATE
Transform3d m_grabbers_transform;
Vec3d m_center{ Vec3d::Zero() };
Vec3d m_instance_center{ Vec3d::Zero() };
#else
Transform3d m_transform;
// Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes)
mutable Transform3d m_offsets_transform;
Vec3d m_scale;
Vec3d m_offset;
double m_snap_step;
Transform3d m_offsets_transform;
#endif // ENABLE_WORLD_COORDINATE
Vec3d m_scale{ Vec3d::Ones() };
Vec3d m_offset{ Vec3d::Zero() };
double m_snap_step{ 0.05 };
StartingData m_starting;
public:
@ -62,6 +71,9 @@ private:
void do_scale_uniform(const UpdateData& data);
double calc_ratio(const UpdateData& data) const;
#if ENABLE_WORLD_COORDINATE
void transform_to_local(const Selection& selection) const;
#endif // ENABLE_WORLD_COORDINATE
};

View file

@ -243,9 +243,12 @@ void GLGizmosManager::update_data()
enable_grabber(Rotate, 0, !is_wipe_tower);
enable_grabber(Rotate, 1, !is_wipe_tower);
#if ENABLE_WORLD_COORDINATE
bool enable_scale_xyz = !selection.requires_uniform_scale();
#else
bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier();
for (unsigned int i = 0; i < 6; ++i)
{
#endif // ENABLE_WORLD_COORDINATE
for (unsigned int i = 0; i < 6; ++i) {
enable_grabber(Scale, i, enable_scale_xyz);
}
@ -254,8 +257,7 @@ void GLGizmosManager::update_data()
? get_current()->get_requirements()
: CommonGizmosDataID(0));
if (selection.is_single_full_instance())
{
if (selection.is_single_full_instance()) {
// all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
set_scale(volume->get_instance_scaling_factor());
@ -265,8 +267,11 @@ void GLGizmosManager::update_data()
set_sla_support_data(model_object);
set_painter_gizmo_data();
}
else if (selection.is_single_volume() || selection.is_single_modifier())
{
#if ENABLE_WORLD_COORDINATE
else if (selection.is_single_volume_or_modifier()) {
#else
else if (selection.is_single_volume() || selection.is_single_modifier()) {
#endif // ENABLE_WORLD_COORDINATE
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
set_scale(volume->get_volume_scaling_factor());
set_rotation(Vec3d::Zero());
@ -274,8 +279,7 @@ void GLGizmosManager::update_data()
set_sla_support_data(nullptr);
set_painter_gizmo_data();
}
else if (is_wipe_tower)
{
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));
@ -283,8 +287,7 @@ void GLGizmosManager::update_data()
set_sla_support_data(nullptr);
set_painter_gizmo_data();
}
else
{
else {
set_scale(Vec3d::Ones());
set_rotation(Vec3d::Zero());
set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
@ -619,26 +622,67 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
case Move:
{
// Apply new temporary offset
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
selection.translate(get_displacement(), wxGetApp().obj_manipul()->get_coordinates_type());
#else
selection.translate(get_displacement(), !wxGetApp().obj_manipul()->get_world_coordinates());
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#else
selection.translate(get_displacement());
#endif // ENABLE_WORLD_COORDINATE
wxGetApp().obj_manipul()->set_dirty();
break;
}
case Scale:
{
// Apply new temporary scale factors
#if ENABLE_WORLD_COORDINATE
TransformationType transformation_type;
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (!wxGetApp().obj_manipul()->is_world_coordinates())
#else
if (!wxGetApp().obj_manipul()->get_world_coordinates())
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
transformation_type.set_local();
#else
TransformationType transformation_type(TransformationType::Local_Absolute_Joint);
#endif // ENABLE_WORLD_COORDINATE
if (evt.AltDown())
transformation_type.set_independent();
selection.scale(get_scale(), transformation_type);
if (control_down)
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
selection.translate(get_scale_offset(), wxGetApp().obj_manipul()->get_coordinates_type());
#else
selection.translate(get_scale_offset(), !wxGetApp().obj_manipul()->get_world_coordinates());
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#else
selection.translate(get_scale_offset(), true);
#endif // ENABLE_WORLD_COORDINATE
wxGetApp().obj_manipul()->set_dirty();
break;
}
case Rotate:
{
// Apply new temporary rotations
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
TransformationType transformation_type;
switch (wxGetApp().obj_manipul()->get_coordinates_type())
{
default:
case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; }
case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; }
case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; }
}
#else
TransformationType transformation_type(wxGetApp().obj_manipul()->get_world_coordinates() ? TransformationType::World_Relative_Joint : TransformationType::Local_Relative_Joint);
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#else
TransformationType transformation_type(TransformationType::World_Relative_Joint);
#endif // ENABLE_WORLD_COORDINATE
if (evt.AltDown())
transformation_type.set_independent();
selection.rotate(get_rotation(), transformation_type);

View file

@ -294,6 +294,10 @@ void HintDatabase::uninit()
write_used_binary(m_used_ids);
}
m_initialized = false;
m_loaded_hints.clear();
m_sorted_hints = false;
m_used_ids.clear();
m_used_ids_loaded = false;
}
void HintDatabase::init()
{
@ -336,7 +340,8 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
size_t weight = 1;
bool was_displayed = is_used(id_string);
//unescape text1
unescape_string_cstyle(_utf8(dict["text"]), fulltext);
unescape_string_cstyle(dict["text"], fulltext);
fulltext = _utf8(fulltext);
// replace <b> and </b> for imgui markers
std::string marker_s(1, ImGui::ColorMarkerStart);
std::string marker_e(1, ImGui::ColorMarkerEnd);

View file

@ -56,6 +56,9 @@
#include "GUI_ObjectManipulation.hpp"
#include "GUI_ObjectLayers.hpp"
#include "GUI_Utils.hpp"
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#include "GUI_Geometry.hpp"
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#include "GUI_Factories.hpp"
#include "wxExtensions.hpp"
#include "MainFrame.hpp"
@ -1502,6 +1505,11 @@ void Sidebar::update_mode()
wxWindowUpdateLocker noUpdates(this);
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (m_mode == comSimple)
p->object_manipulation->set_coordinates_type(ECoordinatesType::World);
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
p->object_list->get_sizer()->Show(m_mode > comSimple);
p->object_list->unselect_objects();
@ -4192,20 +4200,17 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
menu = menus.sla_object_menu();
else {
const Selection& selection = get_selection();
// check if selected item is object's part
if (selection.is_single_volume() || selection.is_single_modifier()) {
int vol_idx = get_selected_volume_idx();
if (vol_idx < 0)
return;
menu = model.objects[obj_idx]->volumes[vol_idx]->text_configuration.has_value() ? menus.text_part_menu() : menus.part_menu();
}
else {
// show "Object menu" for each one or several FullInstance instead of FullObject
const bool is_some_full_instances = selection.is_single_full_instance() ||
selection.is_single_full_object() ||
selection.is_multiple_full_instance();
menu = is_some_full_instances ? menus.object_menu() : menus.multi_selection_menu();
}
// show "Object menu" for each one or several FullInstance instead of FullObject
const bool is_some_full_instances = selection.is_single_full_instance() ||
selection.is_single_full_object() ||
selection.is_multiple_full_instance();
#if ENABLE_WORLD_COORDINATE
const bool is_part = selection.is_single_volume_or_modifier();
#else
const bool is_part = selection.is_single_volume() || selection.is_single_modifier();
#endif // ENABLE_WORLD_COORDINATE
menu = is_some_full_instances ? menus.object_menu() :
is_part ? menus.part_menu() : menus.multi_selection_menu();
}
}

View file

@ -602,12 +602,82 @@ bool Selection::matches(const std::vector<unsigned int>& volume_idxs) const
return count == (unsigned int)m_list.size();
}
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
bool Selection::requires_uniform_scale(EUniformScaleRequiredReason* reason) const
#else
bool Selection::requires_uniform_scale() const
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
{
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
ECoordinatesType coord_type = wxGetApp().obj_manipul()->get_coordinates_type();
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (is_single_volume_or_modifier())
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
{
if (coord_type == ECoordinatesType::World) {
if (!Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation())) {
if (reason != nullptr)
*reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_World;
return true;
}
}
else if (coord_type == ECoordinatesType::Instance) {
if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_volume_rotation())) {
if (reason != nullptr)
*reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance;
return true;
}
}
return false;
}
#else
return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation());
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else if (is_single_full_instance())
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (coord_type == ECoordinatesType::World) {
if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation())) {
if (reason != nullptr)
*reason = EUniformScaleRequiredReason::InstanceNotAxisAligned_World;
return true;
}
else {
for (unsigned int i : m_list) {
if (!Geometry::is_rotation_ninety_degrees((*m_volumes)[i]->get_volume_rotation())) {
if (reason != nullptr)
*reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance;
return true;
}
}
}
return false;
}
else {
for (unsigned int i : m_list) {
if (!Geometry::is_rotation_ninety_degrees((*m_volumes)[i]->get_volume_rotation())) {
if (reason != nullptr)
*reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance;
return true;
}
}
return false;
}
if (reason != nullptr)
*reason = EUniformScaleRequiredReason::MultipleSelection;
#else
return wxGetApp().obj_manipul()->get_world_coordinates() ?
!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false;
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
return true;
#else
if (is_single_full_instance() || is_single_modifier() || is_single_volume())
return false;
return true;
#endif // ENABLE_WORLD_COORDINATE
}
int Selection::get_object_idx() const
@ -680,7 +750,7 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const
const GLVolume& volume = *(*m_volumes)[i];
if (volume.is_modifier)
continue;
Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix();
Transform3d trafo = volume.get_instance_transformation().get_matrix() * volume.get_volume_transformation().get_matrix();
trafo.translation().z() += volume.get_sla_shift_z();
(*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo));
}
@ -698,7 +768,11 @@ void Selection::start_dragging()
set_caches();
}
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
void Selection::translate(const Vec3d& displacement, ECoordinatesType type)
#else
void Selection::translate(const Vec3d& displacement, bool local)
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
{
if (!m_valid)
return;
@ -708,16 +782,51 @@ void Selection::translate(const Vec3d& displacement, bool local)
for (unsigned int i : m_list) {
GLVolume& v = *(*m_volumes)[i];
if (m_mode == Volume || v.is_wipe_tower) {
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (type == ECoordinatesType::Instance)
#else
if (local)
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement);
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else if (type == ECoordinatesType::Local) {
const VolumeCache& volume_data = m_cache.volumes_data[i];
const Vec3d local_displacement = volume_data.get_volume_rotation_matrix() * displacement;
v.set_volume_offset(volume_data.get_volume_position() + local_displacement);
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else {
#if ENABLE_WORLD_COORDINATE
const VolumeCache& volume_data = m_cache.volumes_data[i];
const Vec3d local_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix() * volume_data.get_instance_mirror_matrix()).inverse() * displacement;
v.set_volume_offset(volume_data.get_volume_position() + local_displacement);
#else
const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement;
v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement);
#endif // ENABLE_WORLD_COORDINATE
}
}
else if (m_mode == Instance) {
#if ENABLE_WORLD_COORDINATE
if (is_from_fully_selected_instance(i)) {
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (type == ECoordinatesType::Local) {
const VolumeCache& volume_data = m_cache.volumes_data[i];
const Vec3d world_displacement = volume_data.get_instance_rotation_matrix() * displacement;
#else
if (local) {
const VolumeCache& volume_data = m_cache.volumes_data[i];
const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_mirror_matrix()) * displacement;
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
v.set_instance_offset(volume_data.get_instance_position() + world_displacement);
}
else
v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement);
}
#else
if (is_from_fully_selected_instance(i))
v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement);
#endif // ENABLE_WORLD_COORDINATE
else {
const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement;
v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement);
@ -728,7 +837,7 @@ void Selection::translate(const Vec3d& displacement, bool local)
#if !DISABLE_INSTANCES_SYNCH
if (translation_type == Instance)
synchronize_unselected_instances(SYNC_ROTATION_NONE);
synchronize_unselected_instances(SyncRotationType::NONE);
else if (translation_type == Volume)
synchronize_unselected_volumes();
#endif // !DISABLE_INSTANCES_SYNCH
@ -781,11 +890,23 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
const GLVolume &first_volume = *(*m_volumes)[first_volume_idx];
const Vec3d &rotation = first_volume.get_instance_rotation();
const 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));
volume.set_instance_rotation(Vec3d(rotation.x(), rotation.y(), rotation.z() + z_diff));
}
else {
// extracts rotations from the composed transformation
Vec3d new_rotation = transformation_type.world() ?
#if ENABLE_WORLD_COORDINATE
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
const Vec3d new_rotation = transformation_type.world() ?
Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()) :
transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix() * m);
const Vec3d relative_instance_offset = m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center;
if (rot_axis_max == 2 && transformation_type.joint() && !relative_instance_offset.isApprox(Vec3d::Zero())) {
// Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis.
const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation);
volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * relative_instance_offset);
}
#else
const 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()) {
@ -793,6 +914,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation);
volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center));
}
#endif // ENABLE_WORLD_COORDINATE
volume.set_instance_rotation(new_rotation);
object_instance_first[volume.object_idx()] = i;
}
@ -802,23 +924,46 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
GLVolume &v = *(*m_volumes)[i];
if (is_single_full_instance())
rotate_instance(v, i);
else if (is_single_volume() || is_single_modifier()) {
if (transformation_type.independent())
v.set_volume_rotation(v.get_volume_rotation() + rotation);
else {
#if ENABLE_WORLD_COORDINATE
else if (is_single_volume_or_modifier()) {
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (transformation_type.local()) {
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
v.set_volume_rotation(new_rotation);
v.set_volume_rotation(Geometry::extract_euler_angles(m_cache.volumes_data[i].get_volume_rotation_matrix() * m));
}
else if (transformation_type.instance()) {
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
v.set_volume_rotation(Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()));
}
#else
if (transformation_type.local())
v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation);
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
else {
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
m = m * m_cache.volumes_data[i].get_instance_rotation_matrix();
m = m * m_cache.volumes_data[i].get_volume_rotation_matrix();
m = m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * m;
v.set_volume_rotation(Geometry::extract_euler_angles(m));
}
#else
else if (is_single_volume() || is_single_modifier()) {
if (transformation_type.independent())
v.set_volume_rotation(m_cache.volumes_data[i] + rotation);
else {
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
v.set_volume_rotation(new_rotation);
}
#endif // ENABLE_WORLD_COORDINATE
}
else
{
else {
if (m_mode == Instance)
rotate_instance(v, 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());
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
if (transformation_type.joint()) {
const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center;
const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot);
@ -830,12 +975,25 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
}
}
#if !DISABLE_INSTANCES_SYNCH
#if !DISABLE_INSTANCES_SYNCH
#if ENABLE_WORLD_COORDINATE
if (m_mode == Instance) {
SyncRotationType synch;
if (transformation_type.world() && rot_axis_max == 2)
synch = SyncRotationType::NONE;
else if (transformation_type.local())
synch = SyncRotationType::FULL;
else
synch = SyncRotationType::GENERAL;
synchronize_unselected_instances(synch);
}
#else
if (m_mode == Instance)
synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL);
synchronize_unselected_instances((rot_axis_max == 2) ? SyncRotationType::NONE : SyncRotationType::GENERAL);
#endif // ENABLE_WORLD_COORDINATE
else if (m_mode == Volume)
synchronize_unselected_volumes();
#endif // !DISABLE_INSTANCES_SYNCH
#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
@ -843,7 +1001,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
// make sure the wipe tower rotates around its center, not origin
// we can assume that only Z rotation changes
const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset();
const Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0.0, 0.0, 1.0)) * center_local;
const Vec3d center_local_new = Eigen::AngleAxisd(rotation.z()-volume.get_volume_rotation().z(), Vec3d::UnitZ()) * center_local;
volume.set_volume_rotation(rotation);
volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new);
}
@ -878,7 +1036,7 @@ void Selection::flattening_rotate(const Vec3d& normal)
// Apply the same transformation also to other instances,
// but respect their possibly diffrent z-rotation.
if (m_mode == Instance)
synchronize_unselected_instances(SYNC_ROTATION_GENERAL);
synchronize_unselected_instances(SyncRotationType::GENERAL);
#endif // !DISABLE_INSTANCES_SYNCH
this->set_bounding_boxes_dirty();
@ -893,16 +1051,19 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
GLVolume &v = *(*m_volumes)[i];
if (is_single_full_instance()) {
if (transformation_type.relative()) {
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale);
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3);
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale);
const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3);
// extracts scaling factors from the composed transformation
Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
if (transformation_type.joint())
v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center));
v.set_instance_scaling_factor(new_scale);
}
else {
#if ENABLE_WORLD_COORDINATE
v.set_instance_scaling_factor(scale);
#else
if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) {
// Non-uniform scaling. Transform the scaling factors into the local coordinate system.
// This is only possible, if the instance rotation is mulitples of ninety degrees.
@ -911,27 +1072,32 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
}
else
v.set_instance_scaling_factor(scale);
#endif // ENABLE_WORLD_COORDINATE
}
}
#if ENABLE_WORLD_COORDINATE
else if (is_single_volume_or_modifier())
#else
else if (is_single_volume() || is_single_modifier())
#endif // ENABLE_WORLD_COORDINATE
v.set_volume_scaling_factor(scale);
else {
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale);
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale);
if (m_mode == Instance) {
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3);
const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3);
// extracts scaling factors from the composed transformation
Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
if (transformation_type.joint())
v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center));
v.set_instance_scaling_factor(new_scale);
}
else if (m_mode == Volume) {
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3);
const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3);
// extracts scaling factors from the composed transformation
Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm());
if (transformation_type.joint()) {
Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center);
const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center);
v.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset);
}
v.set_volume_scaling_factor(new_scale);
@ -941,7 +1107,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
#if !DISABLE_INSTANCES_SYNCH
if (m_mode == Instance)
synchronize_unselected_instances(SYNC_ROTATION_NONE);
synchronize_unselected_instances(SyncRotationType::NONE);
else if (m_mode == Volume)
synchronize_unselected_volumes();
#endif // !DISABLE_INSTANCES_SYNCH
@ -1098,7 +1264,7 @@ void Selection::mirror(Axis axis)
#if !DISABLE_INSTANCES_SYNCH
if (m_mode == Instance)
synchronize_unselected_instances(SYNC_ROTATION_NONE);
synchronize_unselected_instances(SyncRotationType::NONE);
else if (m_mode == Volume)
synchronize_unselected_volumes();
#endif // !DISABLE_INSTANCES_SYNCH
@ -1346,10 +1512,36 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const
glsafe(::glPushMatrix());
if (!boost::starts_with(sidebar_field, "layer")) {
const Vec3d& center = get_bounding_box().center();
const Vec3d center = get_bounding_box().center();
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) {
#else
if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) {
glsafe(::glTranslated(center(0), center(1), center(2)));
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
glsafe(::glTranslated(center.x(), center.y(), center.z()));
#if ENABLE_WORLD_COORDINATE
Transform3d orient_matrix = Transform3d::Identity();
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
#else
if (boost::starts_with(sidebar_field, "position") || boost::starts_with(sidebar_field, "scale"))
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
else if (boost::starts_with(sidebar_field, "rotation")) {
if (boost::ends_with(sidebar_field, "x"))
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
else if (boost::ends_with(sidebar_field, "y")) {
const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation();
if (rotation.x() == 0.0)
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
else
orient_matrix.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()));
}
}
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
glsafe(::glMultMatrixd(orient_matrix.data()));
#else
if (!boost::starts_with(sidebar_field, "position")) {
Transform3d orient_matrix = Transform3d::Identity();
if (boost::starts_with(sidebar_field, "scale"))
@ -1368,15 +1560,40 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const
glsafe(::glMultMatrixd(orient_matrix.data()));
}
} else if (is_single_volume() || is_single_modifier()) {
glsafe(::glTranslated(center(0), center(1), center(2)));
#endif // ENABLE_WORLD_COORDINATE
}
#if ENABLE_WORLD_COORDINATE
else if (is_single_volume_or_modifier()) {
#else
else if (is_single_volume() || is_single_modifier()) {
#endif // ENABLE_WORLD_COORDINATE
glsafe(::glTranslated(center.x(), center.y(), center.z()));
#if ENABLE_WORLD_COORDINATE
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
if (!wxGetApp().obj_manipul()->is_world_coordinates()) {
Transform3d orient_matrix = Transform3d::Identity();
if (wxGetApp().obj_manipul()->is_local_coordinates()) {
#else
if (!wxGetApp().obj_manipul()->get_world_coordinates()) {
Transform3d orient_matrix = Transform3d::Identity();
if (boost::starts_with(sidebar_field, "scale")) {
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
const GLVolume* v = (*m_volumes)[*m_list.begin()];
orient_matrix = v->get_instance_transformation().get_matrix(true, false, true, true) * v->get_volume_transformation().get_matrix(true, false, true, true);
}
else
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
#else
Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
if (!boost::starts_with(sidebar_field, "position"))
orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
} else {
glsafe(::glTranslated(center(0), center(1), center(2)));
#endif // ENABLE_WORLD_COORDINATE
}
else {
glsafe(::glTranslated(center.x(), center.y(), center.z()));
if (requires_local_axes()) {
const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
@ -1411,8 +1628,7 @@ void Selection::copy_to_clipboard()
m_clipboard.reset();
for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content)
{
for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content) {
ModelObject* src_object = m_model->objects[object.first];
ModelObject* dst_object = m_clipboard.add_object();
dst_object->name = src_object->name;
@ -1425,26 +1641,22 @@ void Selection::copy_to_clipboard()
dst_object->layer_height_profile.assign(src_object->layer_height_profile);
dst_object->origin_translation = src_object->origin_translation;
for (int i : object.second)
{
for (int i : object.second) {
dst_object->add_instance(*src_object->instances[i]);
}
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
// Copy the ModelVolumes only for the selected GLVolumes of the 1st selected instance.
const GLVolume* volume = (*m_volumes)[i];
if ((volume->object_idx() == object.first) && (volume->instance_idx() == *object.second.begin()))
{
if (volume->object_idx() == object.first && volume->instance_idx() == *object.second.begin()) {
int volume_idx = volume->volume_idx();
if ((0 <= volume_idx) && (volume_idx < (int)src_object->volumes.size()))
{
if (0 <= volume_idx && volume_idx < (int)src_object->volumes.size()) {
ModelVolume* src_volume = src_object->volumes[volume_idx];
ModelVolume* dst_volume = dst_object->add_volume(*src_volume);
dst_volume->set_new_unique_id();
} else {
assert(false);
}
else
assert(false);
}
}
}
@ -1480,8 +1692,7 @@ std::vector<unsigned int> Selection::get_volume_idxs_from_object(unsigned int ob
{
std::vector<unsigned int> idxs;
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
{
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
if ((*m_volumes)[i]->object_idx() == (int)object_idx)
idxs.push_back(i);
}
@ -1493,10 +1704,9 @@ std::vector<unsigned int> Selection::get_volume_idxs_from_instance(unsigned int
{
std::vector<unsigned int> idxs;
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
{
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) {
const GLVolume* v = (*m_volumes)[i];
if ((v->object_idx() == (int)object_idx) && (v->instance_idx() == (int)instance_idx))
if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx)
idxs.push_back(i);
}
@ -1510,9 +1720,8 @@ std::vector<unsigned int> Selection::get_volume_idxs_from_volume(unsigned int ob
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i)
{
const GLVolume* v = (*m_volumes)[i];
if ((v->object_idx() == (int)object_idx) && (v->volume_idx() == (int)volume_idx))
{
if (((int)instance_idx != -1) && (v->instance_idx() == (int)instance_idx))
if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) {
if ((int)instance_idx != -1 && v->instance_idx() == (int)instance_idx)
idxs.push_back(i);
}
}
@ -1524,8 +1733,7 @@ std::vector<unsigned int> Selection::get_missing_volume_idxs_from(const std::vec
{
std::vector<unsigned int> idxs;
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
std::vector<unsigned int>::const_iterator it = std::find(volume_idxs.begin(), volume_idxs.end(), i);
if (it == volume_idxs.end())
idxs.push_back(i);
@ -1538,8 +1746,7 @@ std::vector<unsigned int> Selection::get_unselected_volume_idxs_from(const std::
{
std::vector<unsigned int> idxs;
for (unsigned int i : volume_idxs)
{
for (unsigned int i : volume_idxs) {
if (m_list.find(i) == m_list.end())
idxs.push_back(i);
}
@ -1557,8 +1764,7 @@ void Selection::update_type()
m_cache.content.clear();
m_type = Mixed;
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
const GLVolume* volume = (*m_volumes)[i];
int obj_idx = volume->object_idx();
int inst_idx = volume->instance_idx();
@ -1577,23 +1783,19 @@ void Selection::update_type()
{
if (m_list.empty())
m_type = Empty;
else if (m_list.size() == 1)
{
else if (m_list.size() == 1) {
const GLVolume* first = (*m_volumes)[*m_list.begin()];
if (first->is_wipe_tower)
m_type = WipeTower;
else if (first->is_modifier)
{
else if (first->is_modifier) {
m_type = SingleModifier;
requires_disable = true;
}
else
{
else {
const ModelObject* model_object = m_model->objects[first->object_idx()];
unsigned int volumes_count = (unsigned int)model_object->volumes.size();
unsigned int instances_count = (unsigned int)model_object->instances.size();
if (volumes_count * instances_count == 1)
{
if (volumes_count * instances_count == 1) {
m_type = SingleFullObject;
// ensures the correct mode is selected
m_mode = Instance;
@ -1604,15 +1806,13 @@ void Selection::update_type()
// ensures the correct mode is selected
m_mode = Instance;
}
else
{
else {
m_type = SingleVolume;
requires_disable = true;
}
}
}
else
{
else {
unsigned int sla_volumes_count = 0;
// Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is
for (unsigned int i : m_list) {
@ -1627,25 +1827,20 @@ void Selection::update_type()
unsigned int instances_count = (unsigned int)model_object->instances.size();
unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size();
if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size())
{
if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) {
m_type = SingleFullObject;
// ensures the correct mode is selected
m_mode = Instance;
}
else if (selected_instances_count == 1)
{
if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size())
{
else if (selected_instances_count == 1) {
if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) {
m_type = SingleFullInstance;
// ensures the correct mode is selected
m_mode = Instance;
}
else
{
else {
unsigned int modifiers_count = 0;
for (unsigned int i : m_list)
{
for (unsigned int i : m_list) {
if ((*m_volumes)[i]->is_modifier)
++modifiers_count;
}
@ -1658,25 +1853,21 @@ void Selection::update_type()
requires_disable = true;
}
}
else if ((selected_instances_count > 1) && (selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()))
{
else if (selected_instances_count > 1 && selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) {
m_type = MultipleFullInstance;
// ensures the correct mode is selected
m_mode = Instance;
}
}
else
{
else {
unsigned int sels_cntr = 0;
for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it)
{
for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) {
const ModelObject* model_object = m_model->objects[it->first];
unsigned int volumes_count = (unsigned int)model_object->volumes.size();
unsigned int instances_count = (unsigned int)model_object->instances.size();
sels_cntr += volumes_count * instances_count;
}
if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size())
{
if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) {
m_type = MultipleFullObject;
// ensures the correct mode is selected
m_mode = Instance;
@ -1687,8 +1878,7 @@ void Selection::update_type()
int object_idx = get_object_idx();
int instance_idx = get_instance_idx();
for (GLVolume* v : *m_volumes)
{
for (GLVolume* v : *m_volumes) {
v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false;
}
@ -1943,7 +2133,8 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field)
void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const
{
auto render_sidebar_rotation_hint = [this]() {
auto render_sidebar_rotation_hint = [this](const std::array<float, 4>& color) {
const_cast<GLModel*>(&m_curved_arrow)->set_color(-1, color);
m_curved_arrow.render();
glsafe(::glRotated(180.0, 0.0, 0.0, 1.0));
m_curved_arrow.render();
@ -1951,18 +2142,14 @@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field)
if (boost::ends_with(sidebar_field, "x")) {
glsafe(::glRotated(90.0, 0.0, 1.0, 0.0));
const_cast<GLModel*>(&m_curved_arrow)->set_color(-1, get_color(X));
render_sidebar_rotation_hint();
render_sidebar_rotation_hint(get_color(X));
}
else if (boost::ends_with(sidebar_field, "y")) {
glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0));
const_cast<GLModel*>(&m_curved_arrow)->set_color(-1, get_color(Y));
render_sidebar_rotation_hint();
}
else if (boost::ends_with(sidebar_field, "z")) {
const_cast<GLModel*>(&m_curved_arrow)->set_color(-1, get_color(Z));
render_sidebar_rotation_hint();
render_sidebar_rotation_hint(get_color(Y));
}
else if (boost::ends_with(sidebar_field, "z"))
render_sidebar_rotation_hint(get_color(Z));
}
void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) const
@ -2035,10 +2222,10 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co
const BoundingBoxf3& box = get_bounding_box();
const float min_x = box.min(0) - Margin;
const float max_x = box.max(0) + Margin;
const float min_y = box.min(1) - Margin;
const float max_y = box.max(1) + Margin;
const float min_x = box.min.x() - Margin;
const float max_x = box.max.x() + Margin;
const float min_y = box.min.y() - Margin;
const float max_y = box.max.y() + Margin;
// view dependend order of rendering to keep correct transparency
bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward();
@ -2146,7 +2333,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()));
switch (sync_rotation_type) {
case SYNC_ROTATION_NONE: {
case SyncRotationType::NONE: {
// z only rotation -> synch instance z
// The X,Y rotations should be synchronized from start to end of the rotation.
assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation()));
@ -2154,12 +2341,25 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
v->set_instance_offset(Z, volume->get_instance_offset().z());
break;
}
case SYNC_ROTATION_GENERAL:
case SyncRotationType::GENERAL: {
// generic rotation -> update instance z with the delta of the rotation.
const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation());
v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff });
break;
}
#if ENABLE_WORLD_COORDINATE
case SyncRotationType::FULL: {
// generic rotation -> update instance z with the delta of the rotation.
const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, m_cache.volumes_data[j].get_instance_rotation()));
const Vec3d& axis = angle_axis.axis();
const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ?
angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation());
v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff });
break;
}
#endif // ENABLE_WORLD_COORDINATE
}
v->set_instance_scaling_factor(scaling_factor);
v->set_instance_mirror(mirror);

View file

@ -2,6 +2,9 @@
#define slic3r_GUI_Selection_hpp_
#include "libslic3r/Geometry.hpp"
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#include "slic3r/GUI/GUI_Geometry.hpp"
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
#include "GLModel.hpp"
#include <set>
@ -26,6 +29,7 @@ using ModelObjectPtrs = std::vector<ModelObject*>;
namespace GUI {
#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
class TransformationType
{
public:
@ -78,6 +82,7 @@ private:
Enum m_value;
};
#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
class Selection
{
@ -284,6 +289,9 @@ public:
bool is_from_single_object() const;
bool is_sla_compliant() const;
bool is_instance_mode() const { return m_mode == Instance; }
#if ENABLE_WORLD_COORDINATE
bool is_single_volume_or_modifier() const { return is_single_volume() || is_single_modifier(); }
#endif // ENABLE_WORLD_COORDINATE
bool contains_volume(unsigned int volume_idx) const { return m_list.find(volume_idx) != m_list.end(); }
// returns true if the selection contains all the given indices
@ -293,7 +301,19 @@ public:
// returns true if the selection contains all and only the given indices
bool matches(const std::vector<unsigned int>& volume_idxs) const;
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
enum class EUniformScaleRequiredReason : unsigned char
{
NotRequired,
InstanceNotAxisAligned_World,
VolumeNotAxisAligned_World,
VolumeNotAxisAligned_Instance,
MultipleSelection,
};
bool requires_uniform_scale(EUniformScaleRequiredReason* reason = nullptr) const;
#else
bool requires_uniform_scale() const;
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
// Returns the the object id if the selection is from a single object, otherwise is -1
int get_object_idx() const;
@ -319,7 +339,11 @@ public:
void stop_dragging() { m_dragging = false; }
bool is_dragging() const { return m_dragging; }
#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
void translate(const Vec3d& displacement, ECoordinatesType type = ECoordinatesType::World);
#else
void translate(const Vec3d& displacement, bool local = false);
#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES
void rotate(const Vec3d& rotation, TransformationType transformation_type);
void flattening_rotate(const Vec3d& normal);
void scale(const Vec3d& scale, TransformationType transformation_type);
@ -378,11 +402,15 @@ private:
void render_sidebar_layers_hints(const std::string& sidebar_field) const;
public:
enum SyncRotationType {
enum class SyncRotationType {
// Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis.
SYNC_ROTATION_NONE = 0,
NONE = 0,
// Synchronize after rotation by an axis not parallel with Z.
SYNC_ROTATION_GENERAL = 1,
GENERAL = 1,
#if ENABLE_WORLD_COORDINATE
// Fully synchronize rotation.
FULL = 2,
#endif // ENABLE_WORLD_COORDINATE
};
void synchronize_unselected_instances(SyncRotationType sync_rotation_type);
void synchronize_unselected_volumes();

View file

@ -15,6 +15,7 @@
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "Http.hpp"
#include "libslic3r/AppConfig.hpp"
namespace fs = boost::filesystem;
@ -23,6 +24,30 @@ namespace pt = boost::property_tree;
namespace Slic3r {
namespace {
std::string substitute_host(const std::string& orig_addr, const std::string sub_addr)
{
//URI = scheme ":"["//"[userinfo "@"] host [":" port]] path["?" query]["#" fragment]
std::string final_addr = orig_addr;
// http
size_t double_dash = orig_addr.find("//");
size_t host_start = (double_dash == std::string::npos ? 0 : double_dash + 2);
// userinfo
size_t at = orig_addr.find("@");
host_start = (at != std::string::npos && at > host_start ? at + 1 : host_start);
// end of host, could be port, subpath (could be query or fragment?)
size_t host_end = orig_addr.find_first_of(":/?#", host_start);
host_end = (host_end == std::string::npos ? orig_addr.length() : host_end);
// now host_start and host_end should mark where to put resolved addr
// check host_start. if its nonsense, lets just use original addr (or resolved addr?)
if (host_start >= orig_addr.length()) {
return final_addr;
}
final_addr.replace(host_start, host_end - host_start, sub_addr);
return final_addr;
}
} //namespace
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
m_host(config->opt_string("print_host")),
m_apikey(config->opt_string("printhost_apikey")),
@ -115,8 +140,9 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
std::string url;
bool res = true;
if (m_host.find("https://") == 0 || test_msg.empty())
{
bool allow_ip_resolve = GUI::get_app_config()->get("allow_ip_resolve") == "1";
if (m_host.find("https://") == 0 || test_msg.empty() || !allow_ip_resolve) {
// If https is entered we assume signed ceritificate is being used
// IP resolving will not happen - it could resolve into address not being specified in cert
url = make_url("api/files/local");
@ -126,10 +152,13 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
// This new address returns in "test_msg" variable.
// Solves troubles of uploades failing with name address.
std::string resolved_addr = boost::nowide::narrow(test_msg);
// put ipv6 into [] brackets (there shouldn't be any http:// if its resolved addr)
// put ipv6 into [] brackets
if (resolved_addr.find(':') != std::string::npos && resolved_addr.at(0) != '[')
resolved_addr = "[" + resolved_addr + "]";
url = make_url("api/files/local", resolved_addr);
// in original address (m_host) replace host for resolved ip
std::string final_addr = substitute_host(m_host, resolved_addr);
BOOST_LOG_TRIVIAL(debug) << "Upload address after ip resolve: " << final_addr;
url = make_url("api/files/local", final_addr);
}
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")