Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3cc73fb
Factor out the stack settings into their own struct
k3DW Sep 29, 2025
6a38596
Separate sinterbox_parameters into sinterbox_bounding and sinterbox_s…
k3DW Sep 29, 2025
9e61d5e
Remove tiny floating point change in sinterbox_settings
k3DW Sep 29, 2025
dd4aa6d
Use std::expected for files::read_file()
k3DW Sep 29, 2025
9a4ba34
Populate the sinterbox setting and stack setting spinners with defaul…
k3DW Sep 29, 2025
cee5045
Write struct part_base and stack_result_base, for usage in saving pro…
k3DW Oct 1, 2025
8666ae6
Write save_state and its dummy functions
k3DW Oct 1, 2025
ad1de2a
Convert the save state to an internal representation meant for serial…
k3DW Oct 1, 2025
1a3c285
Use library jsoncons
k3DW Oct 2, 2025
4cd75c8
Opt into jsoncons serialization traits
k3DW Oct 2, 2025
b5f0487
Write function on_save()
k3DW Oct 2, 2025
f003fe8
Write a json schema for the PartStacker project
k3DW Oct 2, 2025
08a2545
Compile pstack_gui with /bigobj
k3DW Oct 2, 2025
22ba011
Separate save_state into out_save_state and in_save_state
k3DW Oct 2, 2025
8dd0777
Factor out some part creation functionality into calc::initialize_part()
k3DW Oct 2, 2025
56f5031
Write replace_all() functions for the list views
k3DW Oct 2, 2025
ae7c1f3
Write setters for stack settings and sinterbox settings
k3DW Oct 2, 2025
6fdf72f
Fix missing check in on_new()
k3DW Oct 2, 2025
9e876f1
Write save_state_from_json()
k3DW Oct 2, 2025
dc5137d
Write on_open()
k3DW Oct 2, 2025
66f706b
Load the result meshes
k3DW Oct 2, 2025
ab9a3f7
Rename 'pref' to 'preferences' for conformance to the schema
k3DW Oct 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enable_testing()

find_package(Catch2 CONFIG REQUIRED)
find_package(GLEW REQUIRED)
find_package(jsoncons CONFIG REQUIRED)
find_package(mdspan CONFIG REQUIRED)
find_package(wxWidgets CONFIG REQUIRED COMPONENTS core base net gl)

Expand Down
3 changes: 2 additions & 1 deletion src/pstack/calc/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
add_library(pstack_calc STATIC
mesh.cpp
part.cpp
rotations.cpp
sinterbox.cpp
stacker.cpp
Expand All @@ -20,6 +21,6 @@ set_target_properties(pstack_calc PROPERTIES
PROJECT_LABEL "calc"
)
target_link_libraries(pstack_calc
PUBLIC pstack_geo pstack_util
PUBLIC pstack_files pstack_geo pstack_util
)
target_include_directories(pstack_calc PUBLIC "${PROJECT_SOURCE_DIR}/src")
51 changes: 51 additions & 0 deletions src/pstack/calc/part.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include "pstack/calc/part.hpp"
#include "pstack/files/stl.hpp"
#include <charconv>
#include <cmath>
#include <filesystem>
#include <optional>
#include <string>

namespace pstack::calc {

namespace {

std::optional<int> get_base_quantity(std::string name) {
char looking_for = '.';
if (name.ends_with(')')) {
name.pop_back();
looking_for = '(';
}
std::size_t number_length = 0;
while (name.size() > number_length and std::isdigit(name[name.size() - number_length - 1])) {
++number_length;
}
if (number_length == 0 or not (name.size() > number_length and name[name.size() - number_length - 1] == looking_for)) {
return std::nullopt;
}
std::string_view number{ name.data() + (name.size() - number_length), name.data() + name.size() };
int out{-1};
std::from_chars(number.data(), number.data() + number.size(), out);
return out;
}

} // namespace

part initialize_part(part_base base) {
part result{ std::move(base) };

result.name = std::filesystem::path(result.mesh_file).stem().string();
result.mesh = files::from_stl(result.mesh_file);
result.mesh.set_baseline({ 0, 0, 0 });

result.base_quantity = get_base_quantity(result.name);

auto volume_and_centroid = result.mesh.volume_and_centroid();
result.volume = volume_and_centroid.volume;
result.centroid = volume_and_centroid.centroid;
result.triangle_count = result.mesh.triangles().size();

return result;
}

} // namespace pstack::calc
19 changes: 13 additions & 6 deletions src/pstack/calc/part.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,30 @@

namespace pstack::calc {

struct part {
// `part_base` is the part data needed to save and load
struct part_base {
std::string mesh_file;
int quantity = 1;
bool mirrored = false;
int min_hole = 1;
int rotation_index = 1;
bool rotate_min_box = false;
};

// `part` is everything that can be derived from `part_base`
struct part : part_base {
std::string name;
mesh mesh;

std::optional<int> base_quantity;
int quantity;

double volume;
geo::point3<float> centroid;
int triangle_count;
bool mirrored;
int min_hole;
int rotation_index;
bool rotate_min_box;
};

part initialize_part(part_base base);

} // namespace pstack::calc

#endif // PSTACK_CALC_PART_HPP
15 changes: 8 additions & 7 deletions src/pstack/calc/sinterbox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ void append_side(std::vector<geo::triangle>& triangles, const util::mdspan<const
}

std::tuple<std::vector<float>, std::vector<float>, std::vector<float>> make_positions(const sinterbox_parameters& params) {
const auto [min, max, clearance, thickness, width, desired_spacing] = params;
const auto [clearance, thickness, width, desired_spacing] = params.settings;
const auto [min, max] = params.bounding;
const geo::vector3 size = max - min;

// Number of bars in the given direction
Expand Down Expand Up @@ -111,8 +112,8 @@ void append_sinterbox(std::vector<geo::triangle>& triangles, const sinterbox_par
upper_xy[x, y] = { positions_x[x], positions_y[y], upper_bound.z };
}
}
append_side(triangles, lower_xy, geo::unit_z<float>, geo::unit_x<float>, geo::unit_y<float>, params.thickness);
append_side(triangles, upper_xy, -geo::unit_z<float>, geo::unit_x<float>, geo::unit_y<float>, params.thickness);
append_side(triangles, lower_xy, geo::unit_z<float>, geo::unit_x<float>, geo::unit_y<float>, params.settings.thickness);
append_side(triangles, upper_xy, -geo::unit_z<float>, geo::unit_x<float>, geo::unit_y<float>, params.settings.thickness);
}

// ZX sides
Expand All @@ -126,8 +127,8 @@ void append_sinterbox(std::vector<geo::triangle>& triangles, const sinterbox_par
upper_zx[z, x] = { positions_x[x], upper_bound.y, positions_z[z] };
}
}
append_side(triangles, lower_zx, geo::unit_y<float>, geo::unit_z<float>, geo::unit_x<float>, params.thickness);
append_side(triangles, upper_zx, -geo::unit_y<float>, geo::unit_z<float>, geo::unit_x<float>, params.thickness);
append_side(triangles, lower_zx, geo::unit_y<float>, geo::unit_z<float>, geo::unit_x<float>, params.settings.thickness);
append_side(triangles, upper_zx, -geo::unit_y<float>, geo::unit_z<float>, geo::unit_x<float>, params.settings.thickness);
}

