mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-09 00:31:19 +00:00
merge main
This commit is contained in:
@@ -141,6 +141,20 @@ using namespace nlohmann;
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GuiColor.hpp"
|
||||
#include <GLFW/glfw3.h>
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/BuildVolume.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include "libslic3r/GCode/ToolOrdering.hpp"
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/Jobs/SendJob.hpp"
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
|
||||
+4
-1
@@ -5,7 +5,6 @@
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/Format/bbs_3mf.hpp"
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -14,6 +13,10 @@
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Format/AssembleList.hpp"
|
||||
|
||||
namespace Slic3r { class Preset; }
|
||||
namespace Slic3r { struct PlateBBoxData; }
|
||||
namespace Slic3r { struct ThumbnailData; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace IO {
|
||||
|
||||
@@ -35,6 +35,7 @@ extern "C"
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <boost/algorithm/string/constants.hpp>
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
class OpenGLVersionCheck
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
|
||||
#include "git_commit_hash.h"
|
||||
#include "libslic3r_version.h"
|
||||
#include "StackWalker.h"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <ctime>
|
||||
#include <excpt.h>
|
||||
|
||||
static std::string g_log_folder;
|
||||
static std::atomic<int> g_crash_log_count = 0;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include "StackWalker.h"
|
||||
#include <eh.h>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
class CBaseException : public CStackWalker
|
||||
{
|
||||
|
||||
@@ -57,6 +57,8 @@
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Format/STEP.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#include <strsafe.h>
|
||||
//#include <atlconv.h>
|
||||
#include <dbghelp.h>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#pragma comment(lib, "version.lib")
|
||||
#pragma comment( lib, "dbghelp.lib" )
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <Windows.h>
|
||||
#include <tchar.h>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
|
||||
namespace textconv_helper
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Algorithm {
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <string_view>
|
||||
|
||||
#ifdef WIN32
|
||||
//FIXME replace the two following includes with <boost/md5.hpp> after it becomes mainstream.
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#include "libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp"
|
||||
#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
|
||||
#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
|
||||
#include "libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp"
|
||||
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include "libslic3r/EdgeGrid.hpp"
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/HalfEdgeGraph.hpp"
|
||||
#include "utils/PolygonsSegmentIndex.hpp"
|
||||
#include "utils/ExtrusionJunction.hpp"
|
||||
#include "utils/ExtrusionLine.hpp"
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include "BeadingStrategy/BeadingStrategyFactory.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "utils/ExtrusionLine.hpp"
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
@@ -19,9 +19,7 @@
|
||||
#include "ExtrusionJunction.hpp"
|
||||
#include "../../Polyline.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
#include "../../BoundingBox.hpp"
|
||||
#include "../../ExtrusionEntity.hpp"
|
||||
#include "../../Flow.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
#include "SparsePointGrid.hpp"
|
||||
#include "PolygonsPointIndex.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include <libnest2d/utils/rotcalipers.hpp>
|
||||
|
||||
#include <numeric>
|
||||
#include <ClipperUtils.hpp>
|
||||
|
||||
#include <boost/geometry/index/rtree.hpp>
|
||||
#include <utility>
|
||||
@@ -45,6 +44,7 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/multiprecision/integer.hpp>
|
||||
#include <boost/rational.hpp>
|
||||
#include "MultiMaterialSegmentation.hpp"
|
||||
|
||||
namespace libnest2d {
|
||||
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
#include <cstdio>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <psapi.h>
|
||||
|
||||
@@ -32,6 +32,11 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "Config.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "MultiMaterialSegmentation.hpp"
|
||||
#include "SurfaceCollection.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
|
||||
#ifndef NDEBUG
|
||||
// #define BRIM_DEBUG_TO_SVG
|
||||
|
||||
+1015
-380
File diff suppressed because it is too large
Load Diff
@@ -78,7 +78,7 @@ struct CadFeature {
|
||||
// the OCCT shape verbatim — it is adopted as a base body in route_feature (no parametric
|
||||
// recipe). Downstream face/edge features (fillet/chamfer/cut/shell/...) act on it like any
|
||||
// other body. TopoDS_Shape is a cheap handle, so copying it through recompute/checkpoint
|
||||
// snapshots is cheap. In-session only for now (no BRep serialization yet).
|
||||
// snapshots is cheap. Saved with the recipe as a BRep string (see save()/load()).
|
||||
TopoDS_Shape imported_solid;
|
||||
|
||||
// Non-destructive placement transform for imported_regions (Text/SVG),
|
||||
@@ -116,6 +116,25 @@ struct CadFeature {
|
||||
double dressup_size{1.0}; // fillet radius or chamfer distance
|
||||
FaceGroup face_group{FaceGroup::All};
|
||||
int dressup_edge{-1}; // global edge id for edge-targeted fillet/chamfer; -1 = use face_group
|
||||
// Several picked edges dressed in ONE operation, all resolved against the same body, so
|
||||
// the ids cannot drift the way they do across a chain of single-edge features. When set,
|
||||
// dressup_edge holds the first of them: a build that predates the list still dresses that
|
||||
// one edge instead of falling back to the whole face group.
|
||||
std::vector<int> dressup_edges;
|
||||
// The edges this dress-up targets: the list, else the single edge, else none (face group).
|
||||
// Text feature: a Sketch whose imported_regions were vectorised from this string in this font
|
||||
// (a WxFontUtils descriptor, bold/italic included) at this cap height in mm. The regions are
|
||||
// what gets built — they are saved too, so the project opens on a machine without the font —
|
||||
// and these three are what an edit reopens the Text dialog with. Empty = not a text feature.
|
||||
std::string text_string;
|
||||
std::string text_font;
|
||||
double text_height{0.0};
|
||||
bool is_text() const { return !text_string.empty(); }
|
||||
std::vector<int> dressup_edge_ids() const {
|
||||
if (!dressup_edges.empty()) return dressup_edges;
|
||||
if (dressup_edge >= 0) return { dressup_edge };
|
||||
return {};
|
||||
}
|
||||
|
||||
// Hole params (positioned circular cut into the current body)
|
||||
double hole_diameter{5};
|
||||
@@ -134,12 +153,21 @@ struct CadFeature {
|
||||
std::string hole_standard; // provenance only, e.g. "M6" / "1/4-20"; not used by geometry
|
||||
|
||||
// Thread params (helical thread about the plane normal at a positioned point)
|
||||
double thread_radius{5}; // nominal cylinder radius
|
||||
double thread_radius{5}; // MAJOR (nominal) radius when thread_major_nominal,
|
||||
// else the legacy reference radius (see below)
|
||||
double thread_pitch{2}; // axial advance per turn
|
||||
double thread_height{10}; // total axial length
|
||||
double thread_depth{1}; // radial crest depth of the thread profile
|
||||
bool thread_internal{false}; // false = external threaded rod (New body);
|
||||
// true = tapped bore cut into the current body
|
||||
double thread_depth{1}; // radial depth of the thread profile
|
||||
bool thread_internal{false}; // false = external: the thread goes on the target body
|
||||
// (or on a standalone rod when there is none);
|
||||
// true = tapped bore cut into the target body
|
||||
// How thread_radius is read. true (every thread made since): it is the nominal MAJOR
|
||||
// radius, for both kinds — an internal thread bores to R - depth and grooves out to R, an
|
||||
// external one is a rod of radius R with its groove cut in to R - depth, so an M6 is 6 mm
|
||||
// across its crests either way. false (older recipes, kept bit-for-bit): the ridge was
|
||||
// added OUTSIDE R, so an internal thread given the minor diameter came out undersized and
|
||||
// an external one given the major diameter came out oversized.
|
||||
bool thread_major_nominal{false};
|
||||
double thread_x{0}; // axis position on the plane (u/x axis)
|
||||
double thread_y{0}; // axis position on the plane (v/y axis)
|
||||
|
||||
@@ -156,6 +184,9 @@ struct CadFeature {
|
||||
// target_body. revolve_axis: 0 = plane X axis, 1 = plane Y axis.
|
||||
double revolve_angle{360}; // sweep angle in degrees (1..360)
|
||||
int revolve_axis{0}; // 0 = plane X, 1 = plane Y
|
||||
// A Line of the profile sketch to revolve about instead (index into its entities: a
|
||||
// centerline, usually construction, or an edge of the profile itself); -1 = revolve_axis.
|
||||
int revolve_axis_entity{-1};
|
||||
|
||||
// Sweep: profile carried by sketch_ref / entities (like Extrude); the spine is a
|
||||
// second Sketch referenced by sweep_path_ref (an open or closed wire). Reuses
|
||||
@@ -178,6 +209,12 @@ struct CadFeature {
|
||||
double pattern_spacing{20}; // linear step (mm)
|
||||
int pattern_dir{0}; // linear direction: 0 = plane X, 1 = plane Y
|
||||
double pattern_angle{360}; // circular total angle (degrees)
|
||||
// How a circular pattern spreads its copies over pattern_angle. true (every pattern made
|
||||
// since): a full turn is split into `count` equal steps, anything less is spanned end to end,
|
||||
// first copy at 0 and last at pattern_angle — the way a pattern along a curve spans its
|
||||
// curve. false (older recipes, kept as they were built): always angle / count, so 90° with 3
|
||||
// copies stopped at 60°.
|
||||
bool pattern_inclusive{false};
|
||||
|
||||
// Pattern along a curve: when pattern_curve_sketch >= 0 this mode takes precedence over
|
||||
// linear/circular. Copies are placed at equal-parameter points along entity
|
||||
@@ -190,6 +227,16 @@ struct CadFeature {
|
||||
// BEFORE geometry runs. Empty (the common case) means the feature uses its literal fields.
|
||||
std::map<std::string, std::string> expr;
|
||||
|
||||
// Transient, never serialized. A body is referred to above by its INDEX, and an index is
|
||||
// only meaningful against the history it was taken in: delete, reorder or disable a
|
||||
// feature that makes a body and every later index points at a different body. So recompute
|
||||
// records, for each body field, the identity of the body it resolved to — the feature that
|
||||
// made it, and which of that feature's bodies it is — and when the history changes the
|
||||
// fields are re-pointed from those identities (see CadDocument::recompute).
|
||||
struct BodyId { int src{-1}; int ord{0}; };
|
||||
std::vector<BodyId> body_ref_ids;
|
||||
bool body_refs_pending{false};
|
||||
|
||||
// Datum/reference plane: a derived SketchPlane the document offers as a selectable
|
||||
// sketch plane (no solid). plane_base selects the reference (0=XY,1=XZ,2=YZ, or 3+N
|
||||
// = the Nth earlier datum plane); plane_offset shifts along the base normal;
|
||||
@@ -361,10 +408,62 @@ struct CadFeature {
|
||||
pattern_curve_sketch, pattern_curve_entity,
|
||||
expr,
|
||||
mate_kind, mate_cs_a, mate_cs_b, mate_offset, mate_angle, mate_flip,
|
||||
coordsys_face_kind, coordsys_face_edges);
|
||||
coordsys_face_kind, coordsys_face_edges,
|
||||
thread_major_nominal, pattern_inclusive,
|
||||
dressup_edges,
|
||||
text_string, text_font, text_height,
|
||||
revolve_axis_entity);
|
||||
}
|
||||
template<class Archive>
|
||||
void load(Archive& ar) {
|
||||
std::string brep;
|
||||
ar(type, name, enabled, shape, plane, width, height, radius,
|
||||
profile, entities, constraints, entity_constraints, imported_regions,
|
||||
import_offset, import_scale_x, import_scale_y, import_on_face, import_face_body,
|
||||
sketch_ref, distance, symmetric, mode, extrude_end, distance2, taper_deg, flip,
|
||||
up_to_face, extrude_src_face, up_to_point, target_body,
|
||||
dressup_size, face_group, dressup_edge,
|
||||
hole_diameter, hole_depth, hole_through, hole_x, hole_y,
|
||||
thread_radius, thread_pitch, thread_height, thread_depth, thread_internal, thread_x, thread_y,
|
||||
shell_thickness, shell_face,
|
||||
draft_face, draft_angle,
|
||||
revolve_angle, revolve_axis,
|
||||
sweep_path_ref, loft_profile_refs, loft_ruled,
|
||||
pattern_circular, pattern_count, pattern_spacing, pattern_dir, pattern_angle,
|
||||
plane_base, plane_offset, plane_angle_tilt, plane_axis,
|
||||
bool_tool_body, bool_keep_tool, bool_tolerance, bool_target_face, bool_tool_face,
|
||||
cut_offset, cut_flip, cut_keep_upper, cut_keep_lower,
|
||||
brep,
|
||||
plane_type, plane_face_body, plane_face, plane_face2_body, plane_face2,
|
||||
plane_edge_body, plane_edge, plane_edge2_body, plane_edge2, plane_u_size, plane_v_size,
|
||||
mirror_keep_original,
|
||||
axis_type, axis_p1, axis_p2, axis_body, axis_face, axis_edge, axis_plane_a, axis_plane_b,
|
||||
coordsys_type, coordsys_point, coordsys_body, coordsys_face, coordsys_edge, coordsys_x_hint,
|
||||
helix_radius, helix_pitch, helix_height, helix_left_handed, helix_taper_deg,
|
||||
xf_translate, xf_axis, xf_pivot, xf_angle_deg, xf_copy,
|
||||
thicken_face, thicken_thickness, thicken_flip,
|
||||
cut_face_body, cut_face,
|
||||
project_source_body, project_edges, project_face,
|
||||
delete_faces,
|
||||
hole_style, hole_cbore_diameter, hole_cbore_depth,
|
||||
hole_csink_diameter, hole_csink_angle, hole_standard,
|
||||
rib_sketch_ref, rib_entity, rib_thickness, rib_depth,
|
||||
pattern_curve_sketch, pattern_curve_entity,
|
||||
expr,
|
||||
mate_kind, mate_cs_a, mate_cs_b, mate_offset, mate_angle, mate_flip,
|
||||
coordsys_face_kind, coordsys_face_edges,
|
||||
thread_major_nominal, pattern_inclusive,
|
||||
dressup_edges,
|
||||
text_string, text_font, text_height,
|
||||
revolve_axis_entity);
|
||||
imported_solid = brep_from_string(brep);
|
||||
}
|
||||
// The pre-framing (v4) layout, FROZEN. A v4 recipe is one flat stream with no per-feature
|
||||
// length, so it can only be read with exactly the field list it was written with; reading it
|
||||
// through load() above breaks the moment a field is appended there (every field added since
|
||||
// v5 is appended ONLY above, never here).
|
||||
template<class Archive>
|
||||
void load_flat_v4(Archive& ar) {
|
||||
std::string brep;
|
||||
ar(type, name, enabled, shape, plane, width, height, radius,
|
||||
profile, entities, constraints, entity_constraints, imported_regions,
|
||||
@@ -403,6 +502,7 @@ struct CadFeature {
|
||||
coordsys_face_kind, coordsys_face_edges);
|
||||
imported_solid = brep_from_string(brep);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Serialize a TopoDS_Shape to/from a BRep string for cereal persistence.
|
||||
@@ -440,6 +540,13 @@ public:
|
||||
// Named document variables: name -> expression. Evaluated topologically each recompute();
|
||||
// an expression may reference other variables. Feature `expr` bindings resolve against these.
|
||||
std::map<std::string, std::string> variables;
|
||||
// Can a feature expression drive this numeric field? The single allow-list the evaluator
|
||||
// uses, so a caller can refuse a name before it breaks the next recompute.
|
||||
static bool is_bindable_field(const std::string& field);
|
||||
// Does a feature of this type leave a body behind? Sketches, datums, the helix curve and
|
||||
// Project (which emits sketch entities) do not. The one answer recompute(), the rollback
|
||||
// rule and the GUI's preview all use.
|
||||
static bool produces_body(CadFeatureType t);
|
||||
// Multi-body result of the last replay. A "New" extrude appends a body; other ops
|
||||
// mutate a target body. Empty after a failed/empty recompute.
|
||||
std::vector<CadBody> bodies;
|
||||
@@ -459,8 +566,16 @@ public:
|
||||
|
||||
// Modeling origin: the world point the default XY/XZ/YZ planes pass through. The GUI sets this
|
||||
// to the bed centre so sketches/datums land in the middle of the bed (not the bed corner =
|
||||
// world 0). Not serialized — the GUI re-applies it from the live bed on every tab show.
|
||||
// world 0) — for a NEW document. It is saved with the recipe: sketches bake it into their
|
||||
// planes while datum planes add it when they are resolved, so a project reopened on another
|
||||
// printer must keep the origin it was made with or its datums move and its sketches do not.
|
||||
Vec3d modeling_origin{Vec3d::Zero()};
|
||||
bool origin_from_recipe{false}; // modeling_origin came from the loaded project
|
||||
// Weld sketch endpoints within kSketchJoinTol (the "Auto-close sketch loops" preference, taken
|
||||
// when the document is started). A property of the DOCUMENT, saved with it: as a machine-wide
|
||||
// preference it made one project rebuild into a closed solid on one computer and an open
|
||||
// loop on another. recompute() pushes it into the kernel.
|
||||
bool auto_close_loops{true};
|
||||
|
||||
// Tessellation quality, matched to Orca's OWN STEP importer (Format/STEP.hpp defaults:
|
||||
// linear 0.003, angular 0.5 rad) so a body modelled here reaches the screen at the same
|
||||
@@ -513,8 +628,10 @@ public:
|
||||
BooleanMode mode, const std::string& name);
|
||||
int add_fillet(double radius, FaceGroup faces, const std::string& name);
|
||||
int add_fillet(double radius, int edge_id, const std::string& name);
|
||||
int add_fillet(double radius, const std::vector<int>& edge_ids, const std::string& name);
|
||||
int add_chamfer(double distance, FaceGroup faces, const std::string& name);
|
||||
int add_chamfer(double distance, int edge_id, const std::string& name);
|
||||
int add_chamfer(double distance, const std::vector<int>& edge_ids, const std::string& name);
|
||||
int add_hole(double diameter, double depth, bool through,
|
||||
double x, double y, const SketchPlane& plane,
|
||||
const std::string& name);
|
||||
@@ -707,6 +824,9 @@ public:
|
||||
// an Extrude, its sketch_ref are preserved from the original).
|
||||
bool remove_feature(int index);
|
||||
bool move_feature(int index, int delta);
|
||||
// Show or hide a feature. Transactional like the others, and it keeps body and datum-plane
|
||||
// references pointing at the same objects, which a bare `enabled` flip does not.
|
||||
bool set_feature_enabled(int index, bool enabled);
|
||||
bool replace_feature(int index, const CadFeature& edited);
|
||||
// replace_sketch_extrude: a box is two linked features (Sketch + Extrude);
|
||||
// overwrite both slots from one `edited` candidate (sketch params ->
|
||||
@@ -718,6 +838,9 @@ public:
|
||||
// tessellate the result into out_mesh, WITHOUT modifying features/body/
|
||||
// display_mesh. Returns false (with err set) if the candidate is invalid.
|
||||
// Used by the Design tab to show a translucent ghost before Confirm.
|
||||
// The solid body the closed profile of sketch `sketch_ref` lies on or touches, or -1. Drives
|
||||
// the Extrude default: a profile drawn on a body joins it, one in free space is a new body.
|
||||
int body_touching_sketch(int sketch_ref) const;
|
||||
bool preview(const CadFeature& candidate, TriangleMesh& out_mesh, std::string& err) const;
|
||||
// Same, but also returns the per-body meshes (in `bodies` order; the candidate may append
|
||||
// one), so the GUI can apply its display-only per-body Move transforms to the ghost and keep
|
||||
@@ -725,6 +848,16 @@ public:
|
||||
bool preview(const CadFeature& candidate, TriangleMesh& out_mesh,
|
||||
std::vector<TriangleMesh>& out_body_meshes, std::string& err) const;
|
||||
|
||||
// The faces of the current bodies that features[index] made, as (body, face id) pairs: what
|
||||
// the Design tab highlights when that feature is selected. A face counts when it lies on the
|
||||
// model's boundary, facing the same way, right after the feature and not right before it, so
|
||||
// a face a later feature trimmed still belongs to the one that made it. Positions are
|
||||
// compared, no history is kept: a face a later feature rebuilt in place stays the earlier
|
||||
// feature's. A feature with no face left of its own (a Boolean union, or one whose faces a
|
||||
// later feature removed) answers with every face of each body it changed. Empty for a feature
|
||||
// that leaves no body, a hidden one, or a history that no longer rebuilds up to it.
|
||||
std::vector<std::pair<int, int>> faces_made_by(int index) const;
|
||||
|
||||
private:
|
||||
TopoDS_Wire build_sketch_wire(const CadFeature& sketch, bool closed_only = false) const;
|
||||
// The planar region an Extrude sweeps: the sketch's outer loop with its inner loops as
|
||||
@@ -741,6 +874,13 @@ private:
|
||||
// starts a new body (empty list, or an Extrude with mode New) vs mutates an existing
|
||||
// one, then apply_feature. Shared by recompute() (replay all) and preview() (candidate).
|
||||
void route_feature(std::vector<CadBody>& bodies, const CadFeature& f) const;
|
||||
// The parametric pass every replay starts with: evaluate the variables and write each
|
||||
// feature's expression bindings into its numeric fields.
|
||||
void bind_expressions();
|
||||
// One step of the replay: route features[fi] into `built` and stamp the bodies it created
|
||||
// with fi. A hidden feature, a sketch, a helix or a datum leaves `built` alone. Throws on
|
||||
// failure.
|
||||
void replay_feature(size_t fi, std::vector<CadBody>& built);
|
||||
// Boolean between two existing bodies: resolve target + tool, optionally snap the tool so
|
||||
// the picked faces mate, run the OCCT op (with fuzzy tolerance), write the result back to the
|
||||
// target and erase the consumed tool. Mutates the bodies vector directly (unlike apply_feature,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "libslic3r/CAD/GeometryEngine.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
|
||||
#include <BRepMesh_IncrementalMesh.hxx>
|
||||
#include <BRep_Tool.hxx>
|
||||
@@ -73,19 +74,19 @@ std::vector<TopoDS_Shape> GeometryEngine::read_step_solids(const std::string& pa
|
||||
try {
|
||||
STEPControl_Reader reader;
|
||||
if (reader.ReadFile(path.c_str()) != IFSelect_RetDone) {
|
||||
err = "cannot read STEP file";
|
||||
err = _u8L("cannot read STEP file");
|
||||
return out;
|
||||
}
|
||||
reader.TransferRoots();
|
||||
const TopoDS_Shape shape = reader.OneShape();
|
||||
if (shape.IsNull()) { err = "STEP file has no geometry"; return out; }
|
||||
if (shape.IsNull()) { err = _u8L("STEP file has no geometry"); return out; }
|
||||
// One body per top-level solid; fall back to the whole shape (shells/faces) if none.
|
||||
for (TopExp_Explorer ex(shape, TopAbs_SOLID); ex.More(); ex.Next())
|
||||
out.push_back(ex.Current());
|
||||
if (out.empty())
|
||||
out.push_back(shape);
|
||||
} catch (const Standard_Failure& e) {
|
||||
err = *e.what() ? e.what() : "OCCT failed to read STEP";
|
||||
err = *e.what() ? e.what() : _u8L("OCCT failed to read STEP");
|
||||
out.clear();
|
||||
}
|
||||
return out;
|
||||
@@ -115,7 +116,7 @@ TopoDS_Shape GeometryEngine::mesh_to_brep(const indexed_triangle_set& its,
|
||||
if (tolerance <= 0.0)
|
||||
throw std::runtime_error("mesh_to_brep: tolerance must be > 0");
|
||||
if (its.indices.empty())
|
||||
throw std::runtime_error("mesh_to_brep: mesh has no triangles");
|
||||
throw std::runtime_error(_u8L("mesh_to_brep: mesh has no triangles"));
|
||||
|
||||
// 1. Tolerance-quantized vertex dedup. A merged vertex keeps the exact coordinates of the
|
||||
// first input occurrence — vertices are grouped by a cell, never snapped onto its grid.
|
||||
@@ -150,8 +151,8 @@ TopoDS_Shape GeometryEngine::mesh_to_brep(const indexed_triangle_set& its,
|
||||
}
|
||||
stats.kept_tris = int(tris.size());
|
||||
if (tris.empty())
|
||||
throw std::runtime_error("mesh_to_brep: every triangle was rejected as degenerate "
|
||||
"(try a smaller tolerance)");
|
||||
throw std::runtime_error(_u8L("mesh_to_brep: every triangle was rejected as degenerate "
|
||||
"(try a smaller tolerance)"));
|
||||
|
||||
// 3. One face per triangle, sharing vertices and edges through the caches.
|
||||
std::vector<TopoDS_Vertex> vertex_cache(verts.size());
|
||||
@@ -294,21 +295,21 @@ FaceGroup GeometryEngine::classify_face(const TopoDS_Face& face, const TopoDS_Sh
|
||||
|
||||
std::vector<TopoDS_Edge> GeometryEngine::collect_edges(const TopoDS_Shape& solid, FaceGroup target)
|
||||
{
|
||||
// Each edge ONCE. An explorer visits a shared edge from both of its faces, so walking one
|
||||
// handed every edge to the fillet twice; the de-duplicated map is also what edge_by_index
|
||||
// numbers edges by.
|
||||
std::vector<TopoDS_Edge> result;
|
||||
NCollection_IndexedDataMap<TopoDS_Shape, NCollection_List<TopoDS_Shape>, TopTools_ShapeMapHasher> edgeFaceMap;
|
||||
TopExp::MapShapesAndAncestors(solid, TopAbs_EDGE, TopAbs_FACE, edgeFaceMap);
|
||||
if (target == FaceGroup::All) {
|
||||
for (TopExp_Explorer exp(solid, TopAbs_EDGE); exp.More(); exp.Next())
|
||||
result.push_back(TopoDS::Edge(exp.Current()));
|
||||
for (int i = 1; i <= edgeFaceMap.Extent(); ++i)
|
||||
result.push_back(TopoDS::Edge(edgeFaceMap.FindKey(i)));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Build edge-to-face map once
|
||||
NCollection_IndexedDataMap<TopoDS_Shape, NCollection_List<TopoDS_Shape>, TopTools_ShapeMapHasher> edgeFaceMap;
|
||||
TopExp::MapShapesAndAncestors(solid, TopAbs_EDGE, TopAbs_FACE, edgeFaceMap);
|
||||
|
||||
for (TopExp_Explorer edgeExp(solid, TopAbs_EDGE); edgeExp.More(); edgeExp.Next()) {
|
||||
const TopoDS_Edge& edge = TopoDS::Edge(edgeExp.Current());
|
||||
if (!edgeFaceMap.Contains(edge)) continue;
|
||||
const NCollection_List<TopoDS_Shape>& faces = edgeFaceMap.FindFromKey(edge);
|
||||
for (int ei = 1; ei <= edgeFaceMap.Extent(); ++ei) {
|
||||
const TopoDS_Edge& edge = TopoDS::Edge(edgeFaceMap.FindKey(ei));
|
||||
const NCollection_List<TopoDS_Shape>& faces = edgeFaceMap.FindFromIndex(ei);
|
||||
|
||||
bool include = false;
|
||||
for (auto it = faces.begin(); it != faces.end(); ++it) {
|
||||
@@ -344,12 +345,14 @@ std::vector<TopoDS_Edge> GeometryEngine::collect_edges(const TopoDS_Shape& solid
|
||||
|
||||
// ---- Fillet/Chamfer ----
|
||||
|
||||
// All four dress-up entry points fail the same way — with a reason — instead of two of them
|
||||
// handing the solid back unchanged, which recompute then reported as a success.
|
||||
TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radius, FaceGroup faces)
|
||||
{
|
||||
if (radius <= 0.001) return solid;
|
||||
if (radius <= 0.001) throw std::runtime_error("the fillet radius must be greater than 0");
|
||||
|
||||
std::vector<TopoDS_Edge> edges = collect_edges(solid, faces);
|
||||
if (edges.empty()) return solid;
|
||||
if (edges.empty()) throw std::runtime_error("no edges to fillet in that group");
|
||||
|
||||
BRepFilletAPI_MakeFillet fillet(solid);
|
||||
for (const auto& edge : edges)
|
||||
@@ -359,53 +362,67 @@ TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radi
|
||||
// A too-large radius (e.g. >= half the smallest spanned dimension) makes the
|
||||
// operation degenerate; OCCT leaves IsDone() false. Report it instead of
|
||||
// silently returning the unfilleted solid (which reads as a false success).
|
||||
if (!fillet.IsDone()) throw std::runtime_error("fillet radius too large for this geometry");
|
||||
if (!fillet.IsDone()) throw std::runtime_error(_u8L("fillet radius too large for this geometry"));
|
||||
return fillet.Shape();
|
||||
}
|
||||
|
||||
TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double distance, FaceGroup faces)
|
||||
{
|
||||
if (distance <= 0.001) return solid;
|
||||
if (distance <= 0.001) throw std::runtime_error("the chamfer distance must be greater than 0");
|
||||
|
||||
std::vector<TopoDS_Edge> edges = collect_edges(solid, faces);
|
||||
if (edges.empty()) return solid;
|
||||
if (edges.empty()) throw std::runtime_error("no edges to chamfer in that group");
|
||||
|
||||
BRepFilletAPI_MakeChamfer chamfer(solid);
|
||||
for (const auto& edge : edges)
|
||||
chamfer.Add(distance, edge); // symmetric chamfer
|
||||
chamfer.Build();
|
||||
|
||||
if (!chamfer.IsDone()) throw std::runtime_error("chamfer distance too large for this geometry");
|
||||
if (!chamfer.IsDone()) throw std::runtime_error(_u8L("chamfer distance too large for this geometry"));
|
||||
return chamfer.Shape();
|
||||
}
|
||||
|
||||
TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radius, int edge_id)
|
||||
{
|
||||
if (radius <= 0.001) return solid;
|
||||
|
||||
TopoDS_Edge edge = edge_by_index(solid, edge_id);
|
||||
if (edge.IsNull()) throw std::runtime_error("apply_fillet: invalid edge id");
|
||||
|
||||
BRepFilletAPI_MakeFillet mk(solid);
|
||||
mk.Add(radius, edge);
|
||||
mk.Build();
|
||||
|
||||
if (!mk.IsDone()) throw std::runtime_error("apply_fillet: OCCT fillet failed");
|
||||
return mk.Shape();
|
||||
return apply_fillet(solid, radius, std::vector<int>{ edge_id });
|
||||
}
|
||||
|
||||
TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double distance, int edge_id)
|
||||
{
|
||||
if (distance <= 0.001) return solid;
|
||||
return apply_chamfer(solid, distance, std::vector<int>{ edge_id });
|
||||
}
|
||||
|
||||
TopoDS_Edge edge = edge_by_index(solid, edge_id);
|
||||
if (edge.IsNull()) throw std::runtime_error("apply_chamfer: invalid edge id");
|
||||
TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radius, const std::vector<int>& edge_ids)
|
||||
{
|
||||
if (radius <= 0.001) throw std::runtime_error("the fillet radius must be greater than 0");
|
||||
if (edge_ids.empty()) throw std::runtime_error("apply_fillet: no edge picked");
|
||||
|
||||
BRepFilletAPI_MakeChamfer mk(solid);
|
||||
mk.Add(distance, edge);
|
||||
BRepFilletAPI_MakeFillet mk(solid);
|
||||
for (int id : edge_ids) {
|
||||
TopoDS_Edge edge = edge_by_index(solid, id);
|
||||
if (edge.IsNull()) throw std::runtime_error("apply_fillet: invalid edge id");
|
||||
mk.Add(radius, edge);
|
||||
}
|
||||
mk.Build();
|
||||
|
||||
if (!mk.IsDone()) throw std::runtime_error("apply_chamfer: OCCT chamfer failed");
|
||||
if (!mk.IsDone()) throw std::runtime_error(_u8L("apply_fillet: OCCT fillet failed"));
|
||||
return mk.Shape();
|
||||
}
|
||||
|
||||
TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double distance, const std::vector<int>& edge_ids)
|
||||
{
|
||||
if (distance <= 0.001) throw std::runtime_error("the chamfer distance must be greater than 0");
|
||||
if (edge_ids.empty()) throw std::runtime_error("apply_chamfer: no edge picked");
|
||||
|
||||
BRepFilletAPI_MakeChamfer mk(solid);
|
||||
for (int id : edge_ids) {
|
||||
TopoDS_Edge edge = edge_by_index(solid, id);
|
||||
if (edge.IsNull()) throw std::runtime_error("apply_chamfer: invalid edge id");
|
||||
mk.Add(distance, edge);
|
||||
}
|
||||
mk.Build();
|
||||
|
||||
if (!mk.IsDone()) throw std::runtime_error(_u8L("apply_chamfer: OCCT chamfer failed"));
|
||||
return mk.Shape();
|
||||
}
|
||||
|
||||
@@ -531,12 +548,13 @@ GeometryEngine::MassProps GeometryEngine::mass_properties(const TopoDS_Shape& sh
|
||||
std::string GeometryEngine::primitive_name(PrimitiveType type)
|
||||
{
|
||||
switch (type) {
|
||||
case PrimitiveType::Box: return "Box";
|
||||
case PrimitiveType::Cylinder: return "Cylinder";
|
||||
case PrimitiveType::Sphere: return "Sphere";
|
||||
case PrimitiveType::Cone: return "Cone";
|
||||
case PrimitiveType::Torus: return "Torus";
|
||||
default: return "Unknown";
|
||||
// TRN Default name of an object created from the box primitive shape.
|
||||
case PrimitiveType::Box: return _u8L("Box");
|
||||
case PrimitiveType::Cylinder: return _u8L("Cylinder");
|
||||
case PrimitiveType::Sphere: return _u8L("Sphere");
|
||||
case PrimitiveType::Cone: return _u8L("Cone");
|
||||
case PrimitiveType::Torus: return _u8L("Torus");
|
||||
default: return _u8L("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,6 +632,27 @@ std::vector<Vec3d> GeometryEngine::sample_edge_world(const TopoDS_Edge& edge, do
|
||||
return pts;
|
||||
}
|
||||
|
||||
std::vector<std::vector<Vec3d>> GeometryEngine::display_edges(const TopoDS_Shape& shape, double chord_tol)
|
||||
{
|
||||
NCollection_IndexedDataMap<TopoDS_Shape, NCollection_List<TopoDS_Shape>, TopTools_ShapeMapHasher> faces_of_edge;
|
||||
TopExp::MapShapesAndAncestors(shape, TopAbs_EDGE, TopAbs_FACE, faces_of_edge);
|
||||
std::vector<std::vector<Vec3d>> out;
|
||||
for (int i = 1; i <= faces_of_edge.Extent(); ++i) {
|
||||
const TopoDS_Edge& edge = TopoDS::Edge(faces_of_edge.FindKey(i));
|
||||
if (BRep_Tool::Degenerated(edge))
|
||||
continue;
|
||||
bool seam = false;
|
||||
for (NCollection_List<TopoDS_Shape>::Iterator it(faces_of_edge.FindFromIndex(i)); it.More() && !seam; it.Next())
|
||||
seam = BRep_Tool::IsClosed(edge, TopoDS::Face(it.Value()));
|
||||
if (seam)
|
||||
continue;
|
||||
std::vector<Vec3d> pts = sample_edge_world(edge, chord_tol);
|
||||
if (pts.size() >= 2)
|
||||
out.push_back(std::move(pts));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Vec3d GeometryEngine::face_centroid_world(const TopoDS_Face& face)
|
||||
{
|
||||
GProp_GProps props;
|
||||
|
||||
@@ -39,8 +39,9 @@ struct PrimitiveParams {
|
||||
double dressup_radius{1.0}; // fillet radius
|
||||
double dressup_chamfer_dist{1.0}; // chamfer distance (symmetric)
|
||||
|
||||
// Mesh quality
|
||||
double linear_deflection{0.01};
|
||||
// Mesh quality — the Design tab's own density (CadDocument::linear_deflection), so a
|
||||
// primitive and the same body modelled in the Design tab reach the screen alike.
|
||||
double linear_deflection{0.003};
|
||||
double angular_deflection{0.5};
|
||||
|
||||
template<class Archive>
|
||||
@@ -122,9 +123,14 @@ public:
|
||||
FaceGroup faces = FaceGroup::All);
|
||||
static TopoDS_Shape apply_chamfer(const TopoDS_Shape& solid, double distance,
|
||||
int edge_id);
|
||||
// Several edges in one operation, all ids resolved against `solid`.
|
||||
static TopoDS_Shape apply_fillet(const TopoDS_Shape& solid, double radius,
|
||||
const std::vector<int>& edge_ids);
|
||||
static TopoDS_Shape apply_chamfer(const TopoDS_Shape& solid, double distance,
|
||||
const std::vector<int>& edge_ids);
|
||||
|
||||
static TriangleMesh tessellate(const TopoDS_Shape& shape,
|
||||
double linear_deflection = 0.01,
|
||||
double linear_deflection = 0.003,
|
||||
double angular_deflection = 0.5);
|
||||
static std::string primitive_name(PrimitiveType type);
|
||||
|
||||
@@ -146,6 +152,10 @@ public:
|
||||
static Vec3d face_normal_world(const TopoDS_Face& face);
|
||||
// Sample an edge into a world-space polyline (>=2 pts) for pick-distance + highlight.
|
||||
static std::vector<Vec3d> sample_edge_world(const TopoDS_Edge& edge, double chord_tol = 0.05);
|
||||
// The edges a viewer draws over a body, each as a polyline: every edge of the shape once,
|
||||
// without degenerate edges (a cone apex) and without the seam of a closed surface (the line
|
||||
// down a cylinder's side), which is where OCCT closes the parameter space, not a real edge.
|
||||
static std::vector<std::vector<Vec3d>> display_edges(const TopoDS_Shape& shape, double chord_tol);
|
||||
// 0-based edge index into TopExp::MapShapes(shape, TopAbs_EDGE, map).
|
||||
static int edge_count(const TopoDS_Shape& shape);
|
||||
static TopoDS_Edge edge_by_index(const TopoDS_Shape& shape, int index);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "libslic3r/CAD/SketchEngine.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
|
||||
#include <Standard_Handle.hxx>
|
||||
#include <GeomAbs_SurfaceType.hxx>
|
||||
@@ -19,6 +20,8 @@
|
||||
#include <BRepBuilderAPI_MakeVertex.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepCheck_Analyzer.hxx>
|
||||
#include <BRepAdaptor_Curve.hxx>
|
||||
#include <BRepGProp.hxx>
|
||||
#include <GProp_GProps.hxx>
|
||||
#include <BRepClass_FaceClassifier.hxx>
|
||||
@@ -56,32 +59,29 @@
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <GeomAPI_IntCS.hxx>
|
||||
#include <map>
|
||||
#include <atomic>
|
||||
#include <math.h>
|
||||
#include <tuple>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
class Geom_TrimmedCurve;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Single source of truth for the weld tolerance the viewport and the kernel share.
|
||||
// Defaults ON so headless/kernel-only callers keep welding; the GUI pushes the
|
||||
// "auto_close_sketch_loops" preference in via set_sketch_auto_close().
|
||||
static bool s_auto_close = true;
|
||||
// Single source of truth for the weld tolerance the viewport and the kernel share. Defaults ON
|
||||
// so headless/kernel-only callers keep welding. It is the DOCUMENT's setting
|
||||
// (CadDocument::auto_close_loops, saved with the recipe), pushed in by CadDocument::recompute:
|
||||
// the same project must rebuild into the same solid on every machine. Atomic because the GUI
|
||||
// rebuilds on a worker thread while the viewport reads it.
|
||||
static std::atomic<bool> s_auto_close{true};
|
||||
|
||||
double sketch_join_tol() { return s_auto_close ? kSketchJoinTol : 0.0; }
|
||||
void set_sketch_auto_close(bool on) { s_auto_close = on; }
|
||||
double sketch_join_tol() { return s_auto_close.load() ? kSketchJoinTol : 0.0; }
|
||||
void set_sketch_auto_close(bool on) { s_auto_close.store(on); }
|
||||
|
||||
// ---- SketchPlane ----
|
||||
|
||||
gp_Pln SketchPlane::to_occt() const
|
||||
{
|
||||
gp_Pnt o(origin.x(), origin.y(), origin.z());
|
||||
gp_Dir n(normal.x(), normal.y(), normal.z());
|
||||
gp_Dir x(x_axis.x(), x_axis.y(), x_axis.z());
|
||||
return gp_Pln(gp_Ax3(o, n, x));
|
||||
}
|
||||
|
||||
SketchPlane SketchPlane::from_face(const TopoDS_Face& face)
|
||||
{
|
||||
SketchPlane sp;
|
||||
@@ -136,30 +136,10 @@ Vec3d SketchPlane::to_world(const Vec2d& pt) const
|
||||
|
||||
// ---- SketchProfile ----
|
||||
|
||||
bool SketchProfile::is_closed(double tolerance) const
|
||||
{
|
||||
if (points.size() < 3) return false;
|
||||
return (points.front() - points.back()).norm() < tolerance;
|
||||
}
|
||||
|
||||
bool SketchProfile::try_close(double tolerance)
|
||||
{
|
||||
if (is_closed(tolerance)) {
|
||||
closed = true;
|
||||
return true;
|
||||
}
|
||||
if (points.size() < 2) return false;
|
||||
if ((points.front() - points.back()).norm() < tolerance) {
|
||||
closed = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TopoDS_Wire SketchProfile::to_occt_wire(const SketchPlane& plane) const
|
||||
{
|
||||
if (points.size() < 2)
|
||||
throw std::runtime_error("Profile has fewer than 2 points");
|
||||
throw std::runtime_error(_u8L("Profile has fewer than 2 points"));
|
||||
|
||||
BRepBuilderAPI_MakeWire builder;
|
||||
for (size_t i = 0; i < points.size(); ++i) {
|
||||
@@ -171,7 +151,7 @@ TopoDS_Wire SketchProfile::to_occt_wire(const SketchPlane& plane) const
|
||||
}
|
||||
builder.Build();
|
||||
if (!builder.IsDone())
|
||||
throw std::runtime_error("Failed to build wire from profile");
|
||||
throw std::runtime_error(_u8L("Failed to build wire from profile"));
|
||||
return builder.Wire();
|
||||
}
|
||||
|
||||
@@ -179,9 +159,9 @@ TopoDS_Wire SketchProfile::to_occt_wire(const SketchPlane& plane) const
|
||||
|
||||
TopoDS_Shape SketchEngine::make_prism(const TopoDS_Shape& base, const gp_Vec& vec)
|
||||
{
|
||||
if (vec.Magnitude() < 1e-9) throw std::runtime_error("extrude depth is zero");
|
||||
if (vec.Magnitude() < 1e-9) throw std::runtime_error(_u8L("extrude depth is zero"));
|
||||
BRepPrimAPI_MakePrism prism(base, vec);
|
||||
if (!prism.IsDone()) throw std::runtime_error("extrude failed");
|
||||
if (!prism.IsDone()) throw std::runtime_error(_u8L("extrude failed"));
|
||||
return prism.Shape();
|
||||
}
|
||||
|
||||
@@ -190,7 +170,7 @@ static TopoDS_Shape extrude_face_internal(const TopoDS_Face& face, const gp_Dir&
|
||||
if (symmetric) {
|
||||
gp_Vec halfVec = gp_Vec(dir) * (length / 2.0);
|
||||
BRepAlgoAPI_Fuse fuse(SketchEngine::make_prism(face, halfVec), SketchEngine::make_prism(face, -halfVec));
|
||||
if (!fuse.IsDone()) throw std::runtime_error("Fuse failed");
|
||||
if (!fuse.IsDone()) throw std::runtime_error(_u8L("Fuse failed"));
|
||||
return fuse.Shape();
|
||||
}
|
||||
return SketchEngine::make_prism(face, gp_Vec(dir) * length);
|
||||
@@ -200,7 +180,7 @@ TopoDS_Shape SketchEngine::make_extrude(const TopoDS_Wire& wire, const SketchPla
|
||||
double length, bool symmetric, double taper_deg)
|
||||
{
|
||||
BRepBuilderAPI_MakeFace fm(wire);
|
||||
if (!fm.IsDone()) throw std::runtime_error("Failed to make face from wire");
|
||||
if (!fm.IsDone()) throw std::runtime_error(_u8L("Failed to make face from wire"));
|
||||
return make_extrude(fm.Face(), plane, length, symmetric, taper_deg);
|
||||
}
|
||||
|
||||
@@ -215,7 +195,7 @@ TopoDS_Shape SketchEngine::make_extrude_two_sided(const TopoDS_Wire& wire, const
|
||||
double up, double down)
|
||||
{
|
||||
BRepBuilderAPI_MakeFace fm(wire);
|
||||
if (!fm.IsDone()) throw std::runtime_error("Failed to make face from wire");
|
||||
if (!fm.IsDone()) throw std::runtime_error(_u8L("Failed to make face from wire"));
|
||||
return make_extrude_two_sided(fm.Face(), plane, up, down);
|
||||
}
|
||||
|
||||
@@ -227,7 +207,7 @@ TopoDS_Shape SketchEngine::make_extrude_two_sided(const TopoDS_Face& face, const
|
||||
if (d < 1e-9) return make_prism(face, gp_Vec(dir) * u);
|
||||
if (u < 1e-9) return make_prism(face, gp_Vec(dir) * -d);
|
||||
BRepAlgoAPI_Fuse fuse(make_prism(face, gp_Vec(dir) * u), make_prism(face, gp_Vec(dir) * -d));
|
||||
if (!fuse.IsDone()) throw std::runtime_error("two-sided extrude fuse failed");
|
||||
if (!fuse.IsDone()) throw std::runtime_error(_u8L("two-sided extrude fuse failed"));
|
||||
return fuse.Shape();
|
||||
}
|
||||
|
||||
@@ -237,7 +217,7 @@ TopoDS_Shape SketchEngine::make_extrude_taper(const TopoDS_Wire& wire, const Ske
|
||||
gp_Dir dir(plane.normal.x(), plane.normal.y(), plane.normal.z());
|
||||
auto straight = [&]() -> TopoDS_Shape {
|
||||
BRepBuilderAPI_MakeFace fm(wire);
|
||||
if (!fm.IsDone()) throw std::runtime_error("Failed to make face from wire");
|
||||
if (!fm.IsDone()) throw std::runtime_error(_u8L("Failed to make face from wire"));
|
||||
return make_prism(fm.Face(), gp_Vec(dir) * length);
|
||||
};
|
||||
if (std::abs(taper_deg) >= 89.0 || std::abs(length) < 1e-9) return straight();
|
||||
@@ -283,14 +263,16 @@ TopoDS_Shape SketchEngine::make_extrude_regions(
|
||||
const SketchPlane& plane, double length, bool symmetric)
|
||||
{
|
||||
// The loop below skips regions that fail, so a zero depth is rejected before it.
|
||||
if (std::abs(length) < 1e-9) throw std::runtime_error("extrude depth is zero");
|
||||
if (std::abs(length) < 1e-9) throw std::runtime_error(_u8L("extrude depth is zero"));
|
||||
|
||||
// Drop consecutive coincident points and the closing duplicate. FreeType /
|
||||
// SVG flattening routinely emits repeated points which would build a
|
||||
// degenerate OCCT edge and make the wire builder throw — sanitising keeps a
|
||||
// single bad glyph from killing the whole extrude.
|
||||
auto clean = [](const std::vector<Vec2d>& pts) {
|
||||
const double eps2 = 1e-12; // ~1e-6 mm
|
||||
// The sketch's own joint tolerance: points closer than this are one point everywhere
|
||||
// else in the sketcher, so they must not become a sub-micron edge here either.
|
||||
const double eps2 = kSketchJoinTol * kSketchJoinTol;
|
||||
std::vector<Vec2d> out;
|
||||
out.reserve(pts.size());
|
||||
for (const Vec2d& p : pts)
|
||||
@@ -318,6 +300,10 @@ TopoDS_Shape SketchEngine::make_extrude_regions(
|
||||
};
|
||||
|
||||
gp_Dir dir(plane.normal.x(), plane.normal.y(), plane.normal.z());
|
||||
// Faces are built ON the sketch plane, as wires_to_face does: a surface inferred from the
|
||||
// outer wire need not share the sketch normal, and then the extrude direction and the hole
|
||||
// classification disagree with it.
|
||||
const gp_Pln pln(gp_Pnt(plane.origin.x(), plane.origin.y(), plane.origin.z()), dir);
|
||||
|
||||
// Accumulate each region's solid into a compound rather than boolean-fusing:
|
||||
// glyphs are independent profiles, so a compound avoids every boolean-failure
|
||||
@@ -338,7 +324,10 @@ TopoDS_Shape SketchEngine::make_extrude_regions(
|
||||
// classify outer vs holes by area/containment and set correct wire
|
||||
// orientations. This is winding-independent, so holed glyphs extrude
|
||||
// with a solid body and empty counters regardless of source winding.
|
||||
BRepBuilderAPI_MakeFace fm(outer);
|
||||
// Probe on the inferred surface first: naming a plane makes MakeFace accept a wire
|
||||
// that bounds nothing, and this check is what skips such a contour.
|
||||
if (!BRepBuilderAPI_MakeFace(outer).IsDone()) continue;
|
||||
BRepBuilderAPI_MakeFace fm(pln, outer);
|
||||
if (!fm.IsDone()) continue;
|
||||
for (size_t h = 1; h < region.size(); ++h) {
|
||||
TopoDS_Wire hole = contour_wire(region[h]);
|
||||
@@ -360,31 +349,48 @@ TopoDS_Shape SketchEngine::make_extrude_regions(
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) throw std::runtime_error("imported regions produced no extrudable geometry");
|
||||
if (count == 0) throw std::runtime_error(_u8L("imported regions produced no extrudable geometry"));
|
||||
return count == 1 ? last : TopoDS_Shape(comp); // avoid a compound-of-one
|
||||
}
|
||||
|
||||
TopoDS_Shape SketchEngine::make_revolve(const TopoDS_Wire& wire, const SketchPlane& plane,
|
||||
double angle_deg, int axis_sel)
|
||||
TopoDS_Shape SketchEngine::make_revolve(const TopoDS_Wire& wire, const gp_Ax1& axis_in, double angle_deg)
|
||||
{
|
||||
BRepBuilderAPI_MakeFace faceMaker(wire);
|
||||
if (!faceMaker.IsDone())
|
||||
throw std::runtime_error("Failed to make face from wire");
|
||||
throw std::runtime_error(_u8L("Failed to make face from wire"));
|
||||
TopoDS_Face face = faceMaker.Face();
|
||||
|
||||
// Revolution axis lies in the sketch plane through its origin: X (0) or Y (1).
|
||||
const Vec3d& adir = (axis_sel == 1) ? plane.y_axis : plane.x_axis;
|
||||
gp_Pnt o(plane.origin.x(), plane.origin.y(), plane.origin.z());
|
||||
gp_Dir xd(adir.x(), adir.y(), adir.z());
|
||||
gp_Ax1 axis(o, xd);
|
||||
|
||||
// A profile on both sides of the axis sweeps through itself; MakeRevol then fails with no
|
||||
// reason, or builds an invalid solid. Sample every edge and name the cause instead.
|
||||
{
|
||||
const gp_Pnt o = axis_in.Location();
|
||||
const gp_Dir d = axis_in.Direction();
|
||||
bool pos = false, neg = false;
|
||||
gp_Vec side_ref;
|
||||
for (TopExp_Explorer ex(wire, TopAbs_EDGE); ex.More(); ex.Next()) {
|
||||
BRepAdaptor_Curve c(TopoDS::Edge(ex.Current()));
|
||||
for (int i = 0; i <= 16; ++i) {
|
||||
const gp_Pnt p = c.Value(c.FirstParameter() + (c.LastParameter() - c.FirstParameter()) * i / 16.0);
|
||||
const gp_Vec off = gp_Vec(o, p) - gp_Vec(d) * gp_Vec(o, p).Dot(gp_Vec(d)); // from the axis
|
||||
if (off.Magnitude() < 1e-6)
|
||||
continue;
|
||||
if (side_ref.Magnitude() == 0.0) { side_ref = off; pos = true; continue; }
|
||||
(off.Dot(side_ref) > 0.0 ? pos : neg) = true;
|
||||
}
|
||||
}
|
||||
if (pos && neg)
|
||||
throw std::runtime_error("the profile crosses the revolve axis — it must lie on one side of it");
|
||||
}
|
||||
gp_Ax1 axis = axis_in;
|
||||
double angle_rad = angle_deg * M_PI / 180.0;
|
||||
// A negative angle is expressed as a positive sweep about the reversed axis,
|
||||
// since BRepPrimAPI_MakeRevol expects an angle in (0, 2*pi].
|
||||
if (angle_rad < 0) { axis.Reverse(); angle_rad = -angle_rad; }
|
||||
BRepPrimAPI_MakeRevol rev(face, axis, angle_rad);
|
||||
if (!rev.IsDone())
|
||||
throw std::runtime_error("Failed to revolve");
|
||||
throw std::runtime_error(_u8L("Failed to revolve"));
|
||||
if (!BRepCheck_Analyzer(rev.Shape()).IsValid())
|
||||
throw std::runtime_error("the profile crosses the revolve axis — it must lie on one side of it");
|
||||
return rev.Shape();
|
||||
}
|
||||
|
||||
@@ -392,29 +398,29 @@ TopoDS_Shape SketchEngine::make_sweep(const TopoDS_Wire& profile, const TopoDS_W
|
||||
{
|
||||
BRepBuilderAPI_MakeFace faceMaker(profile);
|
||||
if (!faceMaker.IsDone())
|
||||
throw std::runtime_error("Failed to make face from sweep profile");
|
||||
throw std::runtime_error(_u8L("Failed to make face from sweep profile"));
|
||||
TopoDS_Face face = faceMaker.Face();
|
||||
|
||||
BRepOffsetAPI_MakePipe pipe(path, face);
|
||||
pipe.Build();
|
||||
if (!pipe.IsDone())
|
||||
throw std::runtime_error("Failed to sweep profile along path");
|
||||
throw std::runtime_error(_u8L("Failed to sweep profile along path"));
|
||||
return pipe.Shape();
|
||||
}
|
||||
|
||||
TopoDS_Shape SketchEngine::make_loft(const std::vector<TopoDS_Wire>& profiles, bool ruled)
|
||||
{
|
||||
if (profiles.size() < 2)
|
||||
throw std::runtime_error("loft needs at least 2 profiles");
|
||||
throw std::runtime_error(_u8L("loft needs at least 2 profiles"));
|
||||
BRepOffsetAPI_ThruSections loft(true /*solid*/, ruled);
|
||||
for (const TopoDS_Wire& w : profiles) {
|
||||
if (w.IsNull()) throw std::runtime_error("loft: null profile wire");
|
||||
loft.AddWire(w);
|
||||
}
|
||||
loft.Build();
|
||||
if (!loft.IsDone()) throw std::runtime_error("loft failed");
|
||||
if (!loft.IsDone()) throw std::runtime_error(_u8L("loft failed"));
|
||||
TopoDS_Shape s = loft.Shape();
|
||||
if (s.IsNull()) throw std::runtime_error("loft produced no solid");
|
||||
if (s.IsNull()) throw std::runtime_error(_u8L("loft produced no solid"));
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -422,16 +428,16 @@ TopoDS_Shape SketchEngine::make_loft(const std::vector<TopoDS_Wire>& profiles, b
|
||||
TopoDS_Shape SketchEngine::make_loft_surface(const std::vector<TopoDS_Wire>& profiles, bool ruled)
|
||||
{
|
||||
if (profiles.size() < 2)
|
||||
throw std::runtime_error("loft needs at least 2 profiles");
|
||||
throw std::runtime_error(_u8L("loft needs at least 2 profiles"));
|
||||
BRepOffsetAPI_ThruSections loft(false /*shell, no end caps*/, ruled);
|
||||
for (const TopoDS_Wire& w : profiles) {
|
||||
if (w.IsNull()) throw std::runtime_error("loft: null profile wire");
|
||||
loft.AddWire(w);
|
||||
}
|
||||
loft.Build();
|
||||
if (!loft.IsDone()) throw std::runtime_error("loft failed");
|
||||
if (!loft.IsDone()) throw std::runtime_error(_u8L("loft failed"));
|
||||
TopoDS_Shape s = loft.Shape();
|
||||
if (s.IsNull()) throw std::runtime_error("loft produced no shape");
|
||||
if (s.IsNull()) throw std::runtime_error(_u8L("loft produced no shape"));
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -439,11 +445,11 @@ TopoDS_Shape SketchEngine::make_pocket(const TopoDS_Wire& wire, const SketchPlan
|
||||
const TopoDS_Shape& target, double depth)
|
||||
{
|
||||
BRepBuilderAPI_MakeFace fm(wire);
|
||||
if (!fm.IsDone()) throw std::runtime_error("Pocket face failed");
|
||||
if (!fm.IsDone()) throw std::runtime_error(_u8L("Pocket face failed"));
|
||||
TopoDS_Shape tool = extrude_face_internal(fm.Face(),
|
||||
gp_Dir(plane.normal.x(), plane.normal.y(), plane.normal.z()), depth + 1.0, false);
|
||||
BRepAlgoAPI_Cut cut(target, tool);
|
||||
if (!cut.IsDone()) throw std::runtime_error("Pocket cut failed");
|
||||
if (!cut.IsDone()) throw std::runtime_error(_u8L("Pocket cut failed"));
|
||||
return cut.Shape();
|
||||
}
|
||||
|
||||
@@ -579,7 +585,12 @@ std::vector<TopoDS_Wire> SketchEngine::entities_to_wires(const std::vector<Sketc
|
||||
auto make_elips = [&](const SketchEntity& c) -> gp_Elips {
|
||||
Vec3d c3 = plane.to_world(c.center);
|
||||
gp_Pnt center(c3.x(), c3.y(), c3.z());
|
||||
gp_Dir n(plane.normal.x(), plane.normal.y(), plane.normal.z());
|
||||
// The frame's normal is x_axis x y_axis, not plane.normal: OCCT takes the ellipse's Y
|
||||
// as N x X, and the parametric angles were measured in the sketch's own (x, y). The two
|
||||
// agree on XY and YZ; the XZ base plane stores normal = +Y while x x y = -Y, so there
|
||||
// every elliptical arc came out mirrored against what the sketch showed.
|
||||
const Vec3d nz = plane.x_axis.cross(plane.y_axis);
|
||||
gp_Dir n(nz.x(), nz.y(), nz.z());
|
||||
Vec2d maj2(std::cos(c.rotation), std::sin(c.rotation));
|
||||
Vec3d x3 = plane.to_world(c.center + maj2) - c3;
|
||||
gp_Dir xdir(x3.x(), x3.y(), x3.z());
|
||||
@@ -703,6 +714,9 @@ std::vector<TopoDS_Wire> SketchEngine::entities_to_wires(const std::vector<Sketc
|
||||
if (c.radius <= 1e-9 || c.rminor <= 1e-9) return {};
|
||||
e = BRepBuilderAPI_MakeEdge(make_elips(c)).Edge();
|
||||
} else {
|
||||
// Same guard as the ellipse above: a zero radius would reach OCCT and throw from
|
||||
// .Edge() instead of reporting a sketch that cannot be built.
|
||||
if (c.radius <= 1e-9) return {};
|
||||
Vec3d c3 = plane.to_world(c.center);
|
||||
gp_Pnt center(c3.x(), c3.y(), c3.z());
|
||||
gp_Dir n(plane.normal.x(), plane.normal.y(), plane.normal.z());
|
||||
@@ -901,10 +915,22 @@ std::vector<Vec2d> sketch_open_ends(const std::vector<SketchEntity>& entities,
|
||||
return out;
|
||||
}
|
||||
|
||||
// A closed wire can still fail to bound a region: a loop that crosses itself, or one that
|
||||
// doubles back along itself (a cusp, e.g. an arc leaving a line tangent to it but in the
|
||||
// opposite direction). MakeFace reports success on both and the prism built from the face is
|
||||
// an invalid solid with no caps. Refuse it here, with the reason, instead of shipping that.
|
||||
static TopoDS_Face checked_profile_face(const TopoDS_Face& f)
|
||||
{
|
||||
BRepCheck_Analyzer an(f);
|
||||
if (!an.IsValid())
|
||||
throw std::runtime_error("the profile crosses or folds back on itself, so it does not bound one region");
|
||||
return f;
|
||||
}
|
||||
|
||||
TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
|
||||
const SketchPlane& plane)
|
||||
{
|
||||
if (wires.empty()) throw std::runtime_error("sketch has no closed loop");
|
||||
if (wires.empty()) throw std::runtime_error(_u8L("sketch has no closed loop"));
|
||||
|
||||
// The ASSEMBLED face below is built on the SKETCH's own plane rather than on a surface OCCT
|
||||
// infers from the outer wire. The inferred plane has no reason to share the sketch's normal,
|
||||
@@ -919,8 +945,8 @@ TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
|
||||
|
||||
if (wires.size() == 1) {
|
||||
BRepBuilderAPI_MakeFace fm(wires[0]);
|
||||
if (!fm.IsDone()) throw std::runtime_error("sketch loop does not bound a face");
|
||||
return fm.Face();
|
||||
if (!fm.IsDone()) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
|
||||
return checked_profile_face(fm.Face());
|
||||
}
|
||||
|
||||
// Two or more loops: build a face per wire and let the largest area be the outer
|
||||
@@ -931,7 +957,7 @@ TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
|
||||
areas.reserve(wires.size());
|
||||
for (const TopoDS_Wire& w : wires) {
|
||||
BRepBuilderAPI_MakeFace fm(w);
|
||||
if (!fm.IsDone()) throw std::runtime_error("sketch loop does not bound a face");
|
||||
if (!fm.IsDone()) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
|
||||
faces.push_back(fm.Face());
|
||||
GProp_GProps props;
|
||||
BRepGProp::SurfaceProperties(faces.back(), props);
|
||||
@@ -957,10 +983,10 @@ TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
|
||||
got = true;
|
||||
break;
|
||||
}
|
||||
if (!got) throw std::runtime_error("sketch loop does not bound a face");
|
||||
if (!got) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
|
||||
BRepClass_FaceClassifier fc(faces[outer], p, 1e-7);
|
||||
if (fc.State() != TopAbs_IN)
|
||||
throw std::runtime_error("sketch has two disjoint regions; put each in its own sketch");
|
||||
throw std::runtime_error(_u8L("sketch has two disjoint regions; put each in its own sketch"));
|
||||
// Add the hole loop AS-IS and let ShapeFix_Face sort the orientations out below.
|
||||
// Reversing it here only works when the sketch happened to wind both loops the same
|
||||
// way: a circle drawn clockwise inside a counter-clockwise rectangle comes out matching
|
||||
@@ -970,13 +996,13 @@ TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
|
||||
// 147520 — a body larger than its own bounding box, which is the signature of it.
|
||||
fm.Add(wires[i]);
|
||||
}
|
||||
if (!fm.IsDone()) throw std::runtime_error("sketch loop does not bound a face");
|
||||
if (!fm.IsDone()) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
|
||||
// Winding-independent classification of outer vs holes — the same idiom make_extrude_regions
|
||||
// already uses for imported glyphs, which is why holed TEXT extruded correctly all along
|
||||
// while a holed SKETCH did not.
|
||||
ShapeFix_Face sff(fm.Face());
|
||||
sff.FixOrientation();
|
||||
return sff.Face();
|
||||
return checked_profile_face(sff.Face());
|
||||
}
|
||||
|
||||
std::vector<SketchEntity> SketchEngine::mirror_entities(
|
||||
@@ -1048,8 +1074,11 @@ std::vector<SketchEntity> SketchEngine::mirror_entities(
|
||||
} else {
|
||||
m.p0 = reflect(e.p0);
|
||||
m.p1 = reflect(e.p1);
|
||||
// Reflection reverses orientation: recompute parametric angles in
|
||||
// the reflected frame, original end -> new start (CCW sense kept).
|
||||
// Reflection reverses orientation. Like the Arc branch above, this pass keeps
|
||||
// each angle with ITS point (start with p0) and lets the sweep run clockwise;
|
||||
// the reversal pass below then swaps points and angles together, giving a CCW
|
||||
// arc whose start is p0. Pairing them crosswise here, as this used to, was
|
||||
// undone by that same swap and produced the complementary arc.
|
||||
auto param = [&](const Vec2d& P) {
|
||||
const Vec2d d = P - m.center;
|
||||
const double cu = std::cos(m.rotation), su = std::sin(m.rotation);
|
||||
@@ -1057,8 +1086,9 @@ std::vector<SketchEntity> SketchEngine::mirror_entities(
|
||||
const double v = -d.x() * su + d.y() * cu;
|
||||
return std::atan2(v / std::max(e.rminor, 1e-9), u / std::max(e.radius, 1e-9));
|
||||
};
|
||||
m.start_angle = param(m.p1);
|
||||
m.end_angle = param(m.p0);
|
||||
m.start_angle = param(m.p0);
|
||||
m.end_angle = param(m.p1);
|
||||
while (m.end_angle >= m.start_angle) m.end_angle -= 2.0 * M_PI;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1118,9 +1148,14 @@ std::vector<SketchEntity> SketchEngine::mirror_entities(
|
||||
// get their last-to-first seam repaired too, which is what makes the result closed again.
|
||||
namespace {
|
||||
|
||||
constexpr double kOffJoinEps = 1e-6;
|
||||
|
||||
bool off_same(const Vec2d& a, const Vec2d& b) { return (a - b).squaredNorm() < kOffJoinEps * kOffJoinEps; }
|
||||
// Two ends are the same point when the WIRE BUILDER would weld them: a loop the viewport and
|
||||
// the kernel treat as closed must offset as one closed chain, not as separate open pieces with
|
||||
// unrepaired seams. The floor keeps exact coincidence meaningful with auto-close switched off.
|
||||
bool off_same(const Vec2d& a, const Vec2d& b)
|
||||
{
|
||||
const double tol = std::max(sketch_join_tol(), 1e-6);
|
||||
return (a - b).squaredNorm() <= tol * tol;
|
||||
}
|
||||
|
||||
// Does this entity type take part in chaining (i.e. does it have two ends)?
|
||||
bool off_is_open_curve(const SketchEntity& e)
|
||||
@@ -1226,7 +1261,10 @@ bool off_one(const SketchEntity& e, double d, SketchEntity& out)
|
||||
return true;
|
||||
}
|
||||
case SketchEntity::Type::Circle: {
|
||||
const double r = e.radius + d;
|
||||
// A circle runs CCW (it is what a 360° CCW arc chain closes into), so the rule below
|
||||
// applies to it too: +d is the left side, the inside, and the radius SHRINKS. It used to
|
||||
// grow, so a full circle and the same outline drawn as arcs offset opposite ways.
|
||||
const double r = e.radius - d;
|
||||
if (r <= 1e-9) return false;
|
||||
out = e; out.radius = r; out.p0 = out.center;
|
||||
return true;
|
||||
@@ -1465,6 +1503,9 @@ std::vector<SketchEntity> SketchEngine::transform_entities(
|
||||
out.reserve(src.size());
|
||||
const double ca = std::cos(angle), sa = std::sin(angle);
|
||||
const double rs = std::abs(scale); // radii are unsigned magnitudes
|
||||
// A negative scale is |scale| plus a half turn about the pivot: points get that from xf()
|
||||
// below, and angle-valued fields (arc ends, ellipse axis) need the same extra pi.
|
||||
const double turn = angle + (scale < 0.0 ? M_PI : 0.0);
|
||||
// Affine map: translate pivot to origin, scale, rotate, then translate by `move`.
|
||||
auto xf = [&](const Vec2d& p) -> Vec2d {
|
||||
const Vec2d d = scale * (p - pivot);
|
||||
@@ -1487,8 +1528,8 @@ std::vector<SketchEntity> SketchEngine::transform_entities(
|
||||
case SketchEntity::Type::Arc: {
|
||||
m.center = xf(e.center);
|
||||
m.radius = e.radius * rs;
|
||||
m.start_angle = e.start_angle + angle;
|
||||
m.end_angle = e.end_angle + angle; // rigid sweep, shifted by rotation
|
||||
m.start_angle = e.start_angle + turn;
|
||||
m.end_angle = e.end_angle + turn; // rigid sweep, shifted by rotation
|
||||
m.p0 = m.center + m.radius * Vec2d(std::cos(m.start_angle), std::sin(m.start_angle));
|
||||
m.p1 = m.center + m.radius * Vec2d(std::cos(m.end_angle), std::sin(m.end_angle));
|
||||
break;
|
||||
@@ -1498,7 +1539,7 @@ std::vector<SketchEntity> SketchEngine::transform_entities(
|
||||
m.center = xf(e.center);
|
||||
m.radius = e.radius * rs;
|
||||
m.rminor = e.rminor * rs;
|
||||
m.rotation = e.rotation + angle; // major axis rotates with the body
|
||||
m.rotation = e.rotation + turn; // major axis rotates with the body
|
||||
if (e.type == SketchEntity::Type::Ellipse) {
|
||||
m.p0 = m.center;
|
||||
} else {
|
||||
@@ -1845,6 +1886,15 @@ static double wrap_2pi(double x)
|
||||
return x;
|
||||
}
|
||||
|
||||
// An arc's endpoints are stored twice: as angles and as p0/p1. Everything downstream reads
|
||||
// p0/p1 (the wire builder welds on them, the solver seeds from them, snapping uses them), so
|
||||
// any edit of the angles must write them back or the arc's ends silently stay where they were.
|
||||
static void sync_arc_ends(SketchEntity& e)
|
||||
{
|
||||
e.p0 = e.center + e.radius * Vec2d(std::cos(e.start_angle), std::sin(e.start_angle));
|
||||
e.p1 = e.center + e.radius * Vec2d(std::cos(e.end_angle), std::sin(e.end_angle));
|
||||
}
|
||||
|
||||
bool SketchEngine::trim_entity(SketchEntity& e, const std::vector<SketchEntity>& others,
|
||||
const Vec2d& pick)
|
||||
{
|
||||
@@ -1879,6 +1929,7 @@ bool SketchEngine::trim_entity(SketchEntity& e, const std::vector<SketchEntity>&
|
||||
if (uc == -std::numeric_limits<double>::max()) return false;
|
||||
e.end_angle = e.start_angle + uc * sweep; // drop (uc, 1]
|
||||
}
|
||||
sync_arc_ends(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1910,6 +1961,7 @@ bool SketchEngine::trim_entity(SketchEntity& e, const std::vector<SketchEntity>&
|
||||
e.type = SketchEntity::Type::Arc;
|
||||
e.start_angle = hi;
|
||||
e.end_angle = lo + 2.0 * M_PI;
|
||||
sync_arc_ends(e); // p0 was the circle's centre (circle convention); now the arc's start
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1982,6 +2034,7 @@ bool SketchEngine::extend_entity(SketchEntity& e, const std::vector<SketchEntity
|
||||
if (best == std::numeric_limits<double>::max()) return false;
|
||||
if (extend_end) e.end_angle += sgn * best;
|
||||
else e.start_angle -= sgn * best;
|
||||
sync_arc_ends(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2022,7 +2075,9 @@ bool SketchEngine::extend_entity(SketchEntity& e, const std::vector<SketchEntity
|
||||
}
|
||||
|
||||
// Bridge: cubic Bézier with G1 continuity at both ends.
|
||||
// Poles = {Pa, Pa + Ta*d/3, Pb - Tb*d/3, Pb}, where d = |Pb - Pa|.
|
||||
// Poles = {Pa, Pa + Ta*d/3, Pb + Tb*d/3, Pb}, where d = |Pb - Pa| and Ta/Tb are the OUTWARD
|
||||
// tangents at the two ends (the direction you would leave each entity in). The curve leaves
|
||||
// Pa along Ta and arrives at Pb along -Tb, i.e. continuing INTO b.
|
||||
SketchEntity SketchEngine::make_bridge(const SketchEntity& a, int a_end,
|
||||
const SketchEntity& b, int b_end)
|
||||
{
|
||||
@@ -2068,7 +2123,9 @@ SketchEntity SketchEngine::make_bridge(const SketchEntity& a, int a_end,
|
||||
SketchEntity e;
|
||||
e.type = SketchEntity::Type::BSpline;
|
||||
e.construction = false;
|
||||
e.ctrl = { Pa, Pa + Ta * k, Pb - Tb * k, Pb };
|
||||
// Pb + Tb*k, not minus: with Tb outward, minus put the last inner pole on b's side, so the
|
||||
// curve overshot Pb (and against a line arrived with a cusp instead of continuing into it).
|
||||
e.ctrl = { Pa, Pa + Ta * k, Pb + Tb * k, Pb };
|
||||
e.p0 = e.ctrl.front();
|
||||
e.p1 = e.ctrl.back();
|
||||
return e;
|
||||
@@ -2096,6 +2153,153 @@ int sketch_entity_ends(const SketchEntity& e, std::pair<SketchPointRole, Vec2d>
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// An arc's signed angular offset of `ang` from its start, within its sweep: true when the angle
|
||||
// lies on the arc (with `tol` radians of slack at either end).
|
||||
bool angle_on_arc(const SketchEntity& e, double ang, double tol)
|
||||
{
|
||||
const double TWO_PI = 2.0 * M_PI;
|
||||
const double sweep = e.end_angle - e.start_angle;
|
||||
double d = (sweep >= 0.0) ? ang - e.start_angle : e.start_angle - ang;
|
||||
d = std::fmod(d, TWO_PI);
|
||||
if (d < 0.0) d += TWO_PI;
|
||||
if (d > TWO_PI - tol) d -= TWO_PI; // just before the start counts as the start
|
||||
return d >= -tol && d <= std::abs(sweep) + tol;
|
||||
}
|
||||
|
||||
// Every point where two lines/arcs meet (tangent contact included). Exact, no sampling.
|
||||
void entity_intersections(const SketchEntity& A, const SketchEntity& B, std::vector<Vec2d>& out)
|
||||
{
|
||||
using T = SketchEntity::Type;
|
||||
const double eps = 1e-9;
|
||||
auto on_seg = [](const SketchEntity& L, const Vec2d& p) {
|
||||
const Vec2d d = L.p1 - L.p0;
|
||||
const double l2 = d.squaredNorm();
|
||||
if (l2 < 1e-24) return false;
|
||||
const double t = (p - L.p0).dot(d) / l2;
|
||||
return t >= -1e-9 && t <= 1.0 + 1e-9;
|
||||
};
|
||||
if (A.type == T::Line && B.type == T::Line) {
|
||||
const Vec2d r = A.p1 - A.p0, q = B.p1 - B.p0, w = B.p0 - A.p0;
|
||||
const double den = r.x() * q.y() - r.y() * q.x();
|
||||
if (std::abs(den) < eps * r.norm() * q.norm()) {
|
||||
// Parallel. Collinear overlap is a crossing wherever it is: report the overlap's
|
||||
// nearest end, which the caller then tests against the shared joints.
|
||||
if (std::abs(r.x() * w.y() - r.y() * w.x()) > 1e-9 * std::max(1.0, r.norm())) return;
|
||||
for (const Vec2d& p : { B.p0, B.p1 }) if (on_seg(A, p)) out.push_back(p);
|
||||
for (const Vec2d& p : { A.p0, A.p1 }) if (on_seg(B, p)) out.push_back(p);
|
||||
return;
|
||||
}
|
||||
const double t = (w.x() * q.y() - w.y() * q.x()) / den;
|
||||
const double u = (w.x() * r.y() - w.y() * r.x()) / den;
|
||||
if (t >= -1e-9 && t <= 1.0 + 1e-9 && u >= -1e-9 && u <= 1.0 + 1e-9) out.push_back(A.p0 + t * r);
|
||||
return;
|
||||
}
|
||||
if (A.type == T::Arc && B.type == T::Line) { entity_intersections(B, A, out); return; }
|
||||
if (A.type == T::Line && B.type == T::Arc) {
|
||||
const Vec2d d = A.p1 - A.p0, f = A.p0 - B.center;
|
||||
const double a = d.squaredNorm(), b = 2.0 * f.dot(d), c = f.squaredNorm() - B.radius * B.radius;
|
||||
if (a < 1e-24) return;
|
||||
double disc = b * b - 4.0 * a * c;
|
||||
if (disc < -1e-9 * a * B.radius * B.radius) return;
|
||||
disc = std::sqrt(std::max(0.0, disc));
|
||||
for (double t : { (-b - disc) / (2.0 * a), (-b + disc) / (2.0 * a) }) {
|
||||
if (t < -1e-9 || t > 1.0 + 1e-9) continue;
|
||||
const Vec2d p = A.p0 + t * d;
|
||||
if (angle_on_arc(B, std::atan2(p.y() - B.center.y(), p.x() - B.center.x()), 1e-9))
|
||||
out.push_back(p);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (A.type == T::Arc && B.type == T::Arc) {
|
||||
const Vec2d dc = B.center - A.center;
|
||||
const double dd = dc.norm();
|
||||
if (dd < 1e-12) {
|
||||
// Concentric: they overlap only on the same circle, and then everywhere they share.
|
||||
if (std::abs(A.radius - B.radius) > 1e-9) return;
|
||||
for (const Vec2d& p : { B.p0, B.p1 })
|
||||
if (angle_on_arc(A, std::atan2(p.y() - A.center.y(), p.x() - A.center.x()), 1e-9)) out.push_back(p);
|
||||
for (const Vec2d& p : { A.p0, A.p1 })
|
||||
if (angle_on_arc(B, std::atan2(p.y() - B.center.y(), p.x() - B.center.x()), 1e-9)) out.push_back(p);
|
||||
return;
|
||||
}
|
||||
if (dd > A.radius + B.radius + 1e-9 || dd < std::abs(A.radius - B.radius) - 1e-9) return;
|
||||
const double x = (dd * dd + A.radius * A.radius - B.radius * B.radius) / (2.0 * dd);
|
||||
const double h = std::sqrt(std::max(0.0, A.radius * A.radius - x * x));
|
||||
const Vec2d u = dc / dd, m = A.center + x * u, n(-u.y(), u.x());
|
||||
for (const Vec2d& p : { Vec2d(m + h * n), Vec2d(m - h * n) }) {
|
||||
if (angle_on_arc(A, std::atan2(p.y() - A.center.y(), p.x() - A.center.x()), 1e-9) &&
|
||||
angle_on_arc(B, std::atan2(p.y() - B.center.y(), p.x() - B.center.x()), 1e-9))
|
||||
out.push_back(p);
|
||||
if (h < 1e-12) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool sketch_loop_defect(const std::vector<SketchEntity>& ents, const std::vector<int>& order, Vec2d& at)
|
||||
{
|
||||
using T = SketchEntity::Type;
|
||||
const size_t n = order.size();
|
||||
if (n < 2) return false;
|
||||
for (int ei : order)
|
||||
if (ei < 0 || ei >= int(ents.size()) || (ents[ei].type != T::Line && ents[ei].type != T::Arc))
|
||||
return false;
|
||||
const double tol = std::max(1e-6, sketch_join_tol());
|
||||
|
||||
// Traversal direction of each entity: the end it shares with the NEXT one is where it
|
||||
// finishes. start[k]/end[k] are the traversal's ends.
|
||||
std::vector<Vec2d> start(n), end(n);
|
||||
std::vector<bool> rev(n, false);
|
||||
for (size_t k = 0; k < n; ++k) {
|
||||
const SketchEntity& e = ents[order[k]];
|
||||
const SketchEntity& nx = ents[order[(k + 1) % n]];
|
||||
const double to_next_p1 = std::min((e.p1 - nx.p0).norm(), (e.p1 - nx.p1).norm());
|
||||
const double to_next_p0 = std::min((e.p0 - nx.p0).norm(), (e.p0 - nx.p1).norm());
|
||||
rev[k] = to_next_p0 < to_next_p1;
|
||||
start[k] = rev[k] ? e.p1 : e.p0;
|
||||
end[k] = rev[k] ? e.p0 : e.p1;
|
||||
}
|
||||
auto tangent = [&](size_t k, bool at_end) {
|
||||
const SketchEntity& e = ents[order[k]];
|
||||
Vec2d t;
|
||||
if (e.type == T::Line) {
|
||||
t = e.p1 - e.p0;
|
||||
} else {
|
||||
const double a = at_end ? (rev[k] ? e.start_angle : e.end_angle)
|
||||
: (rev[k] ? e.end_angle : e.start_angle);
|
||||
const double s = (e.end_angle >= e.start_angle) ? 1.0 : -1.0;
|
||||
t = s * Vec2d(-std::sin(a), std::cos(a));
|
||||
}
|
||||
if (rev[k]) t = -t;
|
||||
const double l = t.norm();
|
||||
return l > 1e-15 ? Vec2d(t / l) : Vec2d(0, 0);
|
||||
};
|
||||
|
||||
// Cusps: the curve arriving at a joint and the one leaving it point opposite ways.
|
||||
for (size_t k = 0; k < n; ++k) {
|
||||
const size_t j = (k + 1) % n;
|
||||
if (tangent(k, true).dot(tangent(j, false)) < -0.9999) { at = end[k]; return true; }
|
||||
}
|
||||
|
||||
// Crossings: any contact between two entities away from the joints they share.
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
for (size_t j = i + 1; j < n; ++j) {
|
||||
std::vector<Vec2d> hits;
|
||||
entity_intersections(ents[order[i]], ents[order[j]], hits);
|
||||
const bool next = (j == i + 1), prev = (i == 0 && j == n - 1);
|
||||
for (const Vec2d& p : hits) {
|
||||
bool joint = false;
|
||||
if (next && (p - end[i]).norm() < tol) joint = true;
|
||||
if (prev && (p - start[i]).norm() < tol) joint = true;
|
||||
if (!joint) { at = p; return true; }
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sketch_closest_ends(const SketchEntity& A, const SketchEntity& B,
|
||||
SketchPointRole& ra, SketchPointRole& rb, Vec2d& pa, Vec2d& pb)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "libslic3r/CAD/GeometryEngine.hpp"
|
||||
|
||||
#include <gp_Pln.hxx>
|
||||
#include <gp_Ax1.hxx>
|
||||
#include <gp_Ax3.hxx>
|
||||
#include <gp_Vec.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
@@ -53,9 +54,11 @@ struct SketchPlane {
|
||||
Vec3d x_axis{1,0,0};
|
||||
Vec3d y_axis{0,1,0};
|
||||
|
||||
gp_Pln to_occt() const;
|
||||
static SketchPlane from_face(const TopoDS_Face& face);
|
||||
static SketchPlane XY() { return {}; }
|
||||
// NOTE: XZ's stored normal (+Y) is the OPPOSITE of x_axis x y_axis (-Y). Kept as it is —
|
||||
// extrude directions and saved recipes depend on it — so anything that needs the frame's
|
||||
// own handedness takes x_axis.cross(y_axis) instead of `normal` (see make_elips).
|
||||
static SketchPlane XZ() { return {{0,0,0}, {0,1,0}, {1,0,0}, {0,0,1}}; }
|
||||
static SketchPlane YZ() { return {{0,0,0}, {1,0,0}, {0,1,0}, {0,0,1}}; }
|
||||
|
||||
@@ -70,8 +73,6 @@ struct SketchProfile {
|
||||
std::vector<Vec2d> points;
|
||||
bool closed{false};
|
||||
|
||||
bool is_closed(double tolerance = 0.5) const;
|
||||
bool try_close(double tolerance = 0.5);
|
||||
void clear() { points.clear(); closed = false; }
|
||||
TopoDS_Wire to_occt_wire(const SketchPlane& plane) const;
|
||||
|
||||
@@ -91,9 +92,9 @@ inline constexpr double kSketchJoinTol = 1e-3; // mm
|
||||
// Effective sketch joint tolerance. ONE value for the viewport (region_loops /
|
||||
// loop_report / connected_loop) and the kernel (entities_to_wires): if these ever
|
||||
// disagree again, the viewport shades a region closed that the kernel refuses to
|
||||
// build, which is how a sketch got extruded into the wrong solid. The GUI pushes
|
||||
// the "auto_close_sketch_loops" preference in via set_sketch_auto_close(); the
|
||||
// kernel defaults to ON so headless/kernel-only callers keep welding.
|
||||
// build, which is how a sketch got extruded into the wrong solid. The document's
|
||||
// own setting (CadDocument::auto_close_loops) is pushed in via set_sketch_auto_close();
|
||||
// the kernel defaults to ON so headless/kernel-only callers keep welding.
|
||||
double sketch_join_tol();
|
||||
void set_sketch_auto_close(bool on);
|
||||
|
||||
@@ -109,8 +110,8 @@ enum class SketchConstraintType {
|
||||
// inserting anywhere but the end reinterprets every constraint in every saved recipe.
|
||||
EqualRadius,
|
||||
Collinear,
|
||||
DistanceX, // |dx| between two points, projected onto the sketch X axis
|
||||
DistanceY, // |dy| between two points, projected onto the sketch Y axis
|
||||
DistanceX, // signed dx between two points (eb - ea), projected onto the sketch X axis
|
||||
DistanceY, // signed dy between two points (eb - ea), projected onto the sketch Y axis
|
||||
SymmetricAboutY, // mirror across the sketch's vertical axis (x = 0); axis is implicit
|
||||
SymmetricAboutX // mirror across the sketch's horizontal axis (y = 0); axis is implicit
|
||||
};
|
||||
@@ -165,6 +166,15 @@ int sketch_entity_ends(const SketchEntity& e, std::pair<SketchPointRole, Vec2d>
|
||||
bool sketch_closest_ends(const SketchEntity& A, const SketchEntity& B,
|
||||
SketchPointRole& ra, SketchPointRole& rb, Vec2d& pa, Vec2d& pb);
|
||||
|
||||
// Where a CLOSED loop fails to bound one region, although every joint meets: two of its
|
||||
// entities touch somewhere other than a joint they share (the loop crosses itself), or a joint
|
||||
// where the curve turns straight back along itself (a cusp: an arc leaving a line tangent to it
|
||||
// but heading the other way). Either makes a face OCCT accepts and then builds an invalid solid
|
||||
// from. `order` lists the loop's entity indices in traversal order, as the chainer found them.
|
||||
// Lines and arcs are judged exactly; a loop holding any other kind is not judged (false).
|
||||
// On true, `at` is the offending point in sketch coordinates.
|
||||
bool sketch_loop_defect(const std::vector<SketchEntity>& ents, const std::vector<int>& order, Vec2d& at);
|
||||
|
||||
// Why an entity-constraint pick is refused. The caller maps a reason to a localized string;
|
||||
// the planner itself stays translation-free.
|
||||
enum class ConstraintReject {
|
||||
@@ -256,12 +266,10 @@ public:
|
||||
const std::vector<std::vector<std::vector<Vec2d>>>& regions,
|
||||
const SketchPlane& plane, double length, bool symmetric = false);
|
||||
|
||||
// Revolve a planar profile wire about an axis lying in the sketch plane and
|
||||
// passing through the plane origin: axis_sel 0 = plane X axis, 1 = plane Y axis.
|
||||
// A negative angle_deg sweeps the opposite direction (Flip). The profile must
|
||||
// lie to one side of the axis (Onshape rule); a straddling profile self-intersects.
|
||||
static TopoDS_Shape make_revolve(const TopoDS_Wire& wire, const SketchPlane& plane,
|
||||
double angle_deg = 360.0, int axis_sel = 0);
|
||||
// Revolve the closed profile wire about `axis` (world) by angle_deg; a negative angle sweeps
|
||||
// the other way (Flip). The profile must lie to one side of the axis (Onshape rule): one that
|
||||
// straddles it sweeps through itself, and that is refused rather than returned broken.
|
||||
static TopoDS_Shape make_revolve(const TopoDS_Wire& wire, const gp_Ax1& axis, double angle_deg = 360.0);
|
||||
|
||||
// Sweep a planar profile wire along a path (spine) wire. The profile is turned
|
||||
// into a face and swept with BRepOffsetAPI_MakePipe, which keeps the profile
|
||||
@@ -317,7 +325,7 @@ public:
|
||||
// offset together and their seams repaired (miter join), so a closed profile comes back
|
||||
// closed and can still be extruded; per-entity offsetting cannot do that. Sign convention:
|
||||
// +d moves each curve to the LEFT of its direction of travel, which for a CCW closed loop
|
||||
// is inward. Ellipses and splines are not offset (a parallel of either is not the same
|
||||
// is inward; a full circle counts as CCW, so +d shrinks it. Ellipses and splines are not offset (a parallel of either is not the same
|
||||
// kind of curve) and are dropped from the result.
|
||||
static std::vector<SketchEntity> offset_entities(
|
||||
const std::vector<SketchEntity>& src, double d);
|
||||
|
||||
@@ -75,12 +75,18 @@ ImportRegions text_to_regions(const std::string& utf8, double size_mm,
|
||||
return {};
|
||||
|
||||
const std::string path = font_path.empty() ? default_font_path() : font_path;
|
||||
std::unique_ptr<Emboss::FontFile> ff = Emboss::create_font_file(path.c_str());
|
||||
if (!ff)
|
||||
return {};
|
||||
Emboss::FontFileWithCache fwc(std::move(ff));
|
||||
if (!fwc.has_value())
|
||||
return text_to_regions(utf8, size_mm,
|
||||
std::shared_ptr<const Emboss::FontFile>(Emboss::create_font_file(path.c_str())));
|
||||
}
|
||||
|
||||
ImportRegions text_to_regions(const std::string& utf8, double size_mm,
|
||||
const std::shared_ptr<const Emboss::FontFile>& ff)
|
||||
{
|
||||
if (utf8.empty() || size_mm <= 0.0 || !ff)
|
||||
return {};
|
||||
Emboss::FontFileWithCache fwc;
|
||||
fwc.font_file = ff;
|
||||
fwc.cache = std::make_shared<Emboss::Glyphs>();
|
||||
|
||||
FontProp prop(static_cast<float>(size_mm)); // per_glyph=false
|
||||
HealedExPolygons healed = Emboss::text2shapes(fwc, utf8.c_str(), prop);
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
|
||||
#include "libslic3r/Point.hpp" // Vec2d
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace Emboss { struct FontFile; }
|
||||
|
||||
// A rigid imported region: contour[0] = outer loop, contour[1..] = holes;
|
||||
// points in plane (u,v) millimetres. The nested vector type matches
|
||||
// CadFeature::imported_regions exactly, so results assign directly.
|
||||
@@ -19,6 +22,11 @@ using ImportRegions = std::vector<ImportRegion>;
|
||||
// font (resources/fonts). Returns an empty vector on any failure.
|
||||
ImportRegions text_to_regions(const std::string& utf8, double size_mm,
|
||||
const std::string& font_path = std::string());
|
||||
// Same, from an already loaded font — the GUI resolves a system font (face, bold, italic)
|
||||
// to one and keeps it while the user types. Returns an empty vector when the font is null or
|
||||
// yields no shape.
|
||||
ImportRegions text_to_regions(const std::string& utf8, double size_mm,
|
||||
const std::shared_ptr<const Emboss::FontFile>& font);
|
||||
|
||||
// Parse an SVG file's filled paths into regions (mm), centred on the origin.
|
||||
// `scale` multiplies the authored size (1.0 = as authored). Returns an empty
|
||||
|
||||
@@ -80,6 +80,18 @@ InferenceSnap infer_point_snap(const std::vector<SketchEntity>& entities,
|
||||
offer(InferenceSnap::Kind::Midpoint, ei, SketchPointRole::P0,
|
||||
Vec2d(e.center.x() + e.radius * std::cos(am),
|
||||
e.center.y() + e.radius * std::sin(am)));
|
||||
// Nearest point on the arc itself (PointOnObject candidate), as for a line or a
|
||||
// circle — the solver now takes a point on an arc's rim.
|
||||
const Vec2d v = query - e.center;
|
||||
const double n = v.norm();
|
||||
const double sweep = e.end_angle - e.start_angle;
|
||||
if (n > 1e-9 && e.radius > 1e-9 && std::abs(sweep) > 1e-9) {
|
||||
double u = (std::atan2(v.y(), v.x()) - e.start_angle) / sweep;
|
||||
while (u < 0.0) u += 2.0 * M_PI / std::abs(sweep);
|
||||
if (u > 0.02 && u < 0.98)
|
||||
offer(InferenceSnap::Kind::OnEdge, ei, SketchPointRole::Center,
|
||||
e.center + v * (e.radius / n));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SketchEntity::Type::Circle: {
|
||||
@@ -147,6 +159,10 @@ infer_relations(const std::vector<SketchEntity>& entities, int new_ei,
|
||||
{
|
||||
std::vector<SketchEntityConstraintDef> out;
|
||||
if (new_ei <= 0 || new_ei >= int(entities.size())) return out;
|
||||
// Ends count as meeting at the same tolerance the wire builder welds them at; floor keeps
|
||||
// exact coincidence meaningful when auto-close is switched off.
|
||||
const double weld = std::max(sketch_join_tol(), 1e-7);
|
||||
auto joined = [weld](const Vec2d& a, const Vec2d& b) { return (a - b).squaredNorm() <= weld * weld; };
|
||||
|
||||
// AT MOST ONE constraint per rule per new entity, not one per PAIR. Without this the
|
||||
// function is quadratic in the sketch: a drawing with 200 equal holes yields ~20000
|
||||
@@ -176,10 +192,9 @@ infer_relations(const std::vector<SketchEntity>& entities, int new_ei,
|
||||
if (n_line && o_line) {
|
||||
// R1 — parallel / perpendicular, restricted to CONNECTED lines. Connection is
|
||||
// what keeps this from firing on every distant line that is roughly parallel.
|
||||
const bool connected = (n.p0 - o.p0).squaredNorm() <= 1e-14 ||
|
||||
(n.p0 - o.p1).squaredNorm() <= 1e-14 ||
|
||||
(n.p1 - o.p0).squaredNorm() <= 1e-14 ||
|
||||
(n.p1 - o.p1).squaredNorm() <= 1e-14;
|
||||
// "Connected" is the wire builder's weld, so what the viewport shows joined is.
|
||||
const bool connected = joined(n.p0, o.p0) || joined(n.p0, o.p1) ||
|
||||
joined(n.p1, o.p0) || joined(n.p1, o.p1);
|
||||
if (!connected) continue;
|
||||
const double ang = unsigned_angle(n.p1 - n.p0, o.p1 - o.p0);
|
||||
const double par_err = std::min(ang, M_PI - ang);
|
||||
@@ -208,7 +223,7 @@ infer_relations(const std::vector<SketchEntity>& entities, int new_ei,
|
||||
if (cv.type == SketchEntity::Type::Arc) {
|
||||
const Vec2d ce[2] = { cv.p0, cv.p1 };
|
||||
for (int m = 0; m < 2; ++m) {
|
||||
if ((le[k] - ce[m]).squaredNorm() > 1e-14) continue;
|
||||
if (!joined(le[k], ce[m])) continue;
|
||||
const Vec2d r = ce[m] - cv.center;
|
||||
if (r.squaredNorm() < 1e-18) continue;
|
||||
tangent = std::abs(unsigned_angle(ldir, r) - M_PI / 2.0) <= ang_tol_rad;
|
||||
@@ -216,7 +231,7 @@ infer_relations(const std::vector<SketchEntity>& entities, int new_ei,
|
||||
}
|
||||
} else { // Circle: shared point is a line endpoint on the rim.
|
||||
const Vec2d r = le[k] - cv.center;
|
||||
if (std::abs(r.norm() - cv.radius) > 1e-7) continue;
|
||||
if (std::abs(r.norm() - cv.radius) > weld) continue;
|
||||
if (r.squaredNorm() < 1e-18) continue;
|
||||
tangent = std::abs(unsigned_angle(ldir, r) - M_PI / 2.0) <= ang_tol_rad;
|
||||
}
|
||||
|
||||
@@ -38,8 +38,12 @@ InferenceSnap infer_point_snap(const std::vector<SketchEntity>& entities,
|
||||
// within `ang_tol_rad` of an axis, returns Horizontal or Vertical (the constraint to
|
||||
// auto-emit on the committed segment); std::nullopt otherwise. Degenerate (near-zero
|
||||
// length) segments return nullopt.
|
||||
// One angular tolerance for every "is this relation already true?" inference, so a pair of
|
||||
// lines that reads as horizontal to one rule cannot read as not-quite-parallel to the other.
|
||||
inline constexpr double kSketchInferAngleTol = 3.0 * M_PI / 180.0;
|
||||
|
||||
std::optional<SketchConstraintType>
|
||||
infer_axis_constraint(const Vec2d& anchor, const Vec2d& tip, double ang_tol_rad = 3.0 * M_PI / 180.0);
|
||||
infer_axis_constraint(const Vec2d& anchor, const Vec2d& tip, double ang_tol_rad = kSketchInferAngleTol);
|
||||
|
||||
// Relational constraints to auto-emit for a newly drawn entity `new_ei` against the
|
||||
// entities already in the sketch. Pure, no GUI/GL dependencies, unit-testable.
|
||||
@@ -49,7 +53,7 @@ infer_axis_constraint(const Vec2d& anchor, const Vec2d& tip, double ang_tol_rad
|
||||
// relation that is visibly there. Returns an empty vector when nothing qualifies.
|
||||
std::vector<SketchEntityConstraintDef>
|
||||
infer_relations(const std::vector<SketchEntity>& entities, int new_ei,
|
||||
double ang_tol_rad = 2.0 * M_PI / 180.0,
|
||||
double ang_tol_rad = kSketchInferAngleTol,
|
||||
double len_tol_frac = 0.01);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <math.h>
|
||||
#include <slvs.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
@@ -11,6 +12,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -45,12 +47,15 @@ struct Build {
|
||||
{ return E(Slvs_MakePoint2d(++eh, g, wp, P(g, u), P(g, v))); }
|
||||
|
||||
// Generic constraint (entityC unused by Slvs_MakeConstraint — set it manually below).
|
||||
// `other` / `other2` pick the END point (point[2]) of entity A / B instead of its start
|
||||
// (point[1]) for the constraints that read an arc's endpoint (the tangencies).
|
||||
void C(int type, double val, Slvs_hEntity ptA, Slvs_hEntity ptB,
|
||||
Slvs_hEntity eA, Slvs_hEntity eB, Slvs_hEntity eC = 0, int other = 0)
|
||||
Slvs_hEntity eA, Slvs_hEntity eB, Slvs_hEntity eC = 0, int other = 0, int other2 = 0)
|
||||
{
|
||||
Slvs_Constraint c = Slvs_MakeConstraint(++ch, G_SK, type, wp, val, ptA, ptB, eA, eB);
|
||||
c.entityC = eC;
|
||||
c.other = other;
|
||||
c.other2 = other2;
|
||||
cons.push_back(c);
|
||||
}
|
||||
};
|
||||
@@ -112,9 +117,15 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
||||
case SketchEntity::Type::Circle: {
|
||||
s.center = b.pt2d(G_SK, e.center.x(), e.center.y());
|
||||
s.p0 = s.center; // p0 mirrors centre for circles
|
||||
s.rparam = b.P(G_SK, e.radius > 1e-9 ? e.radius : 1.0);
|
||||
Slvs_hEntity dist = b.E(Slvs_MakeDistance(++b.eh, G_SK, b.wp, s.rparam));
|
||||
s.prim = b.E(Slvs_MakeCircle(++b.eh, G_SK, b.wp, s.center, b.normal, dist));
|
||||
// A zero-radius circle is degenerate everywhere else (the wire builder, offset and
|
||||
// inference all reject it). It used to be seeded with radius 1 here and written
|
||||
// back, so any unrelated solve silently turned it into a 1 mm circle. It gets no
|
||||
// primitive: its centre still solves, anything needing the rim is skipped.
|
||||
if (e.radius > 1e-9) {
|
||||
s.rparam = b.P(G_SK, e.radius);
|
||||
Slvs_hEntity dist = b.E(Slvs_MakeDistance(++b.eh, G_SK, b.wp, s.rparam));
|
||||
s.prim = b.E(Slvs_MakeCircle(++b.eh, G_SK, b.wp, s.center, b.normal, dist));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SketchEntity::Type::Arc:
|
||||
@@ -174,6 +185,20 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
||||
switch (r) { case Role::P0: return e.p0; case Role::P1: return e.p1; case Role::Center: return e.center; }
|
||||
return e.p0;
|
||||
};
|
||||
// Which side of line `li` the point (ei, r) is on, in slvs' sign convention for the
|
||||
// in-workplane PT_LINE_DISTANCE: with d = point[0] - point[1] and a = point[0], the signed
|
||||
// distance has the sign of d x (p - a) (pinned by the tests "a point below the line stays
|
||||
// below it" and "tangent line to circle, line below it"). +1 when on the line.
|
||||
auto side_of = [&](int ei, Role r, int li) -> double {
|
||||
Vec2d a, bb;
|
||||
if (li == kSketchRefAxisX) { a = Vec2d(1, 0); bb = Vec2d(0, 0); }
|
||||
else if (li == kSketchRefAxisY) { a = Vec2d(0, 1); bb = Vec2d(0, 0); }
|
||||
else if (valid(li)) { a = entities[li].p0; bb = entities[li].p1; }
|
||||
else return 1.0;
|
||||
const Vec2d d = a - bb, ap = coordOf(ei, r) - a;
|
||||
const double cr = d.x() * ap.y() - d.y() * ap.x();
|
||||
return cr < 0.0 ? -1.0 : 1.0;
|
||||
};
|
||||
// A fixed reference point at (x,y) — used to pin coordinates (Fix / LockX / LockY).
|
||||
auto fixedRef = [&](double x, double y) -> Slvs_hEntity { return b.pt2d(G_FIXED, x, y); };
|
||||
|
||||
@@ -211,7 +236,7 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
||||
case CT::Collinear:
|
||||
ref_ok = primOf(c.ea) && primOf(c.eb); break;
|
||||
}
|
||||
if (!ref_ok) continue;
|
||||
if (!ref_ok) { out.skipped.push_back(int(&c - constraints.data())); continue; }
|
||||
switch (c.type) {
|
||||
case CT::Coincident:
|
||||
b.C(SLVS_C_POINTS_COINCIDENT, 0, ptOf(c.ea, c.ra), ptOf(c.eb, c.rb), 0, 0);
|
||||
@@ -286,9 +311,36 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
||||
case CT::Tangent: {
|
||||
const bool aCurve = valid(c.ea) && entities[c.ea].type != SketchEntity::Type::Line;
|
||||
const bool bCurve = valid(c.eb) && entities[c.eb].type != SketchEntity::Type::Line;
|
||||
if (aCurve && bCurve)
|
||||
b.C(SLVS_C_CURVE_CURVE_TANGENT, 0, 0, 0, primOf(c.ea), primOf(c.eb));
|
||||
else {
|
||||
const bool aCircle = aCurve && entities[c.ea].type == SketchEntity::Type::Circle;
|
||||
const bool bCircle = bCurve && entities[c.eb].type == SketchEntity::Type::Circle;
|
||||
if (aCurve && bCurve && (aCircle || bCircle)) {
|
||||
// CURVE_CURVE_TANGENT reads each curve's ENDPOINT, which a full circle does not
|
||||
// have: slvs asserts and takes the process down (the same trap the circle-line
|
||||
// case below documents). Two round curves are tangent exactly when their
|
||||
// centres are r1 + r2 apart (outside each other) or |r1 - r2| apart (one inside
|
||||
// the other); keep whichever the sketch is closer to now. Radii are captured as
|
||||
// constants, with the same caveat as the circle-line case.
|
||||
const SketchEntity& A = entities[c.ea];
|
||||
const SketchEntity& B = entities[c.eb];
|
||||
const double d = (A.center - B.center).norm();
|
||||
const double ext = A.radius + B.radius;
|
||||
const double in = std::abs(A.radius - B.radius);
|
||||
b.C(SLVS_C_PT_PT_DISTANCE, std::abs(d - ext) <= std::abs(d - in) ? ext : in,
|
||||
ptOf(c.ea, Role::Center), ptOf(c.eb, Role::Center), 0, 0);
|
||||
} else if (aCurve && bCurve) {
|
||||
// Tangent where the two arcs MEET: pick, for each arc, the endpoint closest to
|
||||
// the other arc's nearest endpoint. Always binding the start point (the old
|
||||
// behaviour) made a tangency at an arc's end act on its start instead.
|
||||
const SketchEntity& A = entities[c.ea];
|
||||
const SketchEntity& B = entities[c.eb];
|
||||
int oa = 0, ob = 0; double best = 1e300;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
const double dd = ((i ? A.p1 : A.p0) - (j ? B.p1 : B.p0)).squaredNorm();
|
||||
if (dd < best) { best = dd; oa = i; ob = j; }
|
||||
}
|
||||
b.C(SLVS_C_CURVE_CURVE_TANGENT, 0, 0, 0, primOf(c.ea), primOf(c.eb), 0, oa, ob);
|
||||
} else {
|
||||
const int ci = aCurve ? c.ea : c.eb; // the curve
|
||||
const int li = aCurve ? c.eb : c.ea; // the line
|
||||
if (valid(ci) && entities[ci].type == SketchEntity::Type::Circle) {
|
||||
@@ -309,24 +361,47 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
||||
// not being changed by another constraint in the same solve; if some other
|
||||
// constraint drives the radius, re-solving restores tangency. Tying them
|
||||
// would need an auxiliary point constrained onto both circle and line.
|
||||
b.C(SLVS_C_PT_LINE_DISTANCE, entities[ci].radius,
|
||||
b.C(SLVS_C_PT_LINE_DISTANCE, side_of(ci, Role::Center, li) * entities[ci].radius,
|
||||
ptOf(ci, Role::Center), 0, primOf(li), 0);
|
||||
} else {
|
||||
b.C(SLVS_C_ARC_LINE_TANGENT, 0, 0, 0, primOf(ci), primOf(li));
|
||||
// The arc endpoint that touches the line is the one the tangency is at. The
|
||||
// constraint used to bind the START point always, so a fillet — its start on
|
||||
// one leg, its end on the other — forced both legs parallel.
|
||||
int other = 0;
|
||||
if (valid(ci) && valid(li)) {
|
||||
const SketchEntity& A = entities[ci];
|
||||
const SketchEntity& L = entities[li];
|
||||
auto seg_dist = [&](const Vec2d& p) {
|
||||
const Vec2d d = L.p1 - L.p0;
|
||||
const double t = d.squaredNorm() > 1e-18
|
||||
? std::clamp((p - L.p0).dot(d) / d.squaredNorm(), 0.0, 1.0) : 0.0;
|
||||
return (L.p0 + t * d - p).norm();
|
||||
};
|
||||
other = seg_dist(A.p1) < seg_dist(A.p0) ? 1 : 0;
|
||||
}
|
||||
b.C(SLVS_C_ARC_LINE_TANGENT, 0, 0, 0, primOf(ci), primOf(li), 0, other);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CT::PointOnLine:
|
||||
// slvs' in-workplane point-line distance is SIGNED. A negative stored value is an
|
||||
// explicit side; a positive one (what the UI stores) keeps the side the point is on
|
||||
// now. Passing |value| forced every point to the positive side, flipping any that
|
||||
// sat on the other one across the line.
|
||||
if (std::abs(c.value) < 1e-9)
|
||||
b.C(SLVS_C_PT_ON_LINE, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
|
||||
else
|
||||
b.C(SLVS_C_PT_LINE_DISTANCE, std::abs(c.value), ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
|
||||
b.C(SLVS_C_PT_LINE_DISTANCE,
|
||||
c.value < 0.0 ? c.value : side_of(c.ea, c.ra, c.eb) * c.value,
|
||||
ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
|
||||
break;
|
||||
case CT::PointOnObject:
|
||||
// Point (ea,ra) lies on entity edge eb: a circle rim -> PT_ON_CIRCLE,
|
||||
// otherwise the segment line -> PT_ON_LINE.
|
||||
if (valid(c.eb) && entities[c.eb].type == SketchEntity::Type::Circle)
|
||||
// Point (ea,ra) lies on entity edge eb: a circle or arc rim -> PT_ON_CIRCLE (slvs
|
||||
// takes both), otherwise the segment line -> PT_ON_LINE. An arc used to fall to
|
||||
// PT_ON_LINE, which is not an equation about an arc at all.
|
||||
if (valid(c.eb) && (entities[c.eb].type == SketchEntity::Type::Circle ||
|
||||
entities[c.eb].type == SketchEntity::Type::Arc))
|
||||
b.C(SLVS_C_PT_ON_CIRCLE, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
|
||||
else
|
||||
b.C(SLVS_C_PT_ON_LINE, 0, ptOf(c.ea, c.ra), 0, primOf(c.eb), 0);
|
||||
@@ -437,6 +512,29 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
||||
e.start_angle = ns;
|
||||
e.end_angle = ns + sweep;
|
||||
e.radius = 0.5 * ((e.p0 - e.center).norm() + (e.p1 - e.center).norm());
|
||||
} else if (e.type == SketchEntity::Type::EllipseArc && s.center && e.radius > 1e-9 && e.rminor > 1e-9) {
|
||||
// The solver moves the centre and the two ends as free points (it has no conic), so
|
||||
// after a solve they need not agree with the stored angles, and the wire builder then
|
||||
// finds the arc's ends away from its vertices and drops the whole sketch. Re-derive
|
||||
// the parametric angles from the solved ends and put the ends back ON the ellipse.
|
||||
const double cr = std::cos(e.rotation), sr = std::sin(e.rotation);
|
||||
auto param = [&](const Vec2d& p) {
|
||||
const Vec2d d = p - e.center;
|
||||
const double x = d.x() * cr + d.y() * sr, y = -d.x() * sr + d.y() * cr;
|
||||
return std::atan2(y / e.rminor, x / e.radius);
|
||||
};
|
||||
auto at = [&](double t) {
|
||||
const double x = e.radius * std::cos(t), y = e.rminor * std::sin(t);
|
||||
return Vec2d(e.center.x() + x * cr - y * sr, e.center.y() + x * sr + y * cr);
|
||||
};
|
||||
const double old_sweep = e.end_angle - e.start_angle;
|
||||
double t0 = param(e.p0), t1 = param(e.p1);
|
||||
if (old_sweep >= 0.0) { while (t1 <= t0) t1 += 2.0 * M_PI; }
|
||||
else { while (t1 >= t0) t1 -= 2.0 * M_PI; }
|
||||
e.start_angle = t0;
|
||||
e.end_angle = t1;
|
||||
e.p0 = at(t0);
|
||||
e.p1 = at(t1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,6 +562,21 @@ static SketchSolveResult solve_system(std::vector<SketchEntity>& entities,
|
||||
// that fits today keeps its exact current behaviour, including its reported degrees of freedom.
|
||||
// A genuinely over-constrained sketch still fails: the conflict lives inside one component and
|
||||
// that component still rejects it.
|
||||
// Degrees of freedom an entity has on its own, as the whole-system solve counts them.
|
||||
static int natural_dof(const SketchEntity& e)
|
||||
{
|
||||
switch (e.type) {
|
||||
case SketchEntity::Type::Line: return 4;
|
||||
case SketchEntity::Type::Point: return 2;
|
||||
case SketchEntity::Type::Circle: return e.radius > 1e-9 ? 3 : 2;
|
||||
case SketchEntity::Type::Arc: return 5; // centre + two ends, ends equidistant
|
||||
case SketchEntity::Type::Ellipse: return 2; // only the centre is a solver point
|
||||
case SketchEntity::Type::EllipseArc: return 6; // centre + two ends
|
||||
case SketchEntity::Type::BSpline: return 2 * int(e.ctrl.size());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SketchSolveResult solve_partitioned(std::vector<SketchEntity>& entities,
|
||||
const std::vector<SketchEntityConstraintDef>& constraints,
|
||||
int dragged_ei, Role dragged_role)
|
||||
@@ -482,12 +595,18 @@ static SketchSolveResult solve_partitioned(std::vector<SketchEntity>& entities,
|
||||
};
|
||||
for (const auto& c : constraints) { unite(c.ea, c.eb); unite(c.ea, c.ec); }
|
||||
|
||||
// Group the constraints by the component they belong to.
|
||||
// Group the constraints by the component they belong to: that of the first ENTITY they
|
||||
// reference. ea can be a sketch reference (origin / axis, negative) while eb is the entity,
|
||||
// and such a constraint was dropped outright.
|
||||
std::map<int, std::vector<int>> groups;
|
||||
std::vector<bool> constrained(n, false);
|
||||
for (size_t i = 0; i < constraints.size(); ++i) {
|
||||
const int a = constraints[i].ea;
|
||||
if (a < 0 || a >= n) continue;
|
||||
const SketchEntityConstraintDef& c = constraints[i];
|
||||
const int a = (c.ea >= 0 && c.ea < n) ? c.ea : (c.eb >= 0 && c.eb < n) ? c.eb
|
||||
: (c.ec >= 0 && c.ec < n) ? c.ec : -1;
|
||||
if (a < 0) continue;
|
||||
groups[find(a)].push_back(int(i));
|
||||
for (int e : { c.ea, c.eb, c.ec }) if (e >= 0 && e < n) constrained[e] = true;
|
||||
}
|
||||
|
||||
SketchSolveResult out;
|
||||
@@ -513,7 +632,8 @@ static SketchSolveResult solve_partitioned(std::vector<SketchEntity>& entities,
|
||||
subc.reserve(cidx.size());
|
||||
for (int ci : cidx) {
|
||||
SketchEntityConstraintDef d = constraints[ci];
|
||||
auto map1 = [&](int& e) { e = (e >= 0 && local.count(e)) ? local[e] : -1; };
|
||||
// Sketch references (origin, axes: negative sentinels) are global and pass through.
|
||||
auto map1 = [&](int& e) { if (e >= 0) e = local.count(e) ? local[e] : -1; };
|
||||
map1(d.ea); map1(d.eb); map1(d.ec);
|
||||
subc.push_back(d);
|
||||
}
|
||||
@@ -526,8 +646,14 @@ static SketchSolveResult solve_partitioned(std::vector<SketchEntity>& entities,
|
||||
if (bi >= 0 && bi < int(cidx.size())) out.bad.push_back(cidx[bi]);
|
||||
}
|
||||
if (r.dof > 0) out.dof += r.dof;
|
||||
for (int si : r.skipped)
|
||||
if (si >= 0 && si < int(cidx.size())) out.skipped.push_back(cidx[si]);
|
||||
solved.emplace_back(std::move(ents), std::move(sub));
|
||||
}
|
||||
// Entities no constraint touches are in no group but still have their freedoms; the
|
||||
// whole-system solve counts them, so this path must too or the two report different dof.
|
||||
for (int i = 0; i < n; ++i)
|
||||
if (!constrained[i]) out.dof += natural_dof(entities[i]);
|
||||
if (!out.ok) return out;
|
||||
for (auto& [ents, sub] : solved)
|
||||
for (size_t k = 0; k < ents.size(); ++k) entities[ents[k]] = sub[k];
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
// hand-rolled SketchConstraints: full constraint set, real DoF counting, and
|
||||
// over-constrained (bad-constraint) detection. Solves on a fixed 2D XY workplane.
|
||||
|
||||
#include "libslic3r/CAD/SketchEngine.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r { enum class SketchPointRole; }
|
||||
namespace Slic3r { struct SketchEntity; }
|
||||
namespace Slic3r { struct SketchEntityConstraintDef; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct SketchSolveResult {
|
||||
@@ -16,6 +19,10 @@ struct SketchSolveResult {
|
||||
int dof{-1}; // remaining degrees of freedom (>0 under-constrained)
|
||||
int result{0}; // raw SLVS_RESULT_* code
|
||||
std::vector<int> bad; // indices (into `constraints`) of conflicting constraints
|
||||
// Indices of constraints that were NOT applied because an entity they reference has no
|
||||
// solver representation for the role they need (an ellipse rim, a spline curve, a
|
||||
// zero-radius circle...). A solve can be ok with some skipped; callers should say so.
|
||||
std::vector<int> skipped;
|
||||
};
|
||||
|
||||
// Solve `constraints` over `entities` in place (writes solved coordinates back into the
|
||||
|
||||
@@ -24,6 +24,8 @@ struct ThreadSpec {
|
||||
double thread_depth_mm() const { return 0.6134 * pitch_mm; }
|
||||
// Internal/tapped minor (tap-drill) diameter for the same nominal thread.
|
||||
double minor_diameter_mm() const { return major_diameter_mm - 1.0825 * pitch_mm; }
|
||||
// Radial depth of the internal (tapped) thread, minor to major: (D - D1) / 2.
|
||||
double internal_depth_mm() const { return 0.5 * (major_diameter_mm - minor_diameter_mm()); }
|
||||
|
||||
bool imperial() const { return series == Series::UNC || series == Series::UNF; }
|
||||
};
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <math.h>
|
||||
#include "Geometry.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "libslic3r.h"
|
||||
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
#define slic3r_Circle_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace Slic3r { class Line; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
constexpr double ZERO_TOLERANCE = 0.000005;
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
@@ -24,6 +23,7 @@
|
||||
#include <clipper2/clipper.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "ExtrusionEntity.hpp"
|
||||
|
||||
// #define CLIPPER_UTILS_DEBUG
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <clipper2/clipper2_z.hpp>
|
||||
#include <cstddef>
|
||||
#include "Point.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace ClipperZUtils {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include "Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#include <math.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include "Config.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
#include "CustomGCode.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include "GCodeWriter.hpp"
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
#include <vector>
|
||||
#include <admesh/stl.h> // indexed_triangle_set
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Emboss.hpp" // IProjection
|
||||
|
||||
namespace Slic3r::Emboss { class IProject3d; }
|
||||
namespace Slic3r::Emboss { class IProjection; }
|
||||
|
||||
namespace Slic3r{
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <optional>
|
||||
#include <math.h>
|
||||
#include <cmath>
|
||||
#include "BoundingBox.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "ElephantFootCompensation.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -19,6 +18,7 @@
|
||||
#include <utility>
|
||||
#include <math.h>
|
||||
#include <limits>
|
||||
#include "ExtrusionEntity.hpp"
|
||||
|
||||
// #define CONTOUR_DISTANCE_DEBUG_SVG
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifndef slic3r_ElephantFootCompensation_hpp_
|
||||
#define slic3r_ElephantFootCompensation_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include <vector>
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "libslic3r/AABBTreeLines.hpp" // search structure for found close points
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include <sstream>
|
||||
|
||||
// Experimentaly suggested ration of font ascent by multiple fonts
|
||||
// to get approx center of normal text line
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Geometry/MedialAxis.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "MultiPoint.hpp"
|
||||
@@ -8,7 +7,6 @@
|
||||
#include "Line.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include "Polygon.hpp"
|
||||
using namespace Slic3r;
|
||||
|
||||
// IMPROVE: use one dimensional vector for polygons offset with searching by std::lower_bound
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include "Config.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define slic3r_Extruder_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Point.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Extruder.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
@@ -21,6 +20,7 @@
|
||||
#include <string_view>
|
||||
#include "Utils.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "ArcFitter.hpp"
|
||||
|
||||
#define L(s) (s)
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "ExtrusionEntity.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "ExtrusionSimulator.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.1415926535897932384626433832795
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define slic3r_ExtrusionSimulator_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "BoundingBox.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "SLA/IndexedMesh.hpp"
|
||||
#include "Model.hpp"
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "FuzzySkin.hpp"
|
||||
|
||||
#include "libnoise/noise.h"
|
||||
#include <functional>
|
||||
|
||||
// #define DEBUG_FUZZY
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
|
||||
#include "libslic3r/Arachne/utils/ExtrusionLine.hpp"
|
||||
#include "libslic3r/PerimeterGenerator.hpp"
|
||||
#include <cstddef>
|
||||
|
||||
namespace Slic3r { class PerimeterGenerator; }
|
||||
namespace Slic3r { struct FuzzySkinConfig; }
|
||||
namespace Slic3r::Arachne { struct ExtrusionLine; }
|
||||
|
||||
namespace Slic3r::Feature::FuzzySkin {
|
||||
|
||||
void fuzzy_polyline(Points& poly, bool closed, coordf_t slice_z, const FuzzySkinConfig& cfg);
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/SurfaceCollection.hpp"
|
||||
|
||||
namespace std {
|
||||
template<> struct hash<Slic3r::GridPoint3>
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "VoxelUtils.hpp"
|
||||
#include <functional>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include "libslic3r/MultiMaterialSegmentation.hpp"
|
||||
|
||||
namespace Slic3r { class PrintObject; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Fill/FillRectilinear.hpp"
|
||||
#include "libslic3r/Surface.hpp"
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
@@ -41,6 +41,13 @@
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "libslic3r/ExtrusionEntityCollection.hpp"
|
||||
#include "libslic3r/MultiMaterialSegmentation.hpp"
|
||||
#include "libslic3r/Slicing.hpp"
|
||||
#include "libslic3r/SurfaceCollection.hpp"
|
||||
|
||||
namespace Slic3r::FillAdaptive { struct Octree; }
|
||||
namespace Slic3r::FillLightning { class Generator; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -294,6 +301,9 @@ struct SurfaceFillParams
|
||||
float skin_infill_depth = 0;
|
||||
bool symmetric_infill_y_axis = false;
|
||||
|
||||
// Top fill for 3D honeycomb
|
||||
bool infill_complete_top = false;
|
||||
|
||||
// Params for Lateral honeycomb
|
||||
float infill_overhang_angle = 60.f;
|
||||
|
||||
@@ -337,6 +347,7 @@ struct SurfaceFillParams
|
||||
RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_1);
|
||||
RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_2);
|
||||
RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis);
|
||||
RETURN_COMPARE_NON_EQUAL(infill_complete_top);
|
||||
RETURN_COMPARE_NON_EQUAL(infill_lock_depth);
|
||||
RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
|
||||
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
|
||||
@@ -924,6 +935,8 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.symmetric_infill_y_axis = region_config.symmetric_infill_y_axis;
|
||||
} else if (params.pattern == ipZigZag) {
|
||||
params.symmetric_infill_y_axis = region_config.symmetric_infill_y_axis;
|
||||
} else if (params.pattern == ip3DHoneycomb) {
|
||||
params.infill_complete_top = region_config.infill_complete_top;
|
||||
}
|
||||
|
||||
if (surface.is_solid()) {
|
||||
@@ -1423,6 +1436,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
} else if (surface_fill.params.pattern == ipZigZag) {
|
||||
params.symmetric_infill_y_axis = surface_fill.params.symmetric_infill_y_axis;
|
||||
|
||||
} else if (surface_fill.params.pattern == ip3DHoneycomb) {
|
||||
params.infill_complete_top = surface_fill.params.infill_complete_top;
|
||||
}
|
||||
if (surface_fill.params.pattern == ipGrid)
|
||||
params.can_reverse = false;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include <utility>
|
||||
#include "Fill3DHoneycomb.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -60,7 +61,7 @@ static coordf_t troctWave(coordf_t pos, coordf_t gridSize, coordf_t Zpos)
|
||||
|
||||
// Identify the important points of curve change within a truncated
|
||||
// octahedron wave (as waveform fraction t):
|
||||
// 1. Start of wave (always 0.0)
|
||||
// 1. Start of wave (always 0.0; not needed if the pattern base starts here)
|
||||
// 2. Transition to upper "horizontal" part
|
||||
// 3. Transition from upper "horizontal" part
|
||||
// 4. Transition to lower "horizontal" part
|
||||
@@ -72,17 +73,11 @@ static coordf_t troctWave(coordf_t pos, coordf_t gridSize, coordf_t Zpos)
|
||||
* \ /
|
||||
* o---o
|
||||
*/
|
||||
static std::vector<coordf_t> getCriticalPoints(coordf_t Zpos, coordf_t gridSize)
|
||||
static std::vector<coordf_t> getCriticalPoints(coordf_t Zpos, coordf_t gridSize)
|
||||
{
|
||||
std::vector<coordf_t> res = {0.};
|
||||
std::vector<coordf_t> res;
|
||||
coordf_t perpOffset = abs(triWave(Zpos, gridSize) / 2.);
|
||||
|
||||
coordf_t normalisedOffset = perpOffset / gridSize;
|
||||
// // for debugging: just generate evenly-distributed points
|
||||
// for(coordf_t i = 0; i < 2; i += 0.05){
|
||||
// res.push_back(gridSize * i);
|
||||
// }
|
||||
// note: 0 == straight line
|
||||
if(normalisedOffset > 0){
|
||||
res.push_back(gridSize * (0. + normalisedOffset));
|
||||
res.push_back(gridSize * (1. - normalisedOffset));
|
||||
@@ -92,113 +87,251 @@ static std::vector<coordf_t> getCriticalPoints(coordf_t Zpos, coordf_t gridSize)
|
||||
return(res);
|
||||
}
|
||||
|
||||
// Generate an array of points that are in the same direction as the
|
||||
// basic printing line (i.e. Y points for columns, X points for rows)
|
||||
// Note: a negative offset only causes a change in the perpendicular
|
||||
// direction
|
||||
static std::vector<coordf_t> colinearPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints,
|
||||
const size_t baseLocation, size_t gridLength)
|
||||
// Add additional dense fill in line with the pattern direction to
|
||||
// cover the top squares of the pattern
|
||||
static Polylines addTops(coordf_t Zpos, coordf_t gridSize, coordf_t lengthX, coordf_t lengthY, coordf_t spacing,
|
||||
size_t multiline_count, size_t topDistance)
|
||||
{
|
||||
std::vector<coordf_t> points;
|
||||
points.push_back(baseLocation);
|
||||
for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= (gridSize*2)) {
|
||||
for(size_t pi = 0; pi < critPoints.size(); pi++){
|
||||
points.push_back(baseLocation + cLoc + critPoints[pi]);
|
||||
coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
|
||||
coordf_t zHalfCycle = fmod(zCycle, 0.5) * 2.;
|
||||
bool printVert = zCycle < 0.5;
|
||||
coordf_t offsetX = multiline_count;
|
||||
coordf_t offsetY = multiline_count;
|
||||
coordf_t perpOffset = abs(triWave(Zpos, gridSize) / 2.);
|
||||
coordf_t gridPoint = gridSize * (0. + perpOffset / gridSize);
|
||||
coordf_t topOffset = gridSize / 2.0 - abs(troctWave(gridPoint, gridSize, Zpos));
|
||||
coordf_t multilineAdjust = (sqrt(2) - 1.0) / 2.;
|
||||
Polylines lines;
|
||||
size_t pointCount = 0;
|
||||
coordf_t gridStartL = gridSize * 0.5 - topOffset;
|
||||
coordf_t gridEndL = gridSize * 0.5 + topOffset;
|
||||
if((topDistance == 0) && (multiline_count == 1)){
|
||||
// extend out a little bit on the first layer to help fuse the cover
|
||||
gridStartL -= spacing;
|
||||
gridEndL += spacing;
|
||||
} else if(multiline_count > 1) {
|
||||
// match start point to the corner edge
|
||||
gridStartL -= spacing * multiline_count * multilineAdjust;
|
||||
gridEndL += spacing * multiline_count * multilineAdjust;
|
||||
}
|
||||
// top cover extents perpendicular to the direction of travel
|
||||
coordf_t gridStartP = gridSize * 0.5 - topOffset + spacing * multiline_count / 2. + spacing / 2.;
|
||||
coordf_t gridEndP = gridSize * 0.5 + topOffset - spacing * multiline_count / 2. - spacing / 2.;
|
||||
coordf_t x, y;
|
||||
int xm, ym;
|
||||
// if the print direction needs to be rotated, then swap the extents
|
||||
if((topDistance % 2) == 0){
|
||||
std::swap(gridStartL, gridStartP);
|
||||
std::swap(gridEndL, gridEndP);
|
||||
}
|
||||
// adjust spacing so that it starts and ends on exactly the right place
|
||||
// and increase fill density slightly to reduce gaps
|
||||
coordf_t region_count = floor((gridEndP - gridStartP) / (spacing / sqrt(2)));
|
||||
if(region_count <= 0){
|
||||
return lines;
|
||||
}
|
||||
spacing = (gridEndP - gridStartP) / region_count;
|
||||
for (x = offsetX, xm = 0; x <= (lengthX); x+= gridSize, xm = xm ^ 1) {
|
||||
for (y = offsetY, ym = 0; y <= (lengthY); y += gridSize, ym = ym ^ 1) {
|
||||
if(((xm ^ ym) == 1) == printVert){
|
||||
continue;
|
||||
}
|
||||
// // For debugging: remove 0,0 -> 1,1 top to help understand orientation
|
||||
// if((x <= (gridSize + EPSILON)) && (y <= (gridSize + EPSILON)) && ((y - x) < EPSILON)){
|
||||
// continue;
|
||||
// }
|
||||
Polyline newPoints;
|
||||
int dirMod = xm ^ ym;
|
||||
if(printVert == (topDistance % 2)){
|
||||
if(y < (lengthY - spacing * multiline_count * 1.5)){
|
||||
coordf_t endPMod = std::min(lengthX - (multiline_count * (spacing + 1) / 2.), x + gridEndP) - x;
|
||||
coordf_t endLMod = std::min(lengthY - (multiline_count * (spacing + 1) / 2.), y + gridEndL) - y;
|
||||
for(coordf_t xi = gridStartP; xi < (endPMod + EPSILON); xi += spacing, dirMod = dirMod ^ 1){
|
||||
newPoints.points.push_back((dirMod == 0) ? Point(x + xi, y + gridStartL) : Point(x + xi, y + endLMod));
|
||||
newPoints.points.push_back((dirMod == 0) ? Point(x + xi, y + endLMod) : Point(x + xi, y + gridStartL));
|
||||
pointCount += 2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(x < (lengthX - spacing * multiline_count * 1.5)){
|
||||
coordf_t endPMod = std::min(lengthY - (multiline_count * (spacing + 1) / 2.), y + gridEndP) - y;
|
||||
coordf_t endLMod = std::min(lengthX - (multiline_count * (spacing + 1) / 2.), x + gridEndL) - x;
|
||||
for(coordf_t yi = gridStartP; yi < (endPMod + EPSILON); yi += spacing, dirMod = dirMod ^ 1){
|
||||
newPoints.points.push_back((dirMod == 0) ? Point(x + gridStartL, y + yi) : Point(x + endLMod, y + yi));
|
||||
newPoints.points.push_back((dirMod == 0) ? Point(x + endLMod, y + yi) : Point(x + gridStartL, y + yi));
|
||||
pointCount += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
lines.push_back(newPoints);
|
||||
}
|
||||
}
|
||||
points.push_back(gridLength);
|
||||
return points;
|
||||
return lines;
|
||||
}
|
||||
|
||||
// Generate an array of points for the dimension that is perpendicular to
|
||||
// the basic printing line (i.e. X points for columns, Y points for rows)
|
||||
static std::vector<coordf_t> perpendPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints,
|
||||
size_t baseLocation, size_t gridLength,
|
||||
size_t offsetBase, coordf_t perpDir)
|
||||
// Generate a set of polylines that complete octahedron curves on the
|
||||
// extremities of a pattern
|
||||
static Polylines makeEndPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints,
|
||||
coordf_t lengthX, coordf_t lengthY, coordf_t spacing, size_t multiline_count)
|
||||
{
|
||||
std::vector<coordf_t> points;
|
||||
points.push_back(offsetBase);
|
||||
for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= gridSize*2) {
|
||||
for(size_t pi = 0; pi < critPoints.size(); pi++){
|
||||
Polylines lines;
|
||||
coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
|
||||
bool printVert = zCycle < 0.5;
|
||||
bool printHoriz = zCycle >= 0.5;
|
||||
int zFlipOffset = ((sgn(fmod(zCycle, 0.5) - 0.25) > 0) == printVert) ? 0 : 1;
|
||||
// create templates for copying
|
||||
Polylines startLines, endLines;
|
||||
for(size_t li = 0; li < multiline_count; li++){
|
||||
coordf_t oAdj = (li - ((multiline_count - 1) / 2.)) * spacing; // orthogonal line adjustment
|
||||
coordf_t dAdj = oAdj * sqrt(2); // diagonal line adjustment
|
||||
Polyline startLine, endLine;
|
||||
// Left Bottom; Bottom Left
|
||||
startLine.points.push_back(printHoriz ? Point(oAdj, -dAdj) : Point(-dAdj, oAdj));
|
||||
// Right Bottom; Top Left
|
||||
endLine.points.push_back(printHoriz ? Point(-oAdj, -dAdj) : Point(-dAdj, -oAdj));
|
||||
for(size_t pi = 0; pi < 2; pi++){
|
||||
int pDir = pi * 2 - 1;
|
||||
coordf_t pAdj = pDir * (sqrt(2) - 1) * oAdj;
|
||||
coordf_t troctOffset = abs(troctWave(critPoints[pi], gridSize, Zpos));
|
||||
startLine.points.push_back(printHoriz ?
|
||||
Point(-troctOffset, critPoints[pi] + pAdj) :
|
||||
Point(critPoints[pi] + pAdj, -troctOffset));
|
||||
endLine.points.push_back(printHoriz ?
|
||||
Point(troctOffset, critPoints[pi] + pAdj) :
|
||||
Point(critPoints[pi] + pAdj, troctOffset));
|
||||
}
|
||||
// Left Top; Bottom Right
|
||||
startLine.points.push_back(printHoriz ? Point(oAdj, gridSize + dAdj) : Point(gridSize + dAdj, oAdj));
|
||||
// Right Top; Top Right
|
||||
endLine.points.push_back(printHoriz ? Point(-oAdj, gridSize + dAdj) : Point(gridSize + dAdj, -oAdj));
|
||||
startLines.push_back(startLine);
|
||||
endLines.push_back(endLine);
|
||||
}
|
||||
coordf_t gridMaxX = ceil((lengthX - EPSILON) / gridSize) * gridSize;
|
||||
coordf_t gridMaxY = ceil((lengthY - EPSILON) / gridSize) * gridSize;
|
||||
for(size_t li = 0; li < multiline_count; li++){
|
||||
coordf_t mlFactor = (li - ((multiline_count - 1) / 2.)) * spacing;
|
||||
for (coordf_t cLoc = zFlipOffset * gridSize; cLoc < ((printHoriz ? gridMaxY : gridMaxX) - EPSILON); cLoc += gridSize * 2) {
|
||||
Polyline tsLine(startLines[li]);
|
||||
Polyline teLine(endLines[li]);
|
||||
tsLine.translate(printVert ? Point(cLoc, -mlFactor) : Point(-mlFactor, cLoc));
|
||||
teLine.translate(printVert ? Point(cLoc, gridMaxY + mlFactor) : Point(gridMaxX + mlFactor, cLoc));
|
||||
lines.push_back(tsLine);
|
||||
lines.push_back(teLine);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
// Generate a polyline that describes a single path segment through
|
||||
// the infill in the same direction as the basic printing line (i.e. X
|
||||
// points for columns, Y points for rows)
|
||||
static Polyline patternPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints,
|
||||
coordf_t gridLength, coordf_t perpDir, int print_dir, coordf_t oAdj)
|
||||
{
|
||||
Polyline line;
|
||||
coordf_t dAdj = oAdj * (sqrt(2) - 1); // additional diagonal adjustment
|
||||
coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
|
||||
int zFlipDirection = sgn(fmod(zCycle, 0.5) - 0.25);
|
||||
bool hitEnd = false;
|
||||
int endPi = -1;
|
||||
size_t pi = 0;
|
||||
size_t piOfs = 0;
|
||||
line.points.push_back((print_dir == 1) ? Point(dAdj, 0.) : Point(0., dAdj));
|
||||
coordf_t gridMax = ceil((gridLength - EPSILON) / gridSize) * gridSize;
|
||||
for (coordf_t cLoc = 0; cLoc < gridMax; cLoc += gridSize, piOfs = (piOfs + 2) % 4) {
|
||||
for(pi = piOfs; pi < (piOfs + 2); pi++){
|
||||
coordf_t offset = troctWave(critPoints[pi], gridSize, Zpos);
|
||||
points.push_back(offsetBase + (offset * perpDir));
|
||||
coordf_t offsetFlip = sgn(offset);
|
||||
coordf_t posFlip = floor(((pi + 1) % 4) / 2) * 2 - 1;
|
||||
coordf_t posLin = cLoc - (piOfs * gridSize / 2.) + critPoints[pi];
|
||||
coordf_t posPerp = offset * perpDir;
|
||||
line.points.push_back((print_dir == 1) ?
|
||||
Point(posPerp, posLin + posFlip * dAdj * perpDir * zFlipDirection * print_dir) :
|
||||
Point(posLin + posFlip * dAdj * perpDir * zFlipDirection * print_dir, posPerp));
|
||||
}
|
||||
}
|
||||
points.push_back(offsetBase);
|
||||
return points;
|
||||
}
|
||||
|
||||
static inline Pointfs zip(const std::vector<coordf_t> &x, const std::vector<coordf_t> &y)
|
||||
{
|
||||
assert(x.size() == y.size());
|
||||
Pointfs out;
|
||||
out.reserve(x.size());
|
||||
for (size_t i = 0; i < x.size(); ++ i)
|
||||
out.push_back(Vec2d(x[i], y[i]));
|
||||
return out;
|
||||
line.points.push_back((print_dir == 1) ? Point(dAdj, gridMax) : Point(gridMax, dAdj));
|
||||
return line;
|
||||
}
|
||||
|
||||
// Generate a set of curves (array of array of 2d points) that describe a
|
||||
// horizontal slice of a truncated regular octahedron.
|
||||
static std::vector<Pointfs> makeActualGrid(coordf_t Zpos, coordf_t gridSize, size_t boundsX, size_t boundsY)
|
||||
static Polylines makeZigZag(coordf_t Zpos, coordf_t gridSize, coordf_t lengthX, coordf_t lengthY,
|
||||
coordf_t spacing, size_t multiline_count)
|
||||
{
|
||||
std::vector<Pointfs> points;
|
||||
Polylines lines;
|
||||
std::vector<coordf_t> critPoints = getCriticalPoints(Zpos, gridSize);
|
||||
coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
|
||||
bool printVert = zCycle < 0.5;
|
||||
if (printVert) {
|
||||
int perpDir = -1;
|
||||
for (coordf_t x = 0; x <= (boundsX); x+= gridSize, perpDir *= -1) {
|
||||
points.push_back(Pointfs());
|
||||
Pointfs &newPoints = points.back();
|
||||
newPoints = zip(
|
||||
perpendPoints(Zpos, gridSize, critPoints, 0, boundsY, x, perpDir),
|
||||
colinearPoints(Zpos, gridSize, critPoints, 0, boundsY));
|
||||
BoundingBox extents;
|
||||
int perpDir = -1;
|
||||
int perpDirPattern = -1;
|
||||
coordf_t gridMax = ceil(((printVert ? lengthX : lengthY) - EPSILON) / gridSize) * gridSize;
|
||||
for (coordf_t pPos = 0; pPos < gridMax; pPos += gridSize, perpDirPattern *= -1) {
|
||||
for (size_t li = 0; li < multiline_count; li++){
|
||||
coordf_t oAdj = (li - ((multiline_count - 1) / 2.)) * spacing; // orthogonal line adjustment
|
||||
Polyline newPoints;
|
||||
newPoints = patternPoints(Zpos, gridSize, critPoints,
|
||||
printVert ? lengthY : lengthX,
|
||||
perpDirPattern, printVert ? 1 : -1, oAdj);
|
||||
if (perpDir == 1)
|
||||
std::reverse(newPoints.begin(), newPoints.end());
|
||||
}
|
||||
} else {
|
||||
int perpDir = 1;
|
||||
for (coordf_t y = gridSize; y <= (boundsY); y+= gridSize, perpDir *= -1) {
|
||||
points.push_back(Pointfs());
|
||||
Pointfs &newPoints = points.back();
|
||||
newPoints = zip(
|
||||
colinearPoints(Zpos, gridSize, critPoints, 0, boundsX),
|
||||
perpendPoints(Zpos, gridSize, critPoints, 0, boundsX, y, perpDir));
|
||||
if (perpDir == -1)
|
||||
std::reverse(newPoints.begin(), newPoints.end());
|
||||
std::reverse(newPoints.points.begin(), newPoints.points.end());
|
||||
newPoints.translate(printVert ? Point(pPos + oAdj, 0.) : Point(0., pPos + oAdj));
|
||||
extents.merge(newPoints.points);
|
||||
lines.push_back(newPoints);
|
||||
perpDir *= -1;
|
||||
}
|
||||
}
|
||||
return points;
|
||||
return lines;
|
||||
}
|
||||
|
||||
// Generate a set of curves (array of array of 2d points) that describe a
|
||||
// horizontal slice of a truncated regular octahedron with a specified
|
||||
// grid square size.
|
||||
// gridWidth and gridHeight define the width and height of the bounding box respectively
|
||||
static Polylines makeGrid(coordf_t z, coordf_t gridSize, coordf_t boundWidth, coordf_t boundHeight, bool fillEvenly)
|
||||
// Note: this uses the 'complete' infill parameter to determine if the
|
||||
// square tops should be enclosed (true) or open (false). Alternatively,
|
||||
// a rotation angle of 180 degrees or greater can be used.
|
||||
static Polylines makeGrid(coordf_t z, coordf_t zLast, coordf_t gridSize,
|
||||
coordf_t lengthX, coordf_t lengthY,
|
||||
bool completeTops, coordf_t spacing, size_t multiline_count, size_t layer_count)
|
||||
{
|
||||
std::vector<Pointfs> polylines = makeActualGrid(z, gridSize, boundWidth, boundHeight);
|
||||
coordf_t zCycle = fmod(z + gridSize/2, gridSize * 2.) / (gridSize * 2.);
|
||||
bool printVert = zCycle < 0.5;
|
||||
coordf_t zCycleLast = fmod(zLast + gridSize/2, gridSize * 2.) / (gridSize * 2.);
|
||||
bool printVertLast = zCycleLast < 0.5;
|
||||
Polylines result;
|
||||
result.reserve(polylines.size());
|
||||
for (std::vector<Pointfs>::const_iterator it_polylines = polylines.begin();
|
||||
it_polylines != polylines.end(); ++ it_polylines) {
|
||||
result.push_back(Polyline());
|
||||
Polyline &polyline = result.back();
|
||||
for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it)
|
||||
polyline.points.push_back(Point(coord_t((*it)(0)), coord_t((*it)(1))));
|
||||
Polylines polyZag = makeZigZag(z, gridSize, lengthX, lengthY, spacing, multiline_count);
|
||||
result.insert(result.end(), polyZag.begin(), polyZag.end());
|
||||
// add end connectors
|
||||
std::vector<coordf_t> critPoints = getCriticalPoints(z, gridSize);
|
||||
Polylines endPoints = makeEndPoints(z, gridSize, critPoints, lengthX, lengthY, spacing, multiline_count);
|
||||
result.insert(result.end(), endPoints.begin(), endPoints.end());
|
||||
// add tops for the first <multiline_count> layers in each cycle
|
||||
if(completeTops && (printVert != printVertLast)){
|
||||
coordf_t layerHeight = (z - zLast) / (multiline_count * layer_count);
|
||||
size_t top_distance = 0;
|
||||
for(coordf_t zCheck = z; zCheck >= (zLast + EPSILON); zCheck -= layerHeight * layer_count, top_distance++){
|
||||
coordf_t zCheckCycle = fmod(zCheck + gridSize/2, gridSize * 2.) / (gridSize * 2.);
|
||||
if(printVert != (zCheckCycle < 0.5)){
|
||||
break;
|
||||
}
|
||||
}
|
||||
Polylines polytops = addTops(z, gridSize, lengthX, lengthY, spacing, multiline_count, top_distance);
|
||||
result.insert(result.end(), polytops.begin(), polytops.end());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// FillParams has the following useful information:
|
||||
// density <0 .. 1> [proportion of space to fill]
|
||||
// anchor_length [???]
|
||||
// anchor_length_max [???]
|
||||
// dont_connect() [avoid connect lines]
|
||||
// dont_adjust [avoid filling space evenly]
|
||||
// monotonic [fill strictly left to right]
|
||||
// complete [complete each loop]
|
||||
// multiline [number of lines to draw for each pattern line]
|
||||
// complete_top [should the top surfaces of the pattern be filled]
|
||||
|
||||
void Fill3DHoneycomb::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
@@ -207,78 +340,62 @@ void Fill3DHoneycomb::_fill_surface_single(
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
// no rotation is supported for this infill pattern
|
||||
// Support infill angle
|
||||
auto infill_angle = float(this->angle);
|
||||
if (std::abs(infill_angle) >= EPSILON) expolygon.rotate(-infill_angle);
|
||||
BoundingBox bb = expolygon.contour.bounding_box();
|
||||
|
||||
// Expand the bounding box to avoid artifacts at the edges
|
||||
coord_t expand = 5 * (scale_(this->spacing));
|
||||
bb.offset(expand);
|
||||
// Increase the bounding box outwards to avoid edge clipping artefacts
|
||||
coord_t expandSize = 5. * scale_(this->spacing);
|
||||
bb.offset(expandSize);
|
||||
|
||||
// Adjustment for combining infill setting
|
||||
size_t layersPerSlice = 1;
|
||||
if(thickness_layers > 0){
|
||||
layersPerSlice = thickness_layers;
|
||||
}
|
||||
|
||||
// Note: with equally-scaled X/Y/Z, the pattern will create a vertically-stretched
|
||||
// truncated octahedron; so Z is pre-adjusted first by scaling by sqrt(2)
|
||||
coordf_t zScale = sqrt(2);
|
||||
|
||||
// adjustment to account for the additional distance of octagram curves
|
||||
// note: this only strictly applies for a rectangular area where the total
|
||||
// Z travel distance is a multiple of the spacing... but it should
|
||||
// be at least better than the prevous estimate which assumed straight
|
||||
// lines
|
||||
// Density adjustment to account for the additional distance of
|
||||
// octagram curves. [This only strictly applies for a rectangular
|
||||
// area where the total Z travel distance is a multiple of the
|
||||
// spacing]
|
||||
// = 4 * integrate(func=4*x(sqrt(2) - 1) + 1, from=0, to=0.25)
|
||||
// = (sqrt(2) + 1) / 2 [... I think]
|
||||
// make a first guess at the preferred grid Size
|
||||
coordf_t gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) * params.multiline / params.density);
|
||||
|
||||
// This density calculation is incorrect for many values > 25%, possibly
|
||||
// due to quantisation error, so this value is used as a first guess, then the
|
||||
// Z scale is adjusted to make the layer patterns consistent / symmetric
|
||||
// This means that the resultant infill won't be an ideal truncated octahedron,
|
||||
// but it should look better than the equivalent quantised version
|
||||
|
||||
//Orca: uses a fixed layer height to avoid inconsistent bridges and variable layer height artifacts.
|
||||
//coordf_t layerHeight = scale_(thickness_layers);
|
||||
coordf_t layerHeight = scale_(1.0);
|
||||
// ceiling to an integer value of layers per Z
|
||||
// (with a little nudge in case it's close to perfect)
|
||||
// make a first guess at the preferred grid Size (in unscaled units)
|
||||
coordf_t gridSize = (scale_(this->spacing) *
|
||||
((zScale + 1.) / 2.) * params.multiline / params.density);
|
||||
coordf_t layerHeight = scale_(params.layer_height);
|
||||
coordf_t layersPerModule = floor((gridSize * 2) / (zScale * layerHeight) + 0.05);
|
||||
if(params.density > 0.42){ // exact layer pattern for >42% density
|
||||
// If a density over 42% is requested, set an exact layer pattern
|
||||
if((params.density > 0.42) || (layersPerModule < 2)){
|
||||
layersPerModule = 2;
|
||||
// re-adjust the grid size for a partial octahedral path
|
||||
// (scale of 1.1 guessed based on modeling)
|
||||
gridSize = (scale_(this->spacing) * 1.1 * params.multiline / params.density);
|
||||
// re-adjust zScale to make layering consistent
|
||||
zScale = (gridSize * 2) / (layersPerModule * layerHeight);
|
||||
} else {
|
||||
if(layersPerModule < 2){
|
||||
layersPerModule = 2;
|
||||
}
|
||||
// re-adjust zScale to make layering consistent
|
||||
zScale = (gridSize * 2) / (layersPerModule * layerHeight);
|
||||
// re-adjust the grid size to account for the new zScale
|
||||
gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) * params.multiline / params.density);
|
||||
// re-calculate layersPerModule and zScale
|
||||
layersPerModule = floor((gridSize * 2) / (zScale * layerHeight) + 0.05);
|
||||
if(layersPerModule < 2){
|
||||
layersPerModule = 2;
|
||||
}
|
||||
zScale = (gridSize * 2) / (layersPerModule * layerHeight);
|
||||
}
|
||||
|
||||
// align bounding box to a multiple of our honeycomb grid module
|
||||
// (a module is 2*$gridSize since one $gridSize half-module is
|
||||
// growing while the other $gridSize half-module is shrinking)
|
||||
bb.merge(align_to_grid(bb.min, Point(gridSize*4, gridSize*4)));
|
||||
// align bounding box to a multiple of the octahedron grid so that
|
||||
// layers with different starting points have matching origins
|
||||
bb.merge(align_to_grid(bb.min, Point(gridSize * 2., gridSize * 2.)));
|
||||
|
||||
// Z adjustment to start at the widest point for the lowest layer
|
||||
coordf_t startOffset = gridSize / 2. + scale_(params.layer_height / 2.);
|
||||
// generate pattern
|
||||
Polylines polylines =
|
||||
makeGrid(
|
||||
scale_(this->z) * zScale,
|
||||
gridSize,
|
||||
bb.size()(0),
|
||||
bb.size()(1),
|
||||
!params.dont_adjust);
|
||||
scale_(this->z) * zScale + startOffset,
|
||||
scale_(this->z - (params.layer_height * params.multiline * layersPerSlice)) * zScale + startOffset,
|
||||
gridSize, bb.size()(0), bb.size()(1),
|
||||
params.infill_complete_top,
|
||||
scale_(this->spacing),
|
||||
params.multiline,
|
||||
layersPerSlice);
|
||||
|
||||
// move pattern in place
|
||||
for (Polyline &pl : polylines){
|
||||
@@ -289,8 +406,8 @@ void Fill3DHoneycomb::_fill_surface_single(
|
||||
smooth_polyline_corners(pl, params.smooth_factor, scaled<double>(params.resolution));
|
||||
}
|
||||
|
||||
// Apply multiline offset if needed
|
||||
multiline_fill(polylines, params, spacing);
|
||||
// Note: multiline fill adjustment is carried out in this code,
|
||||
// rather than using the multiline_fill function
|
||||
|
||||
// clip pattern to boundaries, chain the clipped polylines
|
||||
polylines = intersection_pl(std::move(polylines), to_polygons(expolygon));
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
|
||||
namespace Slic3r { class Point; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Fill3DHoneycomb : public Fill
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
#include <boost/geometry/geometries/point.hpp>
|
||||
#include <boost/geometry/geometries/segment.hpp>
|
||||
#include <boost/geometry/index/rtree.hpp>
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/SurfaceCollection.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Flow.hpp"
|
||||
// #define INFILL_DEBUG_OUTPUT
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -24,10 +24,11 @@
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../Flow.hpp"
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../ExtrusionEntityCollection.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r { class ExtrusionEntityCollection; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -117,6 +118,7 @@ struct FillParams
|
||||
|
||||
float horiz_move{0.0}; //move infill to get cross zag pattern
|
||||
bool symmetric_infill_y_axis{false};
|
||||
bool infill_complete_top{false};
|
||||
coord_t symmetric_y_axis{0};
|
||||
bool locked_zag{false};
|
||||
float infill_lock_depth{0.0};
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <libslic3r/ShortestPath.hpp>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "FillBase.hpp"
|
||||
#include <utility>
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
|
||||
namespace Slic3r { class Point; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillConcentric : public Fill
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
#include <libslic3r/ShortestPath.hpp>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Flow.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r { class Surface; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r { class Surface; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillConcentricInternal : public Fill
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../Point.hpp"
|
||||
#include "../Polygon.hpp"
|
||||
#include "../Polyline.hpp"
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
@@ -13,6 +11,7 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "FillCrossHatch.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "FillBase.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
|
||||
namespace Slic3r { class Point; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillCrossHatch : public Fill
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../MarchingSquares.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
@@ -18,6 +16,7 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "FillGyroid.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Marching-squares scalar field for the optimized gyroid branch.
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#ifndef slic3r_FillGyroid_hpp_
|
||||
#define slic3r_FillGyroid_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "FillBase.hpp"
|
||||
#include <utility>
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
|
||||
namespace Slic3r { class Point; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillGyroid : public Fill
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include "libslic3r/Fill/FillBase.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
@@ -14,6 +12,7 @@
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include "FillHoneycomb.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../Print.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/Fill/Lightning/Layer.hpp"
|
||||
#include "FillBase.hpp"
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
|
||||
namespace Slic3r { class Point; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include "libslic3r/Fill/FillBase.hpp"
|
||||
#include <utility>
|
||||
#include "libslic3r/Point.hpp"
|
||||
@@ -15,6 +14,8 @@
|
||||
#include <cstdlib>
|
||||
|
||||
#include "FillLine.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Fill/FillBase.hpp"
|
||||
@@ -18,6 +17,7 @@
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include "FillPlanePath.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "../Geometry.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../VariableWidth.hpp"
|
||||
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Fill/FillBase.hpp"
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include "libslic3r/Flow.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
namespace Slic3r { class Flow; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintRegionConfig;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include "../VariableWidth.hpp"
|
||||
#include "Arachne/WallToolPaths.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
@@ -22,6 +20,9 @@
|
||||
#include <math.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "FillBase.hpp"
|
||||
#include <utility>
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
|
||||
namespace Slic3r { class Point; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillSpiralInset : public Fill
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "FillBase.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
namespace marchsq {
|
||||
using namespace Slic3r;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "FillBase.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/MultiMaterialSegmentation.hpp"
|
||||
#include "libslic3r/SurfaceCollection.hpp"
|
||||
|
||||
/* Possible future tasks/optimizations,etc.:
|
||||
* - Improve connecting heuristic to favor connecting to shorter trees
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
#include "../../ClipperUtils.hpp"
|
||||
#include "../../Geometry.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Line.hpp"
|
||||
|
||||
//#define LIGHTNING_TREE_NODE_DEBUG_OUTPUT
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "Flow.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include <cmath>
|
||||
@@ -10,6 +9,7 @@
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <string>
|
||||
#include <math.h>
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
// Mark string for localization and translate.
|
||||
#define L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <math.h>
|
||||
#include "FlushVolPredictor.hpp"
|
||||
#include "slic3r/Utils/ColorSpaceConvert.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "FlushVolCalc.hpp"
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
#include "../Model.hpp"
|
||||
#include "../Utils.hpp"
|
||||
#include "../LocalesUtils.hpp"
|
||||
#include "../GCode.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../GCode/ThumbnailData.hpp"
|
||||
#include "../Semver.hpp"
|
||||
#include "../Time.hpp"
|
||||
|
||||
#include "../I18N.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
@@ -12,17 +12,10 @@
|
||||
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../Exception.hpp"
|
||||
#include "../Model.hpp"
|
||||
#include "../GCode.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../Utils.hpp"
|
||||
#include "../I18N.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../CustomGCode.hpp"
|
||||
#include "../LocalesUtils.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../TriangleMesh.hpp"
|
||||
#include "../TexturePainting.hpp"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "SL1.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
@@ -35,7 +34,6 @@
|
||||
#include "libslic3r/SlicesToTriangleMesh.hpp"
|
||||
#include "libslic3r/MarchingSquares.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/SLA/RasterBase.hpp"
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
@@ -50,6 +48,7 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <string_view>
|
||||
#include "libslic3r/SLA/Pad.hpp"
|
||||
|
||||
namespace marchsq {
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../TriangleMesh.hpp"
|
||||
#include "libslic3r/Exception.hpp"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../TriangleMesh.hpp"
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "../Geometry.hpp"
|
||||
#include "../GCode/ThumbnailData.hpp"
|
||||
#include "../Semver.hpp"
|
||||
#include "../Time.hpp"
|
||||
|
||||
#include "../I18N.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
@@ -113,6 +112,7 @@ namespace pt = boost::property_tree;
|
||||
#include "NSVGUtils.hpp"
|
||||
|
||||
#include <fast_float/fast_float.h>
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
|
||||
// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter,
|
||||
// https://github.com/boostorg/spirit/pull/586
|
||||
@@ -2016,7 +2016,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
else if (boost::algorithm::iequals(name, ORCA_CAD_RECIPE_FILE)
|
||||
|| boost::algorithm::iequals(name, LEGACY_CAD_RECIPE_FILE)) {
|
||||
// Restore the editable CAD recipe (optional; absent in non-CAD projects).
|
||||
if (stat.m_uncomp_size > 0) {
|
||||
// The current name wins over the legacy one whichever the archive lists
|
||||
// first, and the size the archive claims is capped before it is allocated.
|
||||
constexpr mz_uint64 kMaxCadRecipe = mz_uint64(1) << 30; // 1 GiB
|
||||
const bool legacy = boost::algorithm::iequals(name, LEGACY_CAD_RECIPE_FILE);
|
||||
if (stat.m_uncomp_size > kMaxCadRecipe) {
|
||||
BOOST_LOG_TRIVIAL(error) << "3MF: CAD recipe of " << stat.m_uncomp_size
|
||||
<< " bytes exceeds the limit; not loaded";
|
||||
} else if (stat.m_uncomp_size > 0 && !(legacy && !model.cad_recipe.empty())) {
|
||||
std::string buf((size_t)stat.m_uncomp_size, '\0');
|
||||
if (mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, buf.data(), buf.size(), 0))
|
||||
model.cad_recipe = std::move(buf);
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "TopoDS.hxx"
|
||||
#include "BRepExtrema_SelfIntersection.hxx"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
const double STEP_TRANS_CHORD_ERROR = 0.005;
|
||||
|
||||
@@ -119,8 +119,8 @@
|
||||
#endif
|
||||
|
||||
#include <Shiny/Shiny.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "miniz_extension.hpp"
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
@@ -132,6 +132,14 @@ using namespace std::literals::string_view_literals;
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include "AABBTreeLines.hpp"
|
||||
#include "Extruder.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "FilamentMixer.hpp"
|
||||
#include "Format/STEP.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "MultiNozzleUtils.hpp"
|
||||
#include "Slicing.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
#include "ExtrusionEntity.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "GCodeWriter.hpp"
|
||||
#include "Layer.hpp"
|
||||
#include "Point.hpp"
|
||||
@@ -22,7 +20,6 @@
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#include "GCode/SeamPlacer.hpp"
|
||||
#include "GCode/GCodeProcessor.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "GCode/ExtrusionProcessor.hpp"
|
||||
@@ -48,6 +45,10 @@
|
||||
#include <cfloat>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
||||
namespace Slic3r { class ExtrusionEntityCollection; }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user