static void
action_param (int argc, const char *argv[])
{
    std::string command = argv[0];
    bool use_reparam = false;
    if (OIIO::Strutil::istarts_with(command, "--reparam") ||
        OIIO::Strutil::istarts_with(command, "-reparam"))
        use_reparam = true;
    ParamValueList &params (use_reparam ? reparams : (::params));

    std::string paramname = argv[1];
    std::string stringval = argv[2];
    TypeDesc type = TypeDesc::UNKNOWN;
    bool unlockgeom = false;
    float f[16];

    size_t pos;
    while ((pos = command.find_first_of(":")) != std::string::npos) {
        command = command.substr (pos+1, std::string::npos);
        std::vector<std::string> splits;
        OIIO::Strutil::split (command, splits, ":", 1);
        if (splits.size() < 1) {}
        else if (OIIO::Strutil::istarts_with(splits[0],"type="))
            type.fromstring (splits[0].c_str()+5);
        else if (OIIO::Strutil::istarts_with(splits[0],"lockgeom="))
            unlockgeom = (strtol (splits[0].c_str()+9, NULL, 10) == 0);
    }

    // If it is or might be a matrix, look for 16 comma-separated floats
    if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeMatrix)
        && sscanf (stringval.c_str(),
                   "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f",
                   &f[0], &f[1], &f[2], &f[3],
                   &f[4], &f[5], &f[6], &f[7], &f[8], &f[9], &f[10], &f[11],
                   &f[12], &f[13], &f[14], &f[15]) == 16) {
        params.push_back (ParamValue());
        params.back().init (paramname, TypeDesc::TypeMatrix, 1, f);
        if (unlockgeom)
            params.back().interp (ParamValue::INTERP_VERTEX);
        return;
    }
    // If it is or might be a vector type, look for 3 comma-separated floats
    if ((type == TypeDesc::UNKNOWN || equivalent(type,TypeDesc::TypeVector))
        && sscanf (stringval.c_str(), "%g, %g, %g", &f[0], &f[1], &f[2]) == 3) {
        if (type == TypeDesc::UNKNOWN)
            type = TypeDesc::TypeVector;
        params.push_back (ParamValue());
        params.back().init (paramname, type, 1, f);
        if (unlockgeom)
            params.back().interp (ParamValue::INTERP_VERTEX);
        return;
    }
    // If it is or might be an int, look for an int that takes up the whole
    // string.
    if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeInt)) {
        char *endptr = NULL;
        int ival = strtol(stringval.c_str(),&endptr,10);
        if (endptr && *endptr == 0) {
            params.push_back (ParamValue());
            params.back().init (paramname, TypeDesc::TypeInt, 1, &ival);
            if (unlockgeom)
                params.back().interp (ParamValue::INTERP_VERTEX);
            return;
        }
    }
    // If it is or might be an float, look for a float that takes up the
    // whole string.
    if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeFloat)) {
        char *endptr = NULL;
        float fval = (float) strtod(stringval.c_str(),&endptr);
        if (endptr && *endptr == 0) {
            params.push_back (ParamValue());
            params.back().init (paramname, TypeDesc::TypeFloat, 1, &fval);
            if (unlockgeom)
                params.back().interp (ParamValue::INTERP_VERTEX);
            return;
        }
    }

    // All remaining cases -- it's a string
    const char *s = stringval.c_str();
    params.push_back (ParamValue());
    params.back().init (paramname, TypeDesc::TypeString, 1, &s);
    if (unlockgeom)
        params.back().interp (ParamValue::INTERP_VERTEX);
}
static void
action_param (int argc, const char *argv[])
{
    std::string command = argv[0];
    bool use_reparam = false;
    if (OIIO::Strutil::istarts_with(command, "--reparam") ||
        OIIO::Strutil::istarts_with(command, "-reparam"))
        use_reparam = true;
    ParamValueList &params (use_reparam ? reparams : (::params));

    string_view paramname (argv[1]);
    string_view stringval (argv[2]);
    TypeDesc type = TypeDesc::UNKNOWN;
    bool unlockgeom = false;
    float f[16];

    size_t pos;
    while ((pos = command.find_first_of(":")) != std::string::npos) {
        command = command.substr (pos+1, std::string::npos);
        std::vector<std::string> splits;
        OIIO::Strutil::split (command, splits, ":", 1);
        if (splits.size() < 1) {}
        else if (OIIO::Strutil::istarts_with(splits[0],"type="))
            type.fromstring (splits[0].c_str()+5);
        else if (OIIO::Strutil::istarts_with(splits[0],"lockgeom="))
            unlockgeom = (OIIO::Strutil::from_string<int> (splits[0]) == 0);
    }

    // If it is or might be a matrix, look for 16 comma-separated floats
    if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeMatrix)
        && sscanf (stringval.c_str(),
                   "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f",
                   &f[0], &f[1], &f[2], &f[3],
                   &f[4], &f[5], &f[6], &f[7], &f[8], &f[9], &f[10], &f[11],
                   &f[12], &f[13], &f[14], &f[15]) == 16) {
#if OIIO_VERSION >= 10804
        params.emplace_back (paramname, TypeDesc::TypeMatrix, 1, f);
#else
        params.push_back (ParamValue());
        params.back().init (paramname, TypeDesc::TypeMatrix, 1, f);
#endif
        if (unlockgeom)
            params.back().interp (ParamValue::INTERP_VERTEX);
        return;
    }
    // If it is or might be a vector type, look for 3 comma-separated floats
    if ((type == TypeDesc::UNKNOWN || equivalent(type,TypeDesc::TypeVector))
        && sscanf (stringval.c_str(), "%g, %g, %g", &f[0], &f[1], &f[2]) == 3) {
        if (type == TypeDesc::UNKNOWN)
            type = TypeDesc::TypeVector;
#if OIIO_VERSION >= 10804
        params.emplace_back (paramname, type, 1, f);
#else
        params.push_back (ParamValue());
        params.back().init (paramname, type, 1, f);
#endif
        if (unlockgeom)
            params.back().interp (ParamValue::INTERP_VERTEX);
        return;
    }
    // If it is or might be an int, look for an int that takes up the whole
    // string.
    if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeInt)
          && OIIO::Strutil::string_is<int>(stringval)) {
#if OIIO_VERSION >= 10804
        params.emplace_back (paramname, OIIO::Strutil::from_string<int>(stringval));
#else
        params.push_back (ParamValue());
        int i = OIIO::Strutil::from_string<int>(stringval);
        params.back().init (paramname, TypeDesc::TypeInt, 1, &i);
#endif
        if (unlockgeom)
            params.back().interp (ParamValue::INTERP_VERTEX);
        return;
    }
    // If it is or might be an float, look for a float that takes up the
    // whole string.
    if ((type == TypeDesc::UNKNOWN || type == TypeDesc::TypeFloat)
          && OIIO::Strutil::string_is<float>(stringval)) {
#if OIIO_VERSION >= 10804
        params.emplace_back (paramname, OIIO::Strutil::from_string<float>(stringval));
#else
        params.push_back (ParamValue());
        float f = OIIO::Strutil::from_string<float>(stringval);
        params.back().init (paramname, TypeDesc::TypeFloat, 1, &f);
#endif
        if (unlockgeom)
            params.back().interp (ParamValue::INTERP_VERTEX);
        return;
    }

    // Catch-all for float types and arrays
    if (type.basetype == TypeDesc::FLOAT) {
        int n = type.aggregate * type.numelements();
        std::vector<float> vals (n);
        for (int i = 0;  i < n;  ++i) {
            OIIO::Strutil::parse_float (stringval, vals[i]);
            OIIO::Strutil::parse_char (stringval, ',');
        }
#if OIIO_VERSION >= 10804
        params.emplace_back (paramname, type, 1, &vals[0]);
#else
        params.push_back (ParamValue());
        params.back().init (paramname, type, 1, &vals[0]);
#endif
        if (unlockgeom)
            params.back().interp (ParamValue::INTERP_VERTEX);
        return;
    }

    // Catch-all for int types and arrays
    if (type.basetype == TypeDesc::INT) {
        int n = type.aggregate * type.numelements();
        std::vector<int> vals (n);
        for (int i = 0;  i < n;  ++i) {
            OIIO::Strutil::parse_int (stringval, vals[i]);
            OIIO::Strutil::parse_char (stringval, ',');
        }
#if OIIO_VERSION >= 10804
        params.emplace_back (paramname, type, 1, &vals[0]);
#else
        params.push_back (ParamValue());
        params.back().init (paramname, type, 1, &vals[0]);
#endif
        if (unlockgeom)
            params.back().interp (ParamValue::INTERP_VERTEX);
        return;
    }

    // String arrays are slightly tricky
    if (type.basetype == TypeDesc::STRING && type.is_array()) {
        std::vector<string_view> splitelements;
        OIIO::Strutil::split (stringval, splitelements, ",", type.arraylen);
        splitelements.resize (type.arraylen);
        std::vector<ustring> strelements;
        for (auto&& s : splitelements)
            strelements.push_back (ustring(s));
#if OIIO_VERSION >= 10804
        params.emplace_back (paramname, type, 1, &strelements[0]);
#else
        params.push_back (ParamValue());
        params.back().init (paramname, type, 1, &strelements[0]);
#endif
        if (unlockgeom)
            params.back().interp (ParamValue::INTERP_VERTEX);
        return;
    }

    // All remaining cases -- it's a string
    const char *s = stringval.c_str();
#if OIIO_VERSION >= 10804
    params.emplace_back (paramname, TypeDesc::TypeString, 1, &s);
#else
    params.push_back (ParamValue());
    params.back().init (paramname, TypeDesc::TypeString, 1, &s);
#endif
    if (unlockgeom)
        params.back().interp (ParamValue::INTERP_VERTEX);
}