Browse Source

Calibrations : now accept preferred orientation (init_z_rotate)
supermerill/SuperSlicer#4099

supermerill 1 year ago
parent
commit
a935538570

+ 1 - 0
src/libslic3r/Model.hpp

@@ -353,6 +353,7 @@ public:
     /// It operates on the total size by duplicating the object according to all the instances.
     /// \param size Sizef3 the size vector
     void scale_to_fit(const Vec3d &size);
+    /// Rotate the model along its internal position (does not take its position into account)
     void rotate(double angle, Axis axis);
     void rotate(double angle, const Vec3d& axis);
     void mirror(Axis axis);

+ 4 - 2
src/slic3r/GUI/CalibrationAbstractDialog.cpp

@@ -5,6 +5,7 @@
 #include "GUI.hpp"
 #include "GUI_ObjectList.hpp"
 #include "Tab.hpp"
+#include "Plater.hpp"
 
 #include <wx/scrolwin.h>
 #include <wx/display.h>
@@ -101,7 +102,7 @@ void CalibrationAbstractDialog::close_me(wxCommandEvent& event_args) {
     this->Destroy();
 }
 
-ModelObject* CalibrationAbstractDialog::add_part(ModelObject* model_object, std::string input_file, Vec3d move, Vec3d scale) {
+void CalibrationAbstractDialog::add_part(ModelObject* model_object, std::string input_file, Vec3d move, Vec3d scale, bool rotate) {
     Model model;
     try {
         model = Model::read_from_file(input_file);
@@ -124,6 +125,8 @@ ModelObject* CalibrationAbstractDialog::add_part(ModelObject* model_object, std:
             if (scale != Vec3d{ 1,1,1 }) {
                 volume->scale(scale);
             }
+            if(rotate)
+                volume->rotate(Geometry::deg2rad(this->main_frame->plater()->config()->opt_float("init_z_rotate")), Axis::Z);
             ModelVolume* new_volume = model_object->add_volume(*volume);
             new_volume->set_type(ModelVolumeType::MODEL_PART);
             new_volume->name = boost::filesystem::path(input_file).filename().string();
@@ -145,7 +148,6 @@ ModelObject* CalibrationAbstractDialog::add_part(ModelObject* model_object, std:
         }
     }
     assert(model.objects.size() == 1);
-    return model.objects.empty()?nullptr: model.objects[0];
 }
 
 void CalibrationAbstractDialog::on_dpi_changed(const wxRect& suggested_rect)

+ 1 - 1
src/slic3r/GUI/CalibrationAbstractDialog.hpp

@@ -29,7 +29,7 @@ protected:
     virtual void create_buttons(wxStdDialogButtonSizer*) = 0;
     void on_dpi_changed(const wxRect& suggested_rect) override;
     void close_me(wxCommandEvent& event_args);
-    ModelObject* add_part(ModelObject* model_object, std::string input_file, Vec3d move, Vec3d scale = Vec3d{ 1,1,1 });
+    void add_part(ModelObject* model_object, std::string input_file, Vec3d move, Vec3d scale = Vec3d{ 1,1,1 }, bool rotate = true);
 
     wxHtmlWindow* html_viewer;
     MainFrame* main_frame;

+ 13 - 2
src/slic3r/GUI/CalibrationBridgeDialog.cpp

@@ -101,6 +101,16 @@ void CalibrationBridgeDialog::create_geometry(std::string setting_to_test, bool
     } else {
         z_scale = 1;
     }
+    
+    // it's rotated but not around the good origin: correct that
+    double init_z_rotate_angle = Geometry::deg2rad(plat->config()->opt_float("init_z_rotate"));
+    Matrix3d rot_matrix = Eigen::Quaterniond(Eigen::AngleAxisd(init_z_rotate_angle, Vec3d{0,0,1})).toRotationMatrix();
+    auto     translate_from_rotation = [&rot_matrix, &model, &objs_idx](int idx, Vec3d &&translation) {
+            ModelVolume *vol = model.objects[objs_idx[idx]]->volumes[model.objects[objs_idx[idx]]->volumes.size()-1];
+            Geometry::Transformation trsf = vol->get_transformation();
+            trsf.set_offset(rot_matrix * translation - translation + trsf.get_offset());
+            vol->set_transformation(trsf);
+        };
 
     //add sub-part after scale
     const ConfigOptionPercent* bridge_flow_ratio = print_config->option<ConfigOptionPercent>(setting_to_test);
@@ -109,11 +119,12 @@ void CalibrationBridgeDialog::create_geometry(std::string setting_to_test, bool
     for (size_t i = 0; i < nb_items; i++) {
         int step_num = (start + (add ? 1 : -1) * i * step);
         if (step_num < 180 && step_num > 20 && step_num%5 == 0) {
-            add_part(model.objects[objs_idx[i]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "bridge_flow" / ("f" + std::to_string(step_num) + ".amf")).string(), Vec3d{ -10,0, zshift + 4.6 * z_scale }, Vec3d{ 1,1,z_scale });
+            add_part(model.objects[objs_idx[i]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "bridge_flow" / ("f" + std::to_string(step_num) + ".amf")).string(), Vec3d{ -10, 0, zshift + 4.6 * z_scale }, Vec3d{ 1,1,z_scale });
+            translate_from_rotation(i, Vec3d{ -10, 0, zshift + 4.6 * z_scale });
         }
     }
     /// --- translate ---;
-    bool has_to_arrange = false;
+    bool has_to_arrange = init_z_rotate_angle != 0;
     const float brim_width = std::max(print_config->option<ConfigOptionFloat>("brim_width")->value, nozzle_diameter * 5.);
     const ConfigOptionFloat* extruder_clearance_radius = print_config->option<ConfigOptionFloat>("extruder_clearance_radius");
     const ConfigOptionPoints* bed_shape = printer_config->option<ConfigOptionPoints>("bed_shape");

+ 14 - 1
src/slic3r/GUI/CalibrationFlowDialog.cpp

@@ -92,6 +92,17 @@ void CalibrationFlowDialog::create_geometry(float start, float delta) {
     The 0.3 constant is the same as the delta calculated in add_part below, this should probably be calculated per the model object
     */
     float zshift = -(zscale / 2) + ((first_layer_height + layer_height) / 2) + 0.3;
+    
+    // it's rotated but not around the good origin: correct that
+    double init_z_rotate_angle = Geometry::deg2rad(plat->config()->opt_float("init_z_rotate"));
+    Matrix3d rot_matrix = Eigen::Quaterniond(Eigen::AngleAxisd(init_z_rotate_angle, Vec3d{0,0,1})).toRotationMatrix();
+    auto     translate_from_rotation = [&rot_matrix, &model, &objs_idx](int idx, Vec3d &&translation) {
+            ModelVolume *vol = model.objects[objs_idx[idx]]->volumes[model.objects[objs_idx[idx]]->volumes.size()-1];
+            Geometry::Transformation trsf = vol->get_transformation();
+            trsf.set_offset(rot_matrix * translation - translation + trsf.get_offset());
+            vol->set_transformation(trsf);
+        };
+    
     if (delta == 10.f && start == 80.f) {
         add_part(model.objects[objs_idx[0]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "filament_flow" / "m20.amf").string(), Vec3d{ 10 * xyScale,0,zshift }, Vec3d{ xyScale , xyScale, zscale_number});
         add_part(model.objects[objs_idx[1]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "filament_flow" / "m10.amf").string(), Vec3d{ 10 * xyScale,0,zshift }, Vec3d{ xyScale , xyScale, zscale_number });
@@ -106,11 +117,13 @@ void CalibrationFlowDialog::create_geometry(float start, float delta) {
         add_part(model.objects[objs_idx[4]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "filament_flow" / "_0.amf").string(), Vec3d{ 10 * xyScale,0,zshift }, Vec3d{ xyScale , xyScale, zscale_number});
     }
     for (size_t i = 0; i < 5; i++) {
+        translate_from_rotation(i, Vec3d{ 10 * xyScale,0,zshift });
         add_part(model.objects[objs_idx[i]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "filament_flow" / "O.amf").string(), Vec3d{ 0,0,zscale/2.f + 0.5 }, Vec3d{xyScale , xyScale, layer_height / 0.2}); // base: 0.2mm height
     }
 
+    
     /// --- translate ---;
-    bool has_to_arrange = false;
+    bool has_to_arrange = init_z_rotate_angle != 0;
     const ConfigOptionFloat* extruder_clearance_radius = print_config->option<ConfigOptionFloat>("extruder_clearance_radius");
     const ConfigOptionPoints* bed_shape = printerConfig->option<ConfigOptionPoints>("bed_shape");
     const double brim_width = nozzle_diameter * 3.5;

+ 13 - 2
src/slic3r/GUI/CalibrationOverBridgeDialog.cpp

@@ -79,6 +79,16 @@ void CalibrationOverBridgeDialog::create_geometry(bool over_bridge) {
     }
     for (size_t i = 0; i < 6; i++)
         model.objects[objs_idx[i]]->scale(xyz_scale * 1.5f, xyz_scale * 1.5f, xyz_scale);
+    
+    // it's rotated but not around the good origin: correct that
+    double init_z_rotate_angle = Geometry::deg2rad(plat->config()->opt_float("init_z_rotate"));
+    Matrix3d rot_matrix = Eigen::Quaterniond(Eigen::AngleAxisd(init_z_rotate_angle, Vec3d{0,0,1})).toRotationMatrix();
+    auto     translate_from_rotation = [&rot_matrix, &model, &objs_idx](int idx, Vec3d &&translation) {
+            ModelVolume *vol = model.objects[objs_idx[idx]]->volumes[model.objects[objs_idx[idx]]->volumes.size()-1];
+            Geometry::Transformation trsf = vol->get_transformation();
+            trsf.set_offset(rot_matrix * translation - translation + trsf.get_offset());
+            vol->set_transformation(trsf);
+        };
 
     //add sub-part after scale
     const ConfigOptionFloatOrPercent* first_layer_height = print_config->option<ConfigOptionFloatOrPercent>("first_layer_height");
@@ -86,11 +96,12 @@ void CalibrationOverBridgeDialog::create_geometry(bool over_bridge) {
     float zshift =  0.8 * (1 - xyz_scale);
     for (size_t i = 0; i < 6; i++) {
         model.objects[objs_idx[i]]->rotate(PI / 2, { 0,0,1 });
-        ModelObject* obj = add_part(model.objects[objs_idx[i]], (boost::filesystem::path(Slic3r::resources_dir()) /"calibration" / "bridge_flow" / ("f"+std::to_string(100 + i * 5)+".amf")).string(), Vec3d{ 0, 10 * xyz_scale ,zshift }, Vec3d{ 1, 1, patch_zscale });
+        add_part(model.objects[objs_idx[i]], (boost::filesystem::path(Slic3r::resources_dir()) /"calibration" / "bridge_flow" / ("f"+std::to_string(100 + i * 5)+".amf")).string(), Vec3d{ 0, 10 * xyz_scale ,zshift }, Vec3d{ 1, 1, patch_zscale });
+            translate_from_rotation(i, Vec3d{ 0, 10 * xyz_scale ,zshift });
     }
 
     /// --- translate ---;
-    bool has_to_arrange = false;
+    bool has_to_arrange = init_z_rotate_angle != 0;
     const ConfigOptionFloat* extruder_clearance_radius = print_config->option<ConfigOptionFloat>("extruder_clearance_radius");
     const ConfigOptionPoints* bed_shape = printer_config->option<ConfigOptionPoints>("bed_shape");
     const float brim_width = print_config->option<ConfigOptionFloat>("brim_width")->get_float();

+ 32 - 30
src/slic3r/GUI/CalibrationRetractionDialog.cpp

@@ -176,27 +176,28 @@ void CalibrationRetractionDialog::create_geometry(wxCommandEvent& event_args) {
 
     //add sub-part after scale
     float zscale_number = (first_layer_height + layer_height) / 0.4;
-    std::vector < std::vector<ModelObject*>> part_tower;
     std::vector<std::string> filament_temp_item_name;
     for (size_t id_item = 0; id_item < nb_items; id_item++) {
-        part_tower.emplace_back();
         int mytemp = temp - temp_decr * id_item;
         if (mytemp <= 285 && mytemp >= 180 && mytemp % 5 == 0) {
             filament_temp_item_name.push_back("t" + std::to_string(mytemp) + ".amf");
+            assert(model.objects[objs_idx[id_item]]->volumes.size() == 1);
             add_part(model.objects[objs_idx[id_item]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "filament_temp" / filament_temp_item_name.back()).string(),
                 Vec3d{ 0,0, scale * 0.0 - 4.8 }, Vec3d{ scale,scale,scale });
+            assert(model.objects[objs_idx[id_item]]->volumes.size() == 2);
             model.objects[objs_idx[id_item]]->volumes[1]->rotate(PI / 2, Vec3d(0, 0, 1));
             model.objects[objs_idx[id_item]]->volumes[1]->rotate(-PI / 2, Vec3d(1, 0, 0));
+            //model.objects[objs_idx[id_item]]->volumes[1]->rotate(Geometry::deg2rad(plat->config()->opt_float("init_z_rotate")), Axis::Z);
         }
         for (int num_retract = 0; num_retract < nb_retract; num_retract++) {
-            part_tower.back().push_back(add_part(model.objects[objs_idx[id_item]], 
+            add_part(model.objects[objs_idx[id_item]], 
                 (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "retraction" / "retraction_calibration_pillar.amf").string(),
-                Vec3d{ 0,0,scale * 0.7 - 0.3 + scale * num_retract }, Vec3d{ scale,scale,scale }));
+                Vec3d{ 0,0,scale * 0.7 - 0.3 + scale * num_retract }, Vec3d{ scale,scale,scale });
         }
     }
 
     /// --- translate ---;
-    bool has_to_arrange = false;
+    bool has_to_arrange = plat->config()->opt_float("init_z_rotate") != 0;
     const ConfigOptionFloat* extruder_clearance_radius = print_config->option<ConfigOptionFloat>("extruder_clearance_radius");
     const ConfigOptionPoints* bed_shape = printer_config->option<ConfigOptionPoints>("bed_shape");
     const float brim_width = std::max(print_config->option<ConfigOptionFloat>("brim_width")->value, nozzle_diameter * 5.);
@@ -214,41 +215,42 @@ void CalibrationRetractionDialog::create_geometry(wxCommandEvent& event_args) {
     assert(filament_temp_item_name.size() == nb_items);
     assert(model.objects.size() == nb_items);
     for (size_t i = 0; i < nb_items; i++) {
+        ModelObject *current_obj = model.objects[objs_idx[i]];
         //speed
         double perimeter_speed = full_print_config.get_computed_value("perimeter_speed");
         double external_perimeter_speed = full_print_config.get_computed_value("external_perimeter_speed");
         //brim to have some time to build up pressure in the nozzle
-        model.objects[objs_idx[i]]->config.set_key_value("brim_width", new ConfigOptionFloat(0));
-        model.objects[objs_idx[i]]->config.set_key_value("perimeters", new ConfigOptionInt(2));
-        model.objects[objs_idx[i]]->config.set_key_value("external_perimeters_first", new ConfigOptionBool(false));
-        model.objects[objs_idx[i]]->config.set_key_value("bottom_solid_layers", new ConfigOptionInt(0));
-        for(auto& volume : model.objects[objs_idx[i]]->volumes)
+        current_obj->config.set_key_value("brim_width", new ConfigOptionFloat(0));
+        current_obj->config.set_key_value("perimeters", new ConfigOptionInt(2));
+        current_obj->config.set_key_value("external_perimeters_first", new ConfigOptionBool(false));
+        current_obj->config.set_key_value("bottom_solid_layers", new ConfigOptionInt(0));
+        for(auto& volume : current_obj->volumes)
             if( volume->name == filament_temp_item_name[i] || volume->name.empty()) // if temperature patch or the main retraction patch (empty name because it's the initial volume)
                 volume->config.set_key_value("bottom_solid_layers", new ConfigOptionInt(2));
-        model.objects[objs_idx[i]]->config.set_key_value("top_solid_layers", new ConfigOptionInt(0));
-        model.objects[objs_idx[i]]->config.set_key_value("fill_density", new ConfigOptionPercent(0));
-        //model.objects[objs_idx[i]]->config.set_key_value("fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinear));
-        model.objects[objs_idx[i]]->config.set_key_value("only_one_perimeter_top", new ConfigOptionBool(false));
-        model.objects[objs_idx[i]]->config.set_key_value("overhangs_width_speed", new ConfigOptionFloatOrPercent(0,false));
-        model.objects[objs_idx[i]]->config.set_key_value("thin_walls", new ConfigOptionBool(true));
-        model.objects[objs_idx[i]]->config.set_key_value("thin_walls_min_width", new ConfigOptionFloatOrPercent(2,true));
-        model.objects[objs_idx[i]]->config.set_key_value("gap_fill_enabled", new ConfigOptionBool(false));
-        model.objects[objs_idx[i]]->config.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(nozzle_diameter / 2., false));
-        model.objects[objs_idx[i]]->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter / 2.));
+        current_obj->config.set_key_value("top_solid_layers", new ConfigOptionInt(0));
+        current_obj->config.set_key_value("fill_density", new ConfigOptionPercent(0));
+        //current_obj->config.set_key_value("fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinear));
+        current_obj->config.set_key_value("only_one_perimeter_top", new ConfigOptionBool(false));
+        current_obj->config.set_key_value("overhangs_width_speed", new ConfigOptionFloatOrPercent(0,false));
+        current_obj->config.set_key_value("thin_walls", new ConfigOptionBool(true));
+        current_obj->config.set_key_value("thin_walls_min_width", new ConfigOptionFloatOrPercent(2,true));
+        current_obj->config.set_key_value("gap_fill_enabled", new ConfigOptionBool(false));
+        current_obj->config.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(nozzle_diameter / 2., false));
+        current_obj->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter / 2.));
         //temp
-        model.objects[objs_idx[i]]->config.set_key_value("print_temperature", new ConfigOptionInt(int(temp - temp_decr * i)));
+        current_obj->config.set_key_value("print_temperature", new ConfigOptionInt(int(temp - temp_decr * i)));
         //set retraction override
-        size_t num_part = 0;
+        
         const int mytemp = temp - temp_decr * i;
         const int extra_vol = (mytemp <= 285 && mytemp >= 180 && mytemp % 5 == 0) ? 2 : 1;
-        for (ModelObject* part : part_tower[i]) {
-            model.objects[objs_idx[i]]->volumes[num_part + extra_vol]->config.set_key_value("print_retract_length", new ConfigOptionFloat(retraction_start + num_part * retraction_steps));
-            model.objects[objs_idx[i]]->volumes[num_part + extra_vol]->config.set_key_value("small_perimeter_speed", new ConfigOptionFloatOrPercent(external_perimeter_speed, false));
-            model.objects[objs_idx[i]]->volumes[num_part + extra_vol]->config.set_key_value("perimeter_speed", new ConfigOptionFloatOrPercent(std::min(external_perimeter_speed, perimeter_speed), false));
-            model.objects[objs_idx[i]]->volumes[num_part + extra_vol]->config.set_key_value("external_perimeter_speed", new ConfigOptionFloatOrPercent(external_perimeter_speed, false));
-            //model.objects[objs_idx[i]]->volumes[num_part + extra_vol]->config.set_key_value("small_perimeter_speed", new ConfigOptionFloatOrPercent(external_perimeter_speed, false));
-            //model.objects[objs_idx[i]]->volumes[num_part + extra_vol]->config.set_key_value("infill_speed", new ConfigOptionFloatOrPercent(std::min(print_config->option<ConfigOptionFloatOrPercent>("infill_speed")->value, 10.*scale)), false);
-            num_part++;
+        for (size_t num_part = extra_vol; num_part < current_obj->volumes.size(); num_part++) {
+            current_obj->volumes[num_part]->config.set_key_value("print_retract_length", new ConfigOptionFloat(retraction_start + num_part * retraction_steps));
+            current_obj->volumes[num_part]->config.set_key_value("small_perimeter_speed", new ConfigOptionFloatOrPercent(external_perimeter_speed, false));
+            current_obj->volumes[num_part]->config.set_key_value("perimeter_speed", new ConfigOptionFloatOrPercent(std::min(external_perimeter_speed, perimeter_speed), false));
+            current_obj->volumes[num_part]->config.set_key_value("external_perimeter_speed", new ConfigOptionFloatOrPercent(external_perimeter_speed, false));
+            //current_obj->volumes[num_part + extra_vol]->config.set_key_value("small_perimeter_speed", new ConfigOptionFloatOrPercent(external_perimeter_speed, false));
+            //current_obj->volumes[num_part + extra_vol]->config.set_key_value("infill_speed", new ConfigOptionFloatOrPercent(std::min(print_config->option<ConfigOptionFloatOrPercent>("infill_speed")->value, 10.*scale)), false);
+            
         }
     }
 

+ 6 - 8
src/slic3r/GUI/CalibrationTempDialog.cpp

@@ -104,21 +104,19 @@ void CalibrationTempDialog::create_geometry(wxCommandEvent& event_args) {
     }
 
     //add 8 others
-    std::vector<ModelObject*>tower;
-    tower.push_back(model.objects[objs_idx[0]]);
     float zshift = (1 - xyzScale) / 2;
     if (temperature > 175 && temperature < 290 && temperature%5==0) {
-        tower.push_back(add_part(model.objects[objs_idx[0]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "filament_temp" / ("t"+std::to_string(temperature)+".amf")).string(),
+        add_part(model.objects[objs_idx[0]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "filament_temp" / ("t"+std::to_string(temperature)+".amf")).string(),
             //Vec3d{ xyzScale * 5, - xyzScale * 2.5, zshift - xyzScale * 2.5}, Vec3d{ xyzScale, xyzScale, xyzScale * 0.43 }));
-            Vec3d{ 8 - xyzScale * 5, -xyzScale * 2.3, xyzScale * (0 * 10 - 2.45) }, Vec3d{ xyzScale, xyzScale, xyzScale * 0.43 }));
+            Vec3d{ 8 - xyzScale * 5, -xyzScale * 2.3, xyzScale * (0 * 10 - 2.45) }, Vec3d{ xyzScale, xyzScale, xyzScale * 0.43 });
     }
     for (int16_t i = 1; i < nb_items; i++) {
-        tower.push_back(add_part(model.objects[objs_idx[0]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "filament_temp" / ("Smart_compact_temperature_calibration_item.amf")).string(),
-            Vec3d{ 0,0, i * 10 * xyzScale }, Vec3d{ xyzScale, xyzScale * 0.5, xyzScale }));
+        add_part(model.objects[objs_idx[0]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "filament_temp" / ("Smart_compact_temperature_calibration_item.amf")).string(),
+            Vec3d{ 0,0, i * 10 * xyzScale }, Vec3d{ xyzScale, xyzScale * 0.5, xyzScale });
         int sub_temp = temperature - i * step_temp;
         if (sub_temp > 175 && sub_temp < 290 && sub_temp % 5 == 0) {
-            tower.push_back(add_part(model.objects[objs_idx[0]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "filament_temp" / ("t" + std::to_string(sub_temp) + ".amf")).string(),
-                Vec3d{ 8 - xyzScale * 5, -xyzScale * 2.3, xyzScale * (i * 10 - 2.5) }, Vec3d{ xyzScale, xyzScale, xyzScale * 0.43 }));
+            add_part(model.objects[objs_idx[0]], (boost::filesystem::path(Slic3r::resources_dir()) / "calibration" / "filament_temp" / ("t" + std::to_string(sub_temp) + ".amf")).string(),
+                Vec3d{ 8 - xyzScale * 5, -xyzScale * 2.3, xyzScale * (i * 10 - 2.5) }, Vec3d{ xyzScale, xyzScale, xyzScale * 0.43 });
         }
     }