merge main

This commit is contained in:
Lam Wei Lun
2026-10-06 13:08:46 +08:00
parent ccbb73baaf
commit ecbd03fed7
868 changed files with 203633 additions and 35823 deletions
+14
View File
@@ -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
View File
@@ -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 {
+1
View File
@@ -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
+8
View File
@@ -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;
+2
View File
@@ -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;
+3
View File
@@ -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" )
+1
View File
@@ -2,6 +2,7 @@
#include <Windows.h>
#include <tchar.h>
#include <vector>
#include <cstddef>
namespace textconv_helper
{
+1
View File
@@ -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 {
+3
View File
@@ -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"
-1
View File
@@ -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"
+1 -1
View File
@@ -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>
+5
View File
@@ -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
File diff suppressed because it is too large Load Diff
+147 -7
View File
@@ -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,
+84 -45
View File
@@ -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;
+13 -3
View File
@@ -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);
+293 -89
View File
@@ -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)
{
+23 -15
View File
@@ -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);
+11 -5
View File
@@ -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);
+8
View File
@@ -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
+21 -6
View File
@@ -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;
}
+6 -2
View File
@@ -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
+144 -18
View File
@@ -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];
+8 -1
View File
@@ -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
+2
View File
@@ -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; }
};
-2
View File
@@ -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 -1
View File
@@ -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;
+1 -1
View File
@@ -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
+1
View File
@@ -3,6 +3,7 @@
#include <clipper2/clipper2_z.hpp>
#include <cstddef>
#include "Point.hpp"
namespace Slic3r {
namespace ClipperZUtils {
+1
View File
@@ -13,6 +13,7 @@
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Point.hpp>
#include <libslic3r/ExPolygon.hpp>
#include "Polygon.hpp"
namespace Slic3r {
+3
View File
@@ -14,6 +14,9 @@
#include <math.h>
#include <string>
#include <utility>
#include "Config.hpp"
#include "Polyline.hpp"
#include "PrintConfig.hpp"
namespace Slic3r {
-3
View File
@@ -1,7 +1,4 @@
#include "CustomGCode.hpp"
#include "Config.hpp"
#include "GCode.hpp"
#include "GCodeWriter.hpp"
#include <vector>
#include <utility>
#include <cstddef>
+3 -1
View File
@@ -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{
+1
View File
@@ -19,6 +19,7 @@
#include <optional>
#include <math.h>
#include <cmath>
#include "BoundingBox.hpp"
namespace Slic3r {
+1 -1
View File
@@ -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>
+1
View File
@@ -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
-2
View File
@@ -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>
+1
View File
@@ -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
+1
View File
@@ -6,6 +6,7 @@
#include <cassert>
#include <algorithm>
#include <cmath>
#include "Config.hpp"
namespace Slic3r {
-1
View File
@@ -2,7 +2,6 @@
#define slic3r_Extruder_hpp_
#include "libslic3r.h"
#include "Point.hpp"
#include <vector>
namespace Slic3r {
+1 -1
View File
@@ -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)
+1
View File
@@ -25,6 +25,7 @@
#include "ExtrusionEntity.hpp"
#include "libslic3r.h"
#include "ExtrusionSimulator.hpp"
#include "Polyline.hpp"
#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
-1
View File
@@ -2,7 +2,6 @@
#define slic3r_ExtrusionSimulator_hpp_
#include "Point.hpp"
#include "libslic3r.h"
#include "ExtrusionEntity.hpp"
#include "BoundingBox.hpp"
-1
View File
@@ -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
{
+15
View File
@@ -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;
+244 -127
View File
@@ -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 &params,
@@ -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));
+2
View File
@@ -11,6 +11,8 @@
#include "libslic3r/Point.hpp"
#include "libslic3r/Polyline.hpp"
namespace Slic3r { class Point; }
namespace Slic3r {
class Fill3DHoneycomb : public Fill
+3
View File
@@ -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 {
+1
View File
@@ -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 {
+4 -2
View File
@@ -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};
+1
View File
@@ -20,6 +20,7 @@
#include <libslic3r/ShortestPath.hpp>
#include <utility>
#include <vector>
#include "libslic3r/ExtrusionEntity.hpp"
namespace Slic3r {
+2 -1
View File
@@ -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 -2
View File
@@ -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 {
+2 -2
View File
@@ -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 -2
View File
@@ -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.
+2 -2
View File
@@ -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 -2
View File
@@ -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
View File
@@ -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"
+2 -1
View File
@@ -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;
+2 -1
View File
@@ -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 -1
View File
@@ -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 {
-1
View File
@@ -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"
+2 -1
View File
@@ -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;
+3 -2
View File
@@ -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 {
+2 -1
View File
@@ -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
-1
View File
@@ -3,7 +3,6 @@
#include <utility>
#include "libslic3r/libslic3r.h"
#include "FillBase.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/Polyline.hpp"
+1
View File
@@ -16,6 +16,7 @@
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "libslic3r/Polygon.hpp"
namespace marchsq {
using namespace Slic3r;
-1
View File
@@ -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
-1
View File
@@ -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 -1
View File
@@ -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)
-1
View File
@@ -4,7 +4,6 @@
#include <math.h>
#include "FlushVolPredictor.hpp"
#include "slic3r/Utils/ColorSpaceConvert.hpp"
#include "Utils.hpp"
#include "FlushVolCalc.hpp"
-2
View File
@@ -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"
-7
View File
@@ -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
View File
@@ -1,4 +1,3 @@
#include "../libslic3r.h"
#include "../Model.hpp"
#include "../TriangleMesh.hpp"
#include "../TexturePainting.hpp"
+1 -2
View File
@@ -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
View File
@@ -1,4 +1,3 @@
#include "../libslic3r.h"
#include "../Model.hpp"
#include "../TriangleMesh.hpp"
#include "libslic3r/Exception.hpp"
-1
View File
@@ -1,4 +1,3 @@
#include "../libslic3r.h"
#include "../Model.hpp"
#include "../TriangleMesh.hpp"
+9 -2
View File
@@ -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);
+1
View File
@@ -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;
+9 -1
View File
@@ -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 -3
View File
@@ -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