// YZ sides
Expand All @@ -140,8 +141,8 @@ void append_sinterbox(std::vector<geo::triangle>& triangles, const sinterbox_par
upper_yz[y, z] = { upper_bound.x, positions_y[y], positions_z[z] };
}
}
append_side(triangles, lower_yz, geo::unit_x<float>, geo::unit_y<float>, geo::unit_z<float>, params.thickness);
append_side(triangles, upper_yz, -geo::unit_x<float>, geo::unit_y<float>, geo::unit_z<float>, params.thickness);
append_side(triangles, lower_yz, geo::unit_x<float>, geo::unit_y<float>, geo::unit_z<float>, params.settings.thickness);
append_side(triangles, upper_yz, -geo::unit_x<float>, geo::unit_y<float>, geo::unit_z<float>, params.settings.thickness);
}
}

Expand Down
18 changes: 13 additions & 5 deletions src/pstack/calc/sinterbox.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@

namespace pstack::calc {

struct sinterbox_parameters {
struct sinterbox_settings {
double clearance = 0.8;
double thickness = 0.8;
double width = 1.1;
double spacing = 6.0;
};

struct sinterbox_bounding {
geo::point3<float> min;
geo::point3<float> max;
double clearance;
double thickness;
double width;
double spacing;
};

struct sinterbox_parameters {
sinterbox_settings settings;
sinterbox_bounding bounding;
};

void append_sinterbox(std::vector<geo::triangle>& triangles, const sinterbox_parameters& params);
Expand Down
23 changes: 16 additions & 7 deletions src/pstack/calc/stacker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@

namespace pstack::calc {

void stack_result::reload_mesh() {
this->mesh = {};
for (const auto& piece : pieces) {
auto m = piece.part->mesh;
m.rotate(piece.rotation);
this->mesh.add(m, piece.translation);
}
}

namespace {

struct stack_state {
Expand Down Expand Up @@ -129,7 +138,7 @@ std::optional<stack_result> stack_impl(const stack_parameters& params, const std
state.voxels.assign(state.ordered_parts.size(), {});

double triangles = 0;
const double scale_factor = 1 / params.resolution;
const double scale_factor = 1 / params.settings.resolution;
state.total_parts = 0;
state.total_placed = 0;
for (const std::shared_ptr<const part> part : state.ordered_parts) {
Expand Down Expand Up @@ -227,13 +236,13 @@ std::optional<stack_result> stack_impl(const stack_parameters& params, const std
}
}

int max_x = static_cast<int>(scale_factor * params.x_min);
int max_y = static_cast<int>(scale_factor * params.y_min);
int max_z = static_cast<int>(scale_factor * params.z_min);
int max_x = static_cast<int>(scale_factor * params.settings.x_min);
int max_y = static_cast<int>(scale_factor * params.settings.y_min);
int max_z = static_cast<int>(scale_factor * params.settings.z_min);
state.space = {
std::max(max_x, static_cast<int>(scale_factor * params.x_max)),
std::max(max_y, static_cast<int>(scale_factor * params.y_max)),
std::max(max_z, static_cast<int>(scale_factor * params.z_max))
std::max(max_x, static_cast<int>(scale_factor * params.settings.x_max)),
std::max(max_y, static_cast<int>(scale_factor * params.settings.y_max)),
std::max(max_z, static_cast<int>(scale_factor * params.settings.z_max))
};

params.set_progress(0, 1);
Expand Down
29 changes: 21 additions & 8 deletions src/pstack/calc/stacker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,46 @@

namespace pstack::calc {

struct stack_result {
// `stack_result_base` is the base level information about the result
template <class Part, class Sinterbox>
struct stack_result_base {
struct piece {
std::shared_ptr<const part> part;
Part part;
geo::matrix3<float> rotation;
geo::vector3<float> translation;
};
std::vector<piece> pieces{};
std::optional<Sinterbox> sinterbox{};
};

// `stack_result` is everything that can be calculated/derived from the `stack_result_base`
struct stack_result : stack_result_base<std::shared_ptr<const part>, sinterbox_parameters> {
mesh mesh{};
geo::vector3<float> size{};
double density{};
std::optional<sinterbox_parameters> sinterbox{};

void reload_mesh();
};

struct stack_settings {
double resolution = 1.0;
int x_min = 150;
int x_max = 156;
int y_min = 150;
int y_max = 156;
int z_min = 30;
int z_max = 90;
};

struct stack_parameters {
std::vector<std::shared_ptr<const part>> parts;
stack_settings settings;

std::function<void(double, double)> set_progress;
std::function<void(const mesh&, const geo::point3<int>)> display_mesh;
std::function<void(stack_result, std::chrono::system_clock::duration)> on_success;
std::function<void()> on_failure;
std::function<void()> on_finish;

double resolution;
int x_min, x_max;
int y_min, y_max;
int z_min, z_max;
};

class stacker {
Expand Down
5 changes: 3 additions & 2 deletions src/pstack/files/read.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
#include "pstack/files/read.hpp"
#include <expected>
#include <filesystem>
#include <fstream>
#include <string>

namespace pstack::files {

// From StackOverflow https://stackoverflow.com/a/40903508
std::string read_file(const std::string& file_path) {
std::expected<std::string, std::string> read_file(const std::string& file_path) {
std::ifstream file(file_path, std::ios::in | std::ios::binary);
if (not file.is_open()) {
return {};
return std::unexpected("Could not read file: " + file_path);
}
const auto size = std::filesystem::file_size(file_path);
std::string result(size, '\0');
Expand Down
3 changes: 2 additions & 1 deletion src/pstack/files/read.hpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#ifndef PSTACK_FILES_READ_HPP
#define PSTACK_FILES_READ_HPP

#include <expected>
#include <string>

namespace pstack::files {

std::string read_file(const std::string& file_path);
std::expected<std::string, std::string> read_file(const std::string& file_path);

} // namespace pstack::files

Expand Down
5 changes: 3 additions & 2 deletions src/pstack/files/stl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
namespace pstack::files {

calc::mesh from_stl(const std::string& file_path) {
std::string file = read_file(file_path);
if (file.empty()) {
auto file_expected = read_file(file_path);
if (not file_expected.has_value()) {
return {};
}
std::string& file = *file_expected;
const std::size_t file_size = file.size();
std::istringstream ss(std::move(file));

Expand Down
7 changes: 7 additions & 0 deletions src/pstack/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ add_executable(pstack_gui WIN32
main_window.cpp
parts_list.cpp
results_list.cpp
save.cpp
viewport.cpp
)
target_sources(pstack_gui PUBLIC FILE_SET headers TYPE HEADERS FILES
Expand All @@ -15,6 +16,7 @@ target_sources(pstack_gui PUBLIC FILE_SET headers TYPE HEADERS FILES
parts_list.hpp
preferences.hpp
results_list.hpp
save.hpp
transformation.hpp
viewport.hpp
)
Expand All @@ -23,6 +25,7 @@ set_target_properties(pstack_gui PROPERTIES
PROJECT_LABEL "gui"
)
target_link_libraries(pstack_gui PRIVATE
jsoncons
wx::net
wx::core
wx::base
Expand All @@ -34,6 +37,10 @@ target_link_libraries(pstack_gui PRIVATE
)
target_include_directories(pstack_gui PRIVATE "${PROJECT_SOURCE_DIR}/src")

if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_options(pstack_gui PRIVATE "/bigobj")
endif()

set(PSTACK_OUTPUT_TYPE "GUI")
set(PSTACK_OUTPUT_FILE_NAME "PartStackerGUI")
pstack_configure_target_info(pstack_gui)
Expand Down
Loading