mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
Planner: Improve Gas Handling in CCR Mode.
This has become a bit of a catch-all overhaul of a large portion of the planner - I started out wanting to improve the CCR mode, but then as I started pulling all the other threads that needed addressing started to come with it. Improve how the gas selection is handled when planning dives in CCR mode, by making the type (OC / CCR) of segments dependent on the gas use type that was set for the selected gas. Add a preference to allow the user to chose to use OC gases as diluent, in a similar fashion to the original implementation. Hide gases that cannot be used in the currently selected dive mode in all drop downs. Include usage type in gas names if this is needed. Hide columns and disable elements in the 'Dive planner points' table if they can they can not be edited in the curently selected dive mode. Visually identify gases and usage types that are not appropriate for the currently selected dive mode. Move the 'Dive mode' selection to the top of the planner view, to accommodate the fact that this is a property of the dive and not a planner setting. Show a warning instead of the dive plan if the plan contains gases that are not usable in the selected dive mode. Fix the data entry for the setpoint in the 'Dive planner points' table. Fix problems with enabling / disabling planner settings when switching between dive modes. Refactor some names to make them more appropriate for their current usage. One point that is still open is to hide gas usage graphs in the planner profile if the gas isn't used for OC, as there is no way to meaningfully interpolate such usage. Signed-off-by: Michael Keller <github@ike.ch>
This commit is contained in:
parent
7106c4d5f0
commit
2d8e343221
63 changed files with 678 additions and 383 deletions
|
@ -1625,6 +1625,35 @@ static bool cylinder_in_use(const struct dive *dive, int idx)
|
|||
return cylinder_has_data(dive->cylinders[idx]);
|
||||
}
|
||||
|
||||
bool is_cylinder_use_appropriate(const struct divecomputer &dc, const cylinder_t &cyl, bool allowNonUsable)
|
||||
{
|
||||
switch (cyl.cylinder_use) {
|
||||
case OC:
|
||||
if (dc.divemode == FREEDIVE)
|
||||
return false;
|
||||
|
||||
break;
|
||||
case OXYGEN:
|
||||
if (!allowNonUsable)
|
||||
return false;
|
||||
|
||||
case DILUENT:
|
||||
if (dc.divemode != CCR)
|
||||
return false;
|
||||
|
||||
break;
|
||||
case NOT_USED:
|
||||
if (!allowNonUsable)
|
||||
return false;
|
||||
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Merging cylinder information is non-trivial, because the two dive computers
|
||||
* may have different ideas of what the different cylinder indexing is.
|
||||
|
@ -2451,7 +2480,7 @@ int dive::mbar_to_depth(int mbar) const
|
|||
|
||||
if (!surface_pressure.mbar)
|
||||
surface_pressure.mbar = SURFACE_PRESSURE;
|
||||
|
||||
|
||||
return rel_mbar_to_depth(mbar - surface_pressure.mbar);
|
||||
}
|
||||
|
||||
|
|
|
@ -146,6 +146,7 @@ struct dive_or_trip {
|
|||
|
||||
extern void cylinder_renumber(struct dive &dive, int mapping[]);
|
||||
extern int same_gasmix_cylinder(const cylinder_t &cyl, int cylid, const struct dive *dive, bool check_unused);
|
||||
extern bool is_cylinder_use_appropriate(const struct divecomputer &dc, const cylinder_t &cyl, bool allowNonUsable);
|
||||
|
||||
/* Data stored when copying a dive */
|
||||
struct dive_paste_data {
|
||||
|
|
|
@ -35,7 +35,7 @@ struct divecomputer {
|
|||
pressure_t surface_pressure;
|
||||
enum divemode_t divemode = OC; // dive computer type: OC(default) or CCR
|
||||
uint8_t no_o2sensors = 0; // rebreathers: number of O2 sensors used
|
||||
int salinity = 0; // kg per 10000 l
|
||||
int salinity = 0; // kg per 10000 l
|
||||
std::string model, serial, fw_version;
|
||||
uint32_t deviceid = 0, diveid = 0;
|
||||
// Note: ve store samples, events and extra_data in std::vector<>s.
|
||||
|
|
|
@ -4,4 +4,6 @@
|
|||
|
||||
enum divemode_t {OC, CCR, PSCR, FREEDIVE, NUM_DIVEMODE, UNDEF_COMP_TYPE}; // Flags (Open-circuit and Closed-circuit-rebreather) for setting dive computer type
|
||||
|
||||
#define IS_REBREATHER_MODE(divemode) ((divemode) == CCR || (divemode) == PSCR)
|
||||
|
||||
#endif
|
||||
|
|
|
@ -380,14 +380,17 @@ static int setpoint_change(struct dive *dive, int cylinderid)
|
|||
}
|
||||
}
|
||||
|
||||
static std::vector<gaschanges> analyze_gaslist(struct diveplan *diveplan, struct dive *dive, int depth, int *asc_cylinder, bool ccr)
|
||||
static std::vector<gaschanges> analyze_gaslist(struct diveplan *diveplan, struct dive *dive, int dcNr, int depth, int *asc_cylinder, bool ccr, bool &inappropriate_cylinder_use)
|
||||
{
|
||||
size_t nr = 0;
|
||||
std::vector<gaschanges> gaschanges;
|
||||
struct divedatapoint *dp = diveplan->dp;
|
||||
struct divedatapoint *best_ascent_dp = NULL;
|
||||
bool total_time_zero = true;
|
||||
const divecomputer *dc = dive->get_dc(dcNr);
|
||||
while (dp) {
|
||||
inappropriate_cylinder_use = inappropriate_cylinder_use || !is_cylinder_use_appropriate(*dc, *dive->get_cylinder(dp->cylinderid), false);
|
||||
|
||||
if (dp->time == 0 && total_time_zero && (ccr == (bool) setpoint_change(dive, dp->cylinderid))) {
|
||||
if (dp->depth.mm <= depth) {
|
||||
int i = 0;
|
||||
|
@ -661,7 +664,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i
|
|||
bool o2break_next = false;
|
||||
int break_cylinder = -1, breakfrom_cylinder = 0;
|
||||
bool last_segment_min_switch = false;
|
||||
bool error = false;
|
||||
planner_error_t error = PLAN_OK;
|
||||
bool decodive = false;
|
||||
int first_stop_depth = 0;
|
||||
int laststoptime = timestep;
|
||||
|
@ -744,7 +747,11 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i
|
|||
|
||||
/* Find the gases available for deco */
|
||||
|
||||
std::vector<gaschanges> gaschanges = analyze_gaslist(diveplan, dive, depth, &best_first_ascend_cylinder, divemode == CCR && !prefs.dobailout);
|
||||
bool inappropriate_cylinder_use = false;
|
||||
std::vector<gaschanges> gaschanges = analyze_gaslist(diveplan, dive, dcNr, depth, &best_first_ascend_cylinder, divemode == CCR && !prefs.dobailout, inappropriate_cylinder_use);
|
||||
if (inappropriate_cylinder_use) {
|
||||
error = PLAN_ERROR_INAPPROPRIATE_GAS;
|
||||
}
|
||||
|
||||
/* Find the first potential decostopdepth above current depth */
|
||||
for (stopidx = 0; stopidx < decostoplevelcount; stopidx++)
|
||||
|
@ -880,7 +887,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i
|
|||
current_cylinder = 0;
|
||||
}
|
||||
reset_regression(ds);
|
||||
while (1) {
|
||||
while (error == PLAN_OK) {
|
||||
/* We will break out when we hit the surface */
|
||||
do {
|
||||
/* Ascend to next stop depth */
|
||||
|
@ -1005,7 +1012,8 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i
|
|||
laststoptime = new_clock - clock;
|
||||
/* Finish infinite deco */
|
||||
if (laststoptime >= 48 * 3600 && depth >= 6000) {
|
||||
error = true;
|
||||
error = PLAN_ERROR_TIMEOUT;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1070,7 +1078,7 @@ bool plan(struct deco_state *ds, struct diveplan *diveplan, struct dive *dive, i
|
|||
* if the ascent rate is slower, which is completely nonsensical.
|
||||
* Assume final ascent takes 20s, which is the time taken to ascend at 9m/min from 3m */
|
||||
ds->deco_time = clock - bottom_time - (M_OR_FT(3,10) * ( prefs.last_stop ? 2 : 1)) / last_ascend_rate + 20;
|
||||
} while (!is_final_plan);
|
||||
} while (!is_final_plan && error == PLAN_OK);
|
||||
decostoptable[decostopcounter].depth = 0;
|
||||
|
||||
plan_add_segment(diveplan, clock - previous_point_time, 0, current_cylinder, po2, false, divemode);
|
||||
|
@ -1112,24 +1120,31 @@ static int get_decimals(const char *begin, const char **endp, const unsigned dec
|
|||
return -1;
|
||||
|
||||
/* Fraction? We only look at the first digit */
|
||||
if (*end == '.') {
|
||||
unsigned fraction = 0;
|
||||
for (unsigned i = 0; i < decimals; i++) {
|
||||
value *= 10;
|
||||
if (*end == '.')
|
||||
end++;
|
||||
|
||||
unsigned fraction = 0;
|
||||
for (unsigned i = 0; i < decimals; i++) {
|
||||
value *= 10;
|
||||
|
||||
unsigned digit = 0;
|
||||
if (isdigit(*end)) {
|
||||
digit = *end - '0';
|
||||
|
||||
end++;
|
||||
|
||||
if (!isdigit(*end))
|
||||
return -1;
|
||||
|
||||
fraction = 10 * fraction + (*end - '0');
|
||||
} else if (*end != '\0') {
|
||||
return -1;
|
||||
}
|
||||
value += fraction;
|
||||
|
||||
do {
|
||||
end++;
|
||||
} while (isdigit(*end));
|
||||
fraction = 10 * fraction + digit;
|
||||
|
||||
}
|
||||
value += fraction;
|
||||
|
||||
do {
|
||||
end++;
|
||||
} while (isdigit(*end));
|
||||
|
||||
*endp = end;
|
||||
return value;
|
||||
}
|
||||
|
@ -1209,5 +1224,9 @@ int validate_po2(const char *text, int *mbar_po2)
|
|||
return 0;
|
||||
|
||||
*mbar_po2 = po2 * 10;
|
||||
|
||||
if (*mbar_po2 < 160)
|
||||
*mbar_po2 = 160;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -34,11 +34,17 @@ struct diveplan {
|
|||
|
||||
struct deco_state_cache;
|
||||
|
||||
typedef enum {
|
||||
PLAN_OK,
|
||||
PLAN_ERROR_TIMEOUT,
|
||||
PLAN_ERROR_INAPPROPRIATE_GAS,
|
||||
} planner_error_t;
|
||||
|
||||
extern int validate_gas(const char *text, struct gasmix *gas);
|
||||
extern int validate_po2(const char *text, int *mbar_po2);
|
||||
extern int get_cylinderid_at_time(struct dive *dive, struct divecomputer *dc, duration_t time);
|
||||
extern bool diveplan_empty(struct diveplan *diveplan);
|
||||
extern void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, bool error);
|
||||
extern void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, planner_error_t error);
|
||||
extern const char *get_planner_disclaimer();
|
||||
|
||||
extern void free_dps(struct diveplan *diveplan);
|
||||
|
|
|
@ -96,7 +96,7 @@ extern std::string get_planner_disclaimer_formatted()
|
|||
return format_string_std(get_planner_disclaimer(), deco);
|
||||
}
|
||||
|
||||
void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, bool error)
|
||||
void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, planner_error_t error)
|
||||
{
|
||||
std::string buf;
|
||||
std::string icdbuf;
|
||||
|
@ -122,12 +122,31 @@ void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_d
|
|||
if (!dp)
|
||||
return;
|
||||
|
||||
if (error) {
|
||||
if (error != PLAN_OK) {
|
||||
const char *message;
|
||||
switch (error) {
|
||||
case PLAN_ERROR_TIMEOUT:
|
||||
message = translate("gettextFromC", "Decompression calculation aborted due to excessive time");
|
||||
|
||||
break;
|
||||
case PLAN_ERROR_INAPPROPRIATE_GAS:
|
||||
message = translate("gettextFromC", "One or more tanks with a tank use type inappropriate for the selected dive mode are included in the dive plan. "
|
||||
"Please change them to appropriate tanks to enable the generation of a dive plan.");
|
||||
|
||||
break;
|
||||
default:
|
||||
message = translate("gettextFromC", "An error occurred during dive plan generation");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
buf += format_string_std("<span style='color: red;'>%s </span> %s<br/>",
|
||||
translate("gettextFromC", "Warning:"),
|
||||
translate("gettextFromC", "Decompression calculation aborted due to excessive time"));
|
||||
translate("gettextFromC", "Warning:"), message);
|
||||
|
||||
// TODO: avoid copy
|
||||
dive->notes = buf;
|
||||
dive->notes = strdup(buf.c_str());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ preferences::preferences() :
|
|||
vpmb_conservatism(3),
|
||||
zoomed_plot(false),
|
||||
infobox(true),
|
||||
allowOcGasAsDiluent(false),
|
||||
coordinates_traditional(true),
|
||||
unit_system(METRIC),
|
||||
units(SI_UNITS)
|
||||
|
|
|
@ -201,6 +201,7 @@ struct preferences {
|
|||
int vpmb_conservatism;
|
||||
bool zoomed_plot;
|
||||
bool infobox;
|
||||
bool allowOcGasAsDiluent;
|
||||
|
||||
// ********** Units **********
|
||||
bool coordinates_traditional;
|
||||
|
|
|
@ -1151,19 +1151,51 @@ QString get_gas_string(struct gasmix gas)
|
|||
return result;
|
||||
}
|
||||
|
||||
QStringList get_dive_gas_list(const struct dive *d)
|
||||
QString get_dive_gas(const struct dive *d, int dcNr, int cylinderId)
|
||||
{
|
||||
QStringList list;
|
||||
for (auto [i, cyl]: enumerated_range(d->cylinders)) {
|
||||
/* Check if we have the same gasmix two or more times
|
||||
* If yes return more verbose string */
|
||||
int same_gas = same_gasmix_cylinder(cyl, i, d, true);
|
||||
if (same_gas == -1)
|
||||
list.push_back(get_gas_string(cyl.gasmix));
|
||||
else
|
||||
list.push_back(get_gas_string(cyl.gasmix) + QStringLiteral(" (%1 %2 ").arg(gettextFromC::tr("cyl.")).arg(i + 1) +
|
||||
QString::fromStdString(cyl.type.description) + ")");
|
||||
const cylinder_t *cyl = d->get_cylinder(cylinderId);
|
||||
const divecomputer *dc = d->get_dc(dcNr);
|
||||
|
||||
bool showUse = (dc->divemode == CCR) | !is_cylinder_use_appropriate(*dc, *cyl, false);
|
||||
|
||||
QString gasType = get_gas_string(cyl->gasmix);
|
||||
QString gasName;
|
||||
/* Check if we have the same gasmix two or more times
|
||||
* If yes return more verbose string */
|
||||
int same_gas = same_gasmix_cylinder(*cyl, cylinderId, d, true);
|
||||
if (same_gas != -1) {
|
||||
gasType += QString(" (%1 %2").arg(gettextFromC::tr("cyl.")).arg(cylinderId + 1);
|
||||
gasName = QString::fromStdString(d->get_cylinder(cylinderId)->type.description);
|
||||
}
|
||||
|
||||
if (showUse) {
|
||||
if (gasName.isNull())
|
||||
gasType += " (";
|
||||
else
|
||||
gasType += ", ";
|
||||
gasType += gettextFromC::tr(cylinderuse_text[d->get_cylinder(cylinderId)->cylinder_use]);
|
||||
}
|
||||
|
||||
if (!gasName.isNull())
|
||||
gasType += QString(", %1").arg(gasName);
|
||||
|
||||
if (!gasName.isNull() || showUse)
|
||||
gasType += ")";
|
||||
|
||||
return gasType;
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, QString>> get_dive_gas_list(const struct dive *d, int dcNr, bool showOnlyAppropriate)
|
||||
{
|
||||
const divecomputer *dc = d->get_dc(dcNr);
|
||||
std::vector<std::pair<int, QString>> list;
|
||||
for (unsigned int i = 0; i < d->cylinders.size(); i++) {
|
||||
if (showOnlyAppropriate && !is_cylinder_use_appropriate(*dc, *d->get_cylinder(i), false))
|
||||
continue;
|
||||
|
||||
list.push_back(std::pair(i, get_dive_gas(d, dcNr, i)));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,8 @@ enum watertypes {FRESHWATER, BRACKISHWATER, EN13319WATER, SALTWATER, DC_WATERTYP
|
|||
QString distance_string(int distanceInMeters);
|
||||
bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_text, bool *parsed_out = 0);
|
||||
QString get_gas_string(struct gasmix gas);
|
||||
QStringList get_dive_gas_list(const struct dive *d);
|
||||
QString get_dive_gas(const struct dive *d, int dcNr, int cylinderId);
|
||||
std::vector<std::pair<int, QString>> get_dive_gas_list(const struct dive *d, int dcNr, bool showOnlyAppropriate);
|
||||
QStringList stringToList(const QString &s);
|
||||
void read_hashes();
|
||||
void write_hashes();
|
||||
|
|
|
@ -14,6 +14,7 @@ qPrefTechnicalDetails *qPrefTechnicalDetails::instance()
|
|||
|
||||
void qPrefTechnicalDetails::loadSync(bool doSync)
|
||||
{
|
||||
disk_allowOcGasAsDiluent(doSync);
|
||||
disk_calcalltissues(doSync);
|
||||
disk_calcceiling(doSync);
|
||||
disk_calcceiling3m(doSync);
|
||||
|
@ -100,6 +101,8 @@ void qPrefTechnicalDetails::disk_gflow(bool doSync)
|
|||
}
|
||||
}
|
||||
|
||||
HANDLE_PREFERENCE_BOOL(TechnicalDetails, "allowOcGasAsDiluent", allowOcGasAsDiluent);
|
||||
|
||||
HANDLE_PREFERENCE_BOOL(TechnicalDetails, "gf_low_at_maxdepth", gf_low_at_maxdepth);
|
||||
|
||||
HANDLE_PREFERENCE_BOOL(TechnicalDetails, "InfoBox", infobox);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
class qPrefTechnicalDetails : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool allowOcGasAsDiluent READ allowOcGasAsDiluent WRITE set_allowOcGasAsDiluent NOTIFY allowOcGasAsDiluentChanged)
|
||||
Q_PROPERTY(bool calcalltissues READ calcalltissues WRITE set_calcalltissues NOTIFY calcalltissuesChanged)
|
||||
Q_PROPERTY(bool calcceiling READ calcceiling WRITE set_calcceiling NOTIFY calcceilingChanged)
|
||||
Q_PROPERTY(bool calcceiling3m READ calcceiling3m WRITE set_calcceiling3m NOTIFY calcceiling3mChanged)
|
||||
|
@ -44,6 +45,7 @@ public:
|
|||
static void sync() { loadSync(true); }
|
||||
|
||||
public:
|
||||
static bool allowOcGasAsDiluent() { return prefs.allowOcGasAsDiluent; }
|
||||
static bool calcalltissues() { return prefs.calcalltissues; }
|
||||
static bool calcceiling() { return prefs.calcceiling; }
|
||||
static bool calcceiling3m() { return prefs.calcceiling3m; }
|
||||
|
@ -73,6 +75,7 @@ public:
|
|||
static bool infobox() { return prefs.infobox; }
|
||||
|
||||
public slots:
|
||||
static void set_allowOcGasAsDiluent(bool value);
|
||||
static void set_calcalltissues(bool value);
|
||||
static void set_calcceiling(bool value);
|
||||
static void set_calcceiling3m(bool value);
|
||||
|
@ -102,6 +105,7 @@ public slots:
|
|||
static void set_infobox(bool value);
|
||||
|
||||
signals:
|
||||
void allowOcGasAsDiluentChanged(bool value);
|
||||
void calcalltissuesChanged(bool value);
|
||||
void calcceilingChanged(bool value);
|
||||
void calcceiling3mChanged(bool value);
|
||||
|
@ -133,6 +137,7 @@ signals:
|
|||
private:
|
||||
qPrefTechnicalDetails() {}
|
||||
|
||||
static void disk_allowOcGasAsDiluent(bool doSync);
|
||||
static void disk_calcalltissues(bool doSync);
|
||||
static void disk_calcceiling(bool doSync);
|
||||
static void disk_calcceiling3m(bool doSync);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue