|
|
@ -363,6 +363,7 @@ bool target_direction;
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SCARA
|
|
|
|
#ifdef SCARA
|
|
|
|
|
|
|
|
float delta_segments_per_second = SCARA_SEGMENTS_PER_SECOND;
|
|
|
|
static float delta[3] = { 0 };
|
|
|
|
static float delta[3] = { 0 };
|
|
|
|
float axis_scaling[3] = { 1, 1, 1 }; // Build size scaling, default to 1
|
|
|
|
float axis_scaling[3] = { 1, 1, 1 }; // Build size scaling, default to 1
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
@ -1712,7 +1713,7 @@ static void homeaxis(AxisEnum axis) {
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef FWRETRACT
|
|
|
|
#ifdef FWRETRACT
|
|
|
|
|
|
|
|
|
|
|
|
void retract(bool retracting, bool swapretract = false) {
|
|
|
|
void retract(bool retracting, bool swapping=false) {
|
|
|
|
|
|
|
|
|
|
|
|
if (retracting == retracted[active_extruder]) return;
|
|
|
|
if (retracting == retracted[active_extruder]) return;
|
|
|
|
|
|
|
|
|
|
|
@ -1723,7 +1724,7 @@ static void homeaxis(AxisEnum axis) {
|
|
|
|
if (retracting) {
|
|
|
|
if (retracting) {
|
|
|
|
|
|
|
|
|
|
|
|
feedrate = retract_feedrate * 60;
|
|
|
|
feedrate = retract_feedrate * 60;
|
|
|
|
current_position[E_AXIS] += (swapretract ? retract_length_swap : retract_length) / volumetric_multiplier[active_extruder];
|
|
|
|
current_position[E_AXIS] += (swapping ? retract_length_swap : retract_length) / volumetric_multiplier[active_extruder];
|
|
|
|
plan_set_e_position(current_position[E_AXIS]);
|
|
|
|
plan_set_e_position(current_position[E_AXIS]);
|
|
|
|
prepare_move();
|
|
|
|
prepare_move();
|
|
|
|
|
|
|
|
|
|
|
@ -1750,7 +1751,7 @@ static void homeaxis(AxisEnum axis) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
feedrate = retract_recover_feedrate * 60;
|
|
|
|
feedrate = retract_recover_feedrate * 60;
|
|
|
|
float move_e = swapretract ? retract_length_swap + retract_recover_length_swap : retract_length + retract_recover_length;
|
|
|
|
float move_e = swapping ? retract_length_swap + retract_recover_length_swap : retract_length + retract_recover_length;
|
|
|
|
current_position[E_AXIS] -= move_e / volumetric_multiplier[active_extruder];
|
|
|
|
current_position[E_AXIS] -= move_e / volumetric_multiplier[active_extruder];
|
|
|
|
plan_set_e_position(current_position[E_AXIS]);
|
|
|
|
plan_set_e_position(current_position[E_AXIS]);
|
|
|
|
prepare_move();
|
|
|
|
prepare_move();
|
|
|
@ -1792,7 +1793,7 @@ inline void gcode_G0_G1() {
|
|
|
|
#endif //FWRETRACT
|
|
|
|
#endif //FWRETRACT
|
|
|
|
|
|
|
|
|
|
|
|
prepare_move();
|
|
|
|
prepare_move();
|
|
|
|
//ClearToSend();
|
|
|
|
//ok_to_send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -1814,7 +1815,7 @@ inline void gcode_G4() {
|
|
|
|
millis_t codenum = 0;
|
|
|
|
millis_t codenum = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (code_seen('P')) codenum = code_value_long(); // milliseconds to wait
|
|
|
|
if (code_seen('P')) codenum = code_value_long(); // milliseconds to wait
|
|
|
|
if (code_seen('S')) codenum = code_value_long() * 1000; // seconds to wait
|
|
|
|
if (code_seen('S')) codenum = code_value() * 1000; // seconds to wait
|
|
|
|
|
|
|
|
|
|
|
|
st_synchronize();
|
|
|
|
st_synchronize();
|
|
|
|
refresh_cmd_timeout();
|
|
|
|
refresh_cmd_timeout();
|
|
|
@ -2681,7 +2682,7 @@ inline void gcode_G92() {
|
|
|
|
hasP = codenum > 0;
|
|
|
|
hasP = codenum > 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (code_seen('S')) {
|
|
|
|
if (code_seen('S')) {
|
|
|
|
codenum = code_value_short() * 1000UL; // seconds to wait
|
|
|
|
codenum = code_value() * 1000; // seconds to wait
|
|
|
|
hasS = codenum > 0;
|
|
|
|
hasS = codenum > 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
char* starpos = strchr(src, '*');
|
|
|
|
char* starpos = strchr(src, '*');
|
|
|
@ -4314,7 +4315,7 @@ inline void gcode_M303() {
|
|
|
|
destination[X_AXIS] = delta[X_AXIS]/axis_scaling[X_AXIS];
|
|
|
|
destination[X_AXIS] = delta[X_AXIS]/axis_scaling[X_AXIS];
|
|
|
|
destination[Y_AXIS] = delta[Y_AXIS]/axis_scaling[Y_AXIS];
|
|
|
|
destination[Y_AXIS] = delta[Y_AXIS]/axis_scaling[Y_AXIS];
|
|
|
|
prepare_move();
|
|
|
|
prepare_move();
|
|
|
|
//ClearToSend();
|
|
|
|
//ok_to_send();
|
|
|
|
return true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
return false;
|
|
|
@ -5537,7 +5538,7 @@ void process_commands() {
|
|
|
|
SERIAL_ECHOLNPGM("\"");
|
|
|
|
SERIAL_ECHOLNPGM("\"");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ClearToSend();
|
|
|
|
ok_to_send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FlushSerialRequestResend() {
|
|
|
|
void FlushSerialRequestResend() {
|
|
|
@ -5545,10 +5546,10 @@ void FlushSerialRequestResend() {
|
|
|
|
MYSERIAL.flush();
|
|
|
|
MYSERIAL.flush();
|
|
|
|
SERIAL_PROTOCOLPGM(MSG_RESEND);
|
|
|
|
SERIAL_PROTOCOLPGM(MSG_RESEND);
|
|
|
|
SERIAL_PROTOCOLLN(gcode_LastN + 1);
|
|
|
|
SERIAL_PROTOCOLLN(gcode_LastN + 1);
|
|
|
|
ClearToSend();
|
|
|
|
ok_to_send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ClearToSend() {
|
|
|
|
void ok_to_send() {
|
|
|
|
refresh_cmd_timeout();
|
|
|
|
refresh_cmd_timeout();
|
|
|
|
#ifdef SDSUPPORT
|
|
|
|
#ifdef SDSUPPORT
|
|
|
|
if (fromsd[cmd_queue_index_r]) return;
|
|
|
|
if (fromsd[cmd_queue_index_r]) return;
|
|
|
@ -5777,34 +5778,35 @@ void mesh_plan_buffer_line(float x, float y, float z, const float e, float feed_
|
|
|
|
|
|
|
|
|
|
|
|
#endif // PREVENT_DANGEROUS_EXTRUDE
|
|
|
|
#endif // PREVENT_DANGEROUS_EXTRUDE
|
|
|
|
|
|
|
|
|
|
|
|
void prepare_move() {
|
|
|
|
#if defined(DELTA) || defined(SCARA)
|
|
|
|
clamp_to_software_endstops(destination);
|
|
|
|
|
|
|
|
refresh_cmd_timeout();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef PREVENT_DANGEROUS_EXTRUDE
|
|
|
|
|
|
|
|
(void)prevent_dangerous_extrude(current_position[E_AXIS], destination[E_AXIS]);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SCARA //for now same as delta-code
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inline bool prepare_move_delta() {
|
|
|
|
float difference[NUM_AXIS];
|
|
|
|
float difference[NUM_AXIS];
|
|
|
|
for (int8_t i = 0; i < NUM_AXIS; i++) difference[i] = destination[i] - current_position[i];
|
|
|
|
for (int8_t i=0; i < NUM_AXIS; i++) difference[i] = destination[i] - current_position[i];
|
|
|
|
|
|
|
|
|
|
|
|
float cartesian_mm = sqrt(sq(difference[X_AXIS]) + sq(difference[Y_AXIS]) + sq(difference[Z_AXIS]));
|
|
|
|
float cartesian_mm = sqrt(sq(difference[X_AXIS]) + sq(difference[Y_AXIS]) + sq(difference[Z_AXIS]));
|
|
|
|
if (cartesian_mm < 0.000001) { cartesian_mm = abs(difference[E_AXIS]); }
|
|
|
|
if (cartesian_mm < 0.000001) cartesian_mm = abs(difference[E_AXIS]);
|
|
|
|
if (cartesian_mm < 0.000001) { return; }
|
|
|
|
if (cartesian_mm < 0.000001) return false;
|
|
|
|
float seconds = 6000 * cartesian_mm / feedrate / feedrate_multiplier;
|
|
|
|
float seconds = 6000 * cartesian_mm / feedrate / feedrate_multiplier;
|
|
|
|
int steps = max(1, int(scara_segments_per_second * seconds));
|
|
|
|
int steps = max(1, int(delta_segments_per_second * seconds));
|
|
|
|
|
|
|
|
|
|
|
|
//SERIAL_ECHOPGM("mm="); SERIAL_ECHO(cartesian_mm);
|
|
|
|
// SERIAL_ECHOPGM("mm="); SERIAL_ECHO(cartesian_mm);
|
|
|
|
//SERIAL_ECHOPGM(" seconds="); SERIAL_ECHO(seconds);
|
|
|
|
// SERIAL_ECHOPGM(" seconds="); SERIAL_ECHO(seconds);
|
|
|
|
//SERIAL_ECHOPGM(" steps="); SERIAL_ECHOLN(steps);
|
|
|
|
// SERIAL_ECHOPGM(" steps="); SERIAL_ECHOLN(steps);
|
|
|
|
|
|
|
|
|
|
|
|
for (int s = 1; s <= steps; s++) {
|
|
|
|
for (int s = 1; s <= steps; s++) {
|
|
|
|
|
|
|
|
|
|
|
|
float fraction = float(s) / float(steps);
|
|
|
|
float fraction = float(s) / float(steps);
|
|
|
|
for (int8_t i = 0; i < NUM_AXIS; i++) destination[i] = current_position[i] + difference[i] * fraction;
|
|
|
|
|
|
|
|
|
|
|
|
for (int8_t i = 0; i < NUM_AXIS; i++)
|
|
|
|
|
|
|
|
destination[i] = current_position[i] + difference[i] * fraction;
|
|
|
|
|
|
|
|
|
|
|
|
calculate_delta(destination);
|
|
|
|
calculate_delta(destination);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef ENABLE_AUTO_BED_LEVELING
|
|
|
|
|
|
|
|
adjust_delta(destination);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
//SERIAL_ECHOPGM("destination[X_AXIS]="); SERIAL_ECHOLN(destination[X_AXIS]);
|
|
|
|
//SERIAL_ECHOPGM("destination[X_AXIS]="); SERIAL_ECHOLN(destination[X_AXIS]);
|
|
|
|
//SERIAL_ECHOPGM("destination[Y_AXIS]="); SERIAL_ECHOLN(destination[Y_AXIS]);
|
|
|
|
//SERIAL_ECHOPGM("destination[Y_AXIS]="); SERIAL_ECHOLN(destination[Y_AXIS]);
|
|
|
|
//SERIAL_ECHOPGM("destination[Z_AXIS]="); SERIAL_ECHOLN(destination[Z_AXIS]);
|
|
|
|
//SERIAL_ECHOPGM("destination[Z_AXIS]="); SERIAL_ECHOLN(destination[Z_AXIS]);
|
|
|
@ -5814,37 +5816,18 @@ void prepare_move() {
|
|
|
|
|
|
|
|
|
|
|
|
plan_buffer_line(delta[X_AXIS], delta[Y_AXIS], delta[Z_AXIS], destination[E_AXIS], feedrate/60*feedrate_multiplier/100.0, active_extruder);
|
|
|
|
plan_buffer_line(delta[X_AXIS], delta[Y_AXIS], delta[Z_AXIS], destination[E_AXIS], feedrate/60*feedrate_multiplier/100.0, active_extruder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
#endif // SCARA
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef DELTA
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float difference[NUM_AXIS];
|
|
|
|
|
|
|
|
for (int8_t i=0; i < NUM_AXIS; i++) difference[i] = destination[i] - current_position[i];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float cartesian_mm = sqrt(sq(difference[X_AXIS]) + sq(difference[Y_AXIS]) + sq(difference[Z_AXIS]));
|
|
|
|
|
|
|
|
if (cartesian_mm < 0.000001) cartesian_mm = abs(difference[E_AXIS]);
|
|
|
|
|
|
|
|
if (cartesian_mm < 0.000001) return;
|
|
|
|
|
|
|
|
float seconds = 6000 * cartesian_mm / feedrate / feedrate_multiplier;
|
|
|
|
|
|
|
|
int steps = max(1, int(delta_segments_per_second * seconds));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SERIAL_ECHOPGM("mm="); SERIAL_ECHO(cartesian_mm);
|
|
|
|
|
|
|
|
// SERIAL_ECHOPGM(" seconds="); SERIAL_ECHO(seconds);
|
|
|
|
|
|
|
|
// SERIAL_ECHOPGM(" steps="); SERIAL_ECHOLN(steps);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int s = 1; s <= steps; s++) {
|
|
|
|
|
|
|
|
float fraction = float(s) / float(steps);
|
|
|
|
|
|
|
|
for (int8_t i = 0; i < NUM_AXIS; i++) destination[i] = current_position[i] + difference[i] * fraction;
|
|
|
|
|
|
|
|
calculate_delta(destination);
|
|
|
|
|
|
|
|
#ifdef ENABLE_AUTO_BED_LEVELING
|
|
|
|
|
|
|
|
adjust_delta(destination);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
plan_buffer_line(delta[X_AXIS], delta[Y_AXIS], delta[Z_AXIS], destination[E_AXIS], feedrate/60*feedrate_multiplier/100.0, active_extruder);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endif // DELTA
|
|
|
|
#endif // DELTA || SCARA
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef DUAL_X_CARRIAGE
|
|
|
|
#ifdef SCARA
|
|
|
|
|
|
|
|
inline bool prepare_move_scara() { return prepare_move_delta(); }
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef DUAL_X_CARRIAGE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inline bool prepare_move_dual_x_carriage() {
|
|
|
|
if (active_extruder_parked) {
|
|
|
|
if (active_extruder_parked) {
|
|
|
|
if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && active_extruder == 0) {
|
|
|
|
if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && active_extruder == 0) {
|
|
|
|
// move duplicate extruder into correct duplication position.
|
|
|
|
// move duplicate extruder into correct duplication position.
|
|
|
@ -5865,7 +5848,7 @@ void prepare_move() {
|
|
|
|
set_current_to_destination();
|
|
|
|
set_current_to_destination();
|
|
|
|
NOLESS(raised_parked_position[Z_AXIS], destination[Z_AXIS]);
|
|
|
|
NOLESS(raised_parked_position[Z_AXIS], destination[Z_AXIS]);
|
|
|
|
delayed_move_time = millis();
|
|
|
|
delayed_move_time = millis();
|
|
|
|
return;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delayed_move_time = 0;
|
|
|
|
delayed_move_time = 0;
|
|
|
@ -5876,9 +5859,14 @@ void prepare_move() {
|
|
|
|
active_extruder_parked = false;
|
|
|
|
active_extruder_parked = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // DUAL_X_CARRIAGE
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if !defined(DELTA) && !defined(SCARA)
|
|
|
|
#endif // DUAL_X_CARRIAGE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if !defined(DELTA) && !defined(SCARA)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inline bool prepare_move_cartesian() {
|
|
|
|
// Do not use feedrate_multiplier for E or Z only moves
|
|
|
|
// Do not use feedrate_multiplier for E or Z only moves
|
|
|
|
if (current_position[X_AXIS] == destination[X_AXIS] && current_position[Y_AXIS] == destination[Y_AXIS]) {
|
|
|
|
if (current_position[X_AXIS] == destination[X_AXIS] && current_position[Y_AXIS] == destination[Y_AXIS]) {
|
|
|
|
line_to_destination();
|
|
|
|
line_to_destination();
|
|
|
@ -5886,12 +5874,40 @@ void prepare_move() {
|
|
|
|
else {
|
|
|
|
else {
|
|
|
|
#ifdef MESH_BED_LEVELING
|
|
|
|
#ifdef MESH_BED_LEVELING
|
|
|
|
mesh_plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], (feedrate/60)*(feedrate_multiplier/100.0), active_extruder);
|
|
|
|
mesh_plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], (feedrate/60)*(feedrate_multiplier/100.0), active_extruder);
|
|
|
|
return;
|
|
|
|
return false;
|
|
|
|
#else
|
|
|
|
#else
|
|
|
|
line_to_destination(feedrate * feedrate_multiplier / 100.0);
|
|
|
|
line_to_destination(feedrate * feedrate_multiplier / 100.0);
|
|
|
|
#endif // MESH_BED_LEVELING
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // !(DELTA || SCARA)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endif // !DELTA && !SCARA
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Prepare a single move and get ready for the next one
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
void prepare_move() {
|
|
|
|
|
|
|
|
clamp_to_software_endstops(destination);
|
|
|
|
|
|
|
|
refresh_cmd_timeout();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef PREVENT_DANGEROUS_EXTRUDE
|
|
|
|
|
|
|
|
prevent_dangerous_extrude(current_position[E_AXIS], destination[E_AXIS]);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SCARA
|
|
|
|
|
|
|
|
if (!prepare_move_scara()) return;
|
|
|
|
|
|
|
|
#elif defined(DELTA)
|
|
|
|
|
|
|
|
if (!prepare_move_delta()) return;
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef DUAL_X_CARRIAGE
|
|
|
|
|
|
|
|
if (!prepare_move_dual_x_carriage()) return;
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if !defined(DELTA) && !defined(SCARA)
|
|
|
|
|
|
|
|
if (!prepare_move_cartesian()) return;
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
set_current_to_destination();
|
|
|
|
set_current_to_destination();
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -5911,10 +5927,9 @@ void prepare_arc_move(char isclockwise) {
|
|
|
|
|
|
|
|
|
|
|
|
#if HAS_CONTROLLERFAN
|
|
|
|
#if HAS_CONTROLLERFAN
|
|
|
|
|
|
|
|
|
|
|
|
millis_t lastMotor = 0; // Last time a motor was turned on
|
|
|
|
void controllerFan() {
|
|
|
|
millis_t lastMotorCheck = 0; // Last time the state was checked
|
|
|
|
static millis_t lastMotor = 0; // Last time a motor was turned on
|
|
|
|
|
|
|
|
static millis_t lastMotorCheck = 0; // Last time the state was checked
|
|
|
|
void controllerFan() {
|
|
|
|
|
|
|
|
millis_t ms = millis();
|
|
|
|
millis_t ms = millis();
|
|
|
|
if (ms >= lastMotorCheck + 2500) { // Not a time critical function, so we only check every 2500ms
|
|
|
|
if (ms >= lastMotorCheck + 2500) { // Not a time critical function, so we only check every 2500ms
|
|
|
|
lastMotorCheck = ms;
|
|
|
|
lastMotorCheck = ms;
|
|
|
@ -5940,8 +5955,9 @@ void controllerFan() {
|
|
|
|
digitalWrite(CONTROLLERFAN_PIN, speed);
|
|
|
|
digitalWrite(CONTROLLERFAN_PIN, speed);
|
|
|
|
analogWrite(CONTROLLERFAN_PIN, speed);
|
|
|
|
analogWrite(CONTROLLERFAN_PIN, speed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#endif // HAS_CONTROLLERFAN
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef SCARA
|
|
|
|
#ifdef SCARA
|
|
|
|
void calculate_SCARA_forward_Transform(float f_scara[3])
|
|
|
|
void calculate_SCARA_forward_Transform(float f_scara[3])
|
|
|
|