mirror of https://github.com/KLayout/klayout.git
WIP
This commit is contained in:
parent
6e19b34cb3
commit
19ded2d784
|
|
@ -163,7 +163,7 @@ void Device::connect_terminal (size_t terminal_id, Net *net)
|
|||
}
|
||||
}
|
||||
|
||||
double Device::parameter_value (size_t param_id) const
|
||||
const tl::Variant &Device::parameter_value (size_t param_id) const
|
||||
{
|
||||
if (m_parameters.size () > param_id) {
|
||||
return m_parameters [param_id];
|
||||
|
|
@ -173,10 +173,11 @@ double Device::parameter_value (size_t param_id) const
|
|||
return pd->default_value ();
|
||||
}
|
||||
}
|
||||
return 0.0;
|
||||
static tl::Variant dummy_value (0.0);
|
||||
return dummy_value;
|
||||
}
|
||||
|
||||
void Device::set_parameter_value (size_t param_id, double v)
|
||||
void Device::set_parameter_value (size_t param_id, const tl::Variant &v)
|
||||
{
|
||||
if (m_parameters.size () <= param_id) {
|
||||
|
||||
|
|
@ -198,18 +199,26 @@ void Device::set_parameter_value (size_t param_id, double v)
|
|||
m_parameters [param_id] = v;
|
||||
}
|
||||
|
||||
double Device::parameter_value (const std::string &name) const
|
||||
const tl::Variant &Device::parameter_value (const std::string &name) const
|
||||
{
|
||||
return device_class () ? parameter_value (device_class ()->parameter_id_for_name (name)) : 0.0;
|
||||
static tl::Variant dummy_value (0.0);
|
||||
return device_class () ? parameter_value (device_class ()->parameter_id_for_name (name)) : dummy_value;
|
||||
}
|
||||
|
||||
void Device::set_parameter_value (const std::string &name, double v)
|
||||
void Device::set_parameter_value (const std::string &name, const tl::Variant &v)
|
||||
{
|
||||
if (device_class ()) {
|
||||
set_parameter_value (device_class ()->parameter_id_for_name (name), v);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::set_parameter_value_create (const std::string &name, const tl::Variant &v, bool primary, const tl::Variant &def_value)
|
||||
{
|
||||
if (device_class ()) {
|
||||
set_parameter_value (device_class ()->parameter_id_for_name_create (name, primary, def_value), v);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::add_others_terminals (unsigned int this_terminal, db::Device *other, unsigned int other_terminal)
|
||||
{
|
||||
std::vector<DeviceReconnectedTerminal> &terminals = m_reconnected_terminals [this_terminal];
|
||||
|
|
|
|||
|
|
@ -283,24 +283,33 @@ public:
|
|||
/**
|
||||
* @brief Gets the value for the parameter with the given ID
|
||||
*/
|
||||
double parameter_value (size_t param_id) const;
|
||||
const tl::Variant ¶meter_value (size_t param_id) const;
|
||||
|
||||
/**
|
||||
* @brief Sets the value for the parameter with the given ID
|
||||
*/
|
||||
void set_parameter_value (size_t param_id, double v);
|
||||
void set_parameter_value (size_t param_id, const tl::Variant &v);
|
||||
|
||||
/**
|
||||
* @brief Gets the value for the parameter with the given name
|
||||
* If the name is not valid, an exception is thrown.
|
||||
*/
|
||||
double parameter_value (const std::string &name) const;
|
||||
const tl::Variant ¶meter_value (const std::string &name) const;
|
||||
|
||||
/**
|
||||
* @brief Sets the value for the parameter with the given name
|
||||
* If the name is not valid, an exception is thrown.
|
||||
*/
|
||||
void set_parameter_value (const std::string &name, double v);
|
||||
void set_parameter_value (const std::string &name, const tl::Variant &v);
|
||||
|
||||
/**
|
||||
* @brief Sets the value for the parameter with the given name
|
||||
*
|
||||
* If the name is not valid, the parameter is created with the given primary
|
||||
* flag and default value. The default value also defines the type of the
|
||||
* parameter.
|
||||
*/
|
||||
void set_parameter_value_create (const std::string &name, const tl::Variant &v, bool primary, const tl::Variant &def_value);
|
||||
|
||||
/**
|
||||
* @brief Used for device combination: join terminals with other device
|
||||
|
|
@ -402,7 +411,7 @@ private:
|
|||
std::string m_name;
|
||||
db::DCplxTrans m_trans;
|
||||
std::vector<Net::terminal_iterator> m_terminal_refs;
|
||||
std::vector<double> m_parameters;
|
||||
std::vector<tl::Variant> m_parameters;
|
||||
size_t m_id;
|
||||
Circuit *mp_circuit;
|
||||
std::vector<DeviceAbstractRef> m_other_abstracts;
|
||||
|
|
|
|||
|
|
@ -121,9 +121,17 @@ std::string EqualDeviceParameters::to_string () const
|
|||
bool EqualDeviceParameters::less (const db::Device &a, const db::Device &b) const
|
||||
{
|
||||
for (std::vector<std::pair<size_t, std::pair<double, double> > >::const_iterator c = m_compare_set.begin (); c != m_compare_set.end (); ++c) {
|
||||
int cmp = compare_parameters (a.parameter_value (c->first), b.parameter_value (c->first), c->second.first, c->second.second);
|
||||
if (cmp != 0) {
|
||||
return cmp < 0;
|
||||
const tl::Variant &va = a.parameter_value (c->first);
|
||||
const tl::Variant &vb = b.parameter_value (c->first);
|
||||
if (va.is_double () && vb.is_double ()) {
|
||||
int cmp = compare_parameters (va.to_double (), vb.to_double (), c->second.first, c->second.second);
|
||||
if (cmp != 0) {
|
||||
return cmp < 0;
|
||||
}
|
||||
} else {
|
||||
if (va != vb) {
|
||||
return va < vb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,9 +145,17 @@ bool EqualDeviceParameters::less (const db::Device &a, const db::Device &b) cons
|
|||
const std::vector<db::DeviceParameterDefinition> &pd = primary_device_class (a, b)->parameter_definitions ();
|
||||
for (std::vector<db::DeviceParameterDefinition>::const_iterator p = pd.begin (); p != pd.end (); ++p) {
|
||||
if (p->is_primary () && seen.find (p->id ()) == seen.end ()) {
|
||||
int cmp = compare_parameters (a.parameter_value (p->id ()), b.parameter_value (p->id ()));
|
||||
if (cmp != 0) {
|
||||
return cmp < 0;
|
||||
const tl::Variant &va = a.parameter_value (p->id ());
|
||||
const tl::Variant &vb = b.parameter_value (p->id ());
|
||||
if (va.is_double () && vb.is_double ()) {
|
||||
int cmp = compare_parameters (va.to_double (), vb.to_double ());
|
||||
if (cmp != 0) {
|
||||
return cmp < 0;
|
||||
}
|
||||
} else {
|
||||
if (va != vb) {
|
||||
return va < vb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -168,9 +184,17 @@ bool AllDeviceParametersAreEqual::less (const db::Device &a, const db::Device &b
|
|||
{
|
||||
const std::vector<db::DeviceParameterDefinition> ¶meters = a.device_class ()->parameter_definitions ();
|
||||
for (std::vector<db::DeviceParameterDefinition>::const_iterator c = parameters.begin (); c != parameters.end (); ++c) {
|
||||
int cmp = compare_parameters (a.parameter_value (c->id ()), b.parameter_value (c->id ()), 0.0, m_relative);
|
||||
if (cmp != 0) {
|
||||
return cmp < 0;
|
||||
const tl::Variant &va = a.parameter_value (c->id ());
|
||||
const tl::Variant &vb = b.parameter_value (c->id ());
|
||||
if (va.is_double () && vb.is_double ()) {
|
||||
int cmp = compare_parameters (va.to_double (), vb.to_double (), 0.0, m_relative);
|
||||
if (cmp != 0) {
|
||||
return cmp < 0;
|
||||
}
|
||||
} else {
|
||||
if (va != vb) {
|
||||
return va < vb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ public:
|
|||
/**
|
||||
* @brief Creates a device parameter definition with the given name and description
|
||||
*/
|
||||
DeviceParameterDefinition (const std::string &name, const std::string &description, double default_value = 0.0, bool is_primary = true, double si_scaling = 1.0, double geo_scaling = 0.0)
|
||||
DeviceParameterDefinition (const std::string &name, const std::string &description, tl::Variant default_value = tl::Variant (0.0), bool is_primary = true, double si_scaling = 1.0, double geo_scaling = 0.0)
|
||||
: m_name (name), m_description (description), m_default_value (default_value), m_id (0), m_is_primary (is_primary), m_si_scaling (si_scaling), m_geo_scaling (geo_scaling)
|
||||
{
|
||||
// .. nothing yet ..
|
||||
|
|
@ -234,7 +234,7 @@ public:
|
|||
/**
|
||||
* @brief Gets the parameter default value
|
||||
*/
|
||||
double default_value () const
|
||||
const tl::Variant &default_value () const
|
||||
{
|
||||
return m_default_value;
|
||||
}
|
||||
|
|
@ -242,7 +242,7 @@ public:
|
|||
/**
|
||||
* @brief Sets the parameter default value
|
||||
*/
|
||||
void set_default_value (double d)
|
||||
void set_default_value (const tl::Variant &d)
|
||||
{
|
||||
m_default_value = d;
|
||||
}
|
||||
|
|
@ -291,7 +291,7 @@ private:
|
|||
friend class DeviceClass;
|
||||
|
||||
std::string m_name, m_description;
|
||||
double m_default_value;
|
||||
tl::Variant m_default_value;
|
||||
size_t m_id;
|
||||
bool m_is_primary;
|
||||
double m_si_scaling;
|
||||
|
|
@ -578,6 +578,19 @@ public:
|
|||
*/
|
||||
size_t parameter_id_for_name (const std::string &name) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the parameter ID for the parameter with the given name
|
||||
*
|
||||
* A new parameter definition is created, if the parameter does not exist yes.
|
||||
*
|
||||
* @param name The name of the parameter
|
||||
* @param primary The primary flag of the new parameter, if it does not exist yet
|
||||
* @param default_value The default value of the new parameter, if it does not exist yet
|
||||
*
|
||||
* The default value also declares the type.
|
||||
*/
|
||||
size_t parameter_id_for_name_create (const std::string &name, bool primary, const tl::Variant &default_value) const;
|
||||
|
||||
/**
|
||||
* @brief Returns true, if the device has a terminal with the given name
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ namespace l2n_std_format
|
|||
DB_PUBLIC std::string LongKeys::terminal_key ("terminal");
|
||||
DB_PUBLIC std::string LongKeys::abstract_key ("abstract");
|
||||
DB_PUBLIC std::string LongKeys::param_key ("param");
|
||||
DB_PUBLIC std::string LongKeys::param_int_key ("param-int");
|
||||
DB_PUBLIC std::string LongKeys::param_string_key ("param-string");
|
||||
DB_PUBLIC std::string LongKeys::param_var_key ("param-var");
|
||||
DB_PUBLIC std::string LongKeys::param_nil_key ("param-nil");
|
||||
DB_PUBLIC std::string LongKeys::location_key ("location");
|
||||
DB_PUBLIC std::string LongKeys::rotation_key ("rotation");
|
||||
DB_PUBLIC std::string LongKeys::mirror_key ("mirror");
|
||||
|
|
@ -88,6 +92,10 @@ namespace l2n_std_format
|
|||
DB_PUBLIC std::string ShortKeys::terminal_key ("T");
|
||||
DB_PUBLIC std::string ShortKeys::abstract_key ("A");
|
||||
DB_PUBLIC std::string ShortKeys::param_key ("E");
|
||||
DB_PUBLIC std::string ShortKeys::param_int_key ("EI");
|
||||
DB_PUBLIC std::string ShortKeys::param_string_key ("ES");
|
||||
DB_PUBLIC std::string ShortKeys::param_var_key ("EV");
|
||||
DB_PUBLIC std::string ShortKeys::param_nil_key ("EN");
|
||||
DB_PUBLIC std::string ShortKeys::location_key ("Y");
|
||||
DB_PUBLIC std::string ShortKeys::rotation_key ("O");
|
||||
DB_PUBLIC std::string ShortKeys::mirror_key ("M");
|
||||
|
|
|
|||
|
|
@ -163,6 +163,15 @@ namespace db
|
|||
* [template-param]:
|
||||
* param(<name> <primary> <default-value>) - defines a template parameter [short key: E]
|
||||
* ('primary' is a value: 0 or 1, 'default_value' is a float)
|
||||
* param-int(<name> <primary> <default-value>) - defines a template parameter [short key: EI]
|
||||
* ('primary' is a value: 0 or 1, 'default_value' is an int)
|
||||
* param-string(<name> <primary> <default-value>) - defines a template parameter [short key: ES]
|
||||
* ('primary' is a value: 0 or 1, 'default_value' is a string)
|
||||
* param-var(<name> <primary> <default-value>) - defines a template parameter [short key: EV]
|
||||
* ('primary' is a value: 0 or 1, 'default_value' is a string encoding the default
|
||||
* value in KLayout variant notation)
|
||||
* param-nil(<name> <primary>) - defines a template parameter [short key: EN]
|
||||
* ('primary' is a value: 0 or 1)
|
||||
*
|
||||
* [template-terminal]:
|
||||
* terminal(<name>) - defines a terminal [short key: T]
|
||||
|
|
@ -172,7 +181,11 @@ namespace db
|
|||
* - specifies the terminal geometry [short key: T]
|
||||
*
|
||||
* [param]:
|
||||
* param(<name> <value>) - defines a parameter [short key: E]
|
||||
* param(<name> <value>) - defines a parameter with a default value [short key: E]
|
||||
* param-int(<name> <value>) - defines a parameter with an int value [short key: EI]
|
||||
* param-nil(<name>) - defines a parameter with a nil value [short key: EN]
|
||||
* param-string(<name> <value>) - defines a parameter with a string value [short key: ES]
|
||||
* param-var(<name> <value>) - defines a parameter with a variant value [short key: EV]
|
||||
*
|
||||
* [device-terminal]:
|
||||
* terminal(<terminal-name> <net-id>)
|
||||
|
|
@ -246,6 +259,10 @@ namespace l2n_std_format
|
|||
static std::string terminal_key;
|
||||
static std::string abstract_key;
|
||||
static std::string param_key;
|
||||
static std::string param_int_key;
|
||||
static std::string param_string_key;
|
||||
static std::string param_var_key;
|
||||
static std::string param_nil_key;
|
||||
static std::string location_key;
|
||||
static std::string rotation_key;
|
||||
static std::string mirror_key;
|
||||
|
|
@ -287,6 +304,10 @@ namespace l2n_std_format
|
|||
static std::string terminal_key;
|
||||
static std::string abstract_key;
|
||||
static std::string param_key;
|
||||
static std::string param_int_key;
|
||||
static std::string param_string_key;
|
||||
static std::string param_var_key;
|
||||
static std::string param_nil_key;
|
||||
static std::string location_key;
|
||||
static std::string rotation_key;
|
||||
static std::string mirror_key;
|
||||
|
|
|
|||
|
|
@ -104,6 +104,21 @@ LayoutToNetlistStandardReader::try_read_int (int &i)
|
|||
return m_ex.try_read (i);
|
||||
}
|
||||
|
||||
long
|
||||
LayoutToNetlistStandardReader::read_long ()
|
||||
{
|
||||
long i = 0;
|
||||
m_ex.read (i);
|
||||
return i;
|
||||
}
|
||||
|
||||
bool
|
||||
LayoutToNetlistStandardReader::try_read_long (long &i)
|
||||
{
|
||||
i = 0;
|
||||
return m_ex.try_read (i);
|
||||
}
|
||||
|
||||
db::Coord
|
||||
LayoutToNetlistStandardReader::read_coord ()
|
||||
{
|
||||
|
|
@ -322,6 +337,21 @@ static db::Region &layer_by_name (db::LayoutToNetlist *l2n, const std::string &n
|
|||
return *l;
|
||||
}
|
||||
|
||||
static void make_parameter (db::DeviceClass *dc, const std::string ¶m_name, bool primary, const tl::Variant &default_value)
|
||||
{
|
||||
if (! dc->has_parameter_with_name (param_name)) {
|
||||
db::DeviceParameterDefinition pd;
|
||||
pd.set_name (param_name);
|
||||
pd.set_is_primary (primary);
|
||||
pd.set_default_value (default_value);
|
||||
dc->add_parameter_definition (pd);
|
||||
} else {
|
||||
db::DeviceParameterDefinition *pd = dc->parameter_definition_non_const (dc->parameter_id_for_name (param_name));
|
||||
pd->set_default_value (default_value);
|
||||
pd->set_is_primary (primary);
|
||||
}
|
||||
}
|
||||
|
||||
void LayoutToNetlistStandardReader::read_netlist (db::Netlist *netlist, db::LayoutToNetlist *l2n, LayoutToNetlistStandardReader::Brace *nested, std::map<const db::Circuit *, ObjectMap> *map_per_circuit)
|
||||
{
|
||||
m_dbu = 0.001;
|
||||
|
|
@ -459,17 +489,62 @@ void LayoutToNetlistStandardReader::read_netlist (db::Netlist *netlist, db::Layo
|
|||
read_word_or_quoted (param_name);
|
||||
int primary = read_int ();
|
||||
double default_value = read_double ();
|
||||
if (! dc->has_parameter_with_name (param_name)) {
|
||||
db::DeviceParameterDefinition pd;
|
||||
pd.set_name (param_name);
|
||||
pd.set_is_primary (primary);
|
||||
pd.set_default_value (default_value);
|
||||
dc->add_parameter_definition (pd);
|
||||
} else {
|
||||
db::DeviceParameterDefinition *pd = dc->parameter_definition_non_const (dc->parameter_id_for_name (param_name));
|
||||
pd->set_default_value (default_value);
|
||||
pd->set_is_primary (primary);
|
||||
}
|
||||
make_parameter (dc, param_name, primary, default_value);
|
||||
|
||||
br.done ();
|
||||
|
||||
} else if (test (skeys::param_int_key) || test (lkeys::param_int_key)) {
|
||||
|
||||
Brace br (this);
|
||||
|
||||
std::string param_name;
|
||||
read_word_or_quoted (param_name);
|
||||
int primary = read_int ();
|
||||
int default_value = read_int ();
|
||||
make_parameter (dc, param_name, primary, default_value);
|
||||
|
||||
br.done ();
|
||||
|
||||
} else if (test (skeys::param_string_key) || test (lkeys::param_string_key)) {
|
||||
|
||||
Brace br (this);
|
||||
|
||||
std::string param_name;
|
||||
read_word_or_quoted (param_name);
|
||||
int primary = read_int ();
|
||||
std::string default_value;
|
||||
read_word_or_quoted (default_value);
|
||||
make_parameter (dc, param_name, primary, default_value);
|
||||
|
||||
br.done ();
|
||||
|
||||
} else if (test (skeys::param_var_key) || test (lkeys::param_var_key)) {
|
||||
|
||||
Brace br (this);
|
||||
|
||||
std::string param_name;
|
||||
read_word_or_quoted (param_name);
|
||||
|
||||
int primary = read_int ();
|
||||
|
||||
std::string default_value_str;
|
||||
read_word_or_quoted (default_value_str);
|
||||
tl::Variant default_value;
|
||||
tl::Extractor ex (default_value_str.c_str ());
|
||||
ex.read (default_value);
|
||||
|
||||
make_parameter (dc, param_name, primary, default_value);
|
||||
|
||||
br.done ();
|
||||
|
||||
} else if (test (skeys::param_nil_key) || test (lkeys::param_nil_key)) {
|
||||
|
||||
Brace br (this);
|
||||
|
||||
std::string param_name;
|
||||
read_word_or_quoted (param_name);
|
||||
int primary = read_int ();
|
||||
make_parameter (dc, param_name, primary, tl::Variant ());
|
||||
|
||||
br.done ();
|
||||
|
||||
|
|
@ -1052,24 +1127,58 @@ LayoutToNetlistStandardReader::read_device (db::Netlist *netlist, db::LayoutToNe
|
|||
double value = read_double ();
|
||||
br2.done ();
|
||||
|
||||
size_t pid = std::numeric_limits<size_t>::max ();
|
||||
const std::vector<db::DeviceParameterDefinition> &pd = dm.second->parameter_definitions ();
|
||||
for (std::vector<db::DeviceParameterDefinition>::const_iterator p = pd.begin (); p != pd.end (); ++p) {
|
||||
if (p->name () == pname) {
|
||||
pid = p->id ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if no parameter with this name exists, create one
|
||||
if (pid == std::numeric_limits<size_t>::max ()) {
|
||||
// TODO: this should only happen for generic devices
|
||||
db::DeviceClass *dc = const_cast<db::DeviceClass *> (dm.second);
|
||||
pid = dc->add_parameter_definition (db::DeviceParameterDefinition (pname, std::string ())).id ();
|
||||
}
|
||||
|
||||
size_t pid = dm.second->parameter_id_for_name_create (pname, false, 0.0);
|
||||
device->set_parameter_value (pid, value);
|
||||
|
||||
} else if (test (skeys::param_int_key) || test (lkeys::param_int_key)) {
|
||||
|
||||
Brace br2 (this);
|
||||
std::string pname;
|
||||
read_word_or_quoted (pname);
|
||||
long value = read_long ();
|
||||
br2.done ();
|
||||
|
||||
size_t pid = dm.second->parameter_id_for_name_create (pname, false, long (0));
|
||||
device->set_parameter_value (pid, value);
|
||||
|
||||
} else if (test (skeys::param_string_key) || test (lkeys::param_string_key)) {
|
||||
|
||||
Brace br2 (this);
|
||||
std::string pname;
|
||||
read_word_or_quoted (pname);
|
||||
std::string value;
|
||||
read_word_or_quoted (value);
|
||||
br2.done ();
|
||||
|
||||
size_t pid = dm.second->parameter_id_for_name_create (pname, false, std::string ());
|
||||
device->set_parameter_value (pid, value);
|
||||
|
||||
} else if (test (skeys::param_var_key) || test (lkeys::param_var_key)) {
|
||||
|
||||
Brace br2 (this);
|
||||
std::string pname;
|
||||
read_word_or_quoted (pname);
|
||||
std::string value_str;
|
||||
read_word_or_quoted (value_str);
|
||||
br2.done ();
|
||||
|
||||
tl::Variant value;
|
||||
tl::Extractor ex (value_str.c_str ());
|
||||
ex.read (value);
|
||||
|
||||
size_t pid = dm.second->parameter_id_for_name_create (pname, false, tl::Variant ());
|
||||
device->set_parameter_value (pid, value);
|
||||
|
||||
} else if (test (skeys::param_nil_key) || test (lkeys::param_nil_key)) {
|
||||
|
||||
Brace br2 (this);
|
||||
std::string pname;
|
||||
read_word_or_quoted (pname);
|
||||
br2.done ();
|
||||
|
||||
size_t pid = dm.second->parameter_id_for_name_create (pname, false, tl::Variant ());
|
||||
device->set_parameter_value (pid, tl::Variant ());
|
||||
|
||||
} else if (at_end ()) {
|
||||
throw tl::Exception (tl::to_string (tr ("Unexpected end of file inside device definition (location, scale, mirror, rotation, param or terminal expected)")));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -136,6 +136,8 @@ protected:
|
|||
void read_word_or_quoted (std::string &s);
|
||||
int read_int ();
|
||||
bool try_read_int (int &i);
|
||||
long read_long ();
|
||||
bool try_read_long (long &i);
|
||||
db::Coord read_coord ();
|
||||
double read_double ();
|
||||
bool at_end ();
|
||||
|
|
|
|||
|
|
@ -324,7 +324,18 @@ void std_writer_impl<Keys>::write_device_class (TokenizedOutput &stream, const d
|
|||
if (! any_def) {
|
||||
out << endl;
|
||||
}
|
||||
TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0) << tl::to_string (p->default_value ());
|
||||
const tl::Variant &def = p->default_value ();
|
||||
if (def.is_double ()) {
|
||||
TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0) << def.to_string ();
|
||||
} else if (def.is_long () || def.is_ulong ()) {
|
||||
TokenizedOutput (out, Keys::param_int_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0) << def.to_string ();
|
||||
} else if (def.is_a_string ()) {
|
||||
TokenizedOutput (out, Keys::param_string_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0) << tl::to_quoted_string (def.to_string ());
|
||||
} else if (def.is_nil ()) {
|
||||
TokenizedOutput (out, Keys::param_nil_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0);
|
||||
} else {
|
||||
TokenizedOutput (out, Keys::param_var_key) << tl::to_word_or_quoted_string (p->name ()) << tl::to_string (p->is_primary () ? 1 : 0) << tl::to_quoted_string (def.to_parsable_string ());
|
||||
}
|
||||
any_def = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -906,7 +917,19 @@ void std_writer_impl<Keys>::write (TokenizedOutput &stream, const db::Device &de
|
|||
}
|
||||
|
||||
for (std::vector<DeviceParameterDefinition>::const_iterator i = pd.begin (); i != pd.end (); ++i) {
|
||||
TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (i->name ()) << tl::sprintf ("%.12g", device.parameter_value (i->id ()));
|
||||
|
||||
const tl::Variant &value = device.parameter_value (i->id ());
|
||||
if (value.is_double ()) {
|
||||
TokenizedOutput (out, Keys::param_key) << tl::to_word_or_quoted_string (i->name ()) << value.to_string ();
|
||||
} else if (value.is_long () || value.is_ulong ()) {
|
||||
TokenizedOutput (out, Keys::param_int_key) << tl::to_word_or_quoted_string (i->name ()) << value.to_string ();
|
||||
} else if (value.is_a_string ()) {
|
||||
TokenizedOutput (out, Keys::param_string_key) << tl::to_word_or_quoted_string (i->name ()) << tl::to_quoted_string (value.to_string ());
|
||||
} else if (value.is_nil ()) {
|
||||
TokenizedOutput (out, Keys::param_nil_key) << tl::to_word_or_quoted_string (i->name ());
|
||||
} else {
|
||||
TokenizedOutput (out, Keys::param_var_key) << tl::to_word_or_quoted_string (i->name ()) << tl::to_quoted_string (value.to_parsable_string ());
|
||||
}
|
||||
}
|
||||
|
||||
for (std::vector<DeviceTerminalDefinition>::const_iterator i = td.begin (); i != td.end (); ++i) {
|
||||
|
|
|
|||
|
|
@ -326,11 +326,11 @@ DeviceFilter::filter (const db::Device *device) const
|
|||
const db::DeviceClassCapacitor *cap = dynamic_cast<const db::DeviceClassCapacitor *> (device->device_class ());
|
||||
|
||||
if (res) {
|
||||
if (m_res_threshold > 0.0 && device->parameter_value (db::DeviceClassResistor::param_id_R) > m_res_threshold) {
|
||||
if (m_res_threshold > 0.0 && device->parameter_value (db::DeviceClassResistor::param_id_R).to_double () > m_res_threshold) {
|
||||
return false;
|
||||
}
|
||||
} else if (cap) {
|
||||
if (m_cap_threshold > 0.0 && device->parameter_value (db::DeviceClassCapacitor::param_id_C) < m_cap_threshold) {
|
||||
if (m_cap_threshold > 0.0 && device->parameter_value (db::DeviceClassCapacitor::param_id_C).to_double () < m_cap_threshold) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,8 +114,8 @@ class ResistorDeviceCombiner
|
|||
public:
|
||||
void parallel (Device *a, Device *b) const
|
||||
{
|
||||
double va = a->parameter_value (0);
|
||||
double vb = b->parameter_value (0);
|
||||
double va = a->parameter_value (0).to_double ();
|
||||
double vb = b->parameter_value (0).to_double ();
|
||||
a->set_parameter_value (0, va + vb < 1e-30 ? 0.0 : va * vb / (va + vb));
|
||||
|
||||
// parallel width is sum of both, length is the one that gives the same value of resistance
|
||||
|
|
@ -124,10 +124,10 @@ public:
|
|||
// R1 = L1/W1
|
||||
// R2 = L2/W2
|
||||
// -> L = (L1*L2*(W1+W2))/(L2*W1+L1*W2))
|
||||
double l1 = a->parameter_value (1);
|
||||
double w1 = a->parameter_value (2);
|
||||
double l2 = b->parameter_value (1);
|
||||
double w2 = b->parameter_value (2);
|
||||
double l1 = a->parameter_value (1).to_double ();
|
||||
double w1 = a->parameter_value (2).to_double ();
|
||||
double l2 = b->parameter_value (1).to_double ();
|
||||
double w2 = b->parameter_value (2).to_double ();
|
||||
double dnom = (l2 * w1 + l1 * w2);
|
||||
if (fabs (dnom) > 1e-15) {
|
||||
a->set_parameter_value (1, (l1 * l2 * (w1 + w2)) / dnom);
|
||||
|
|
@ -135,20 +135,20 @@ public:
|
|||
a->set_parameter_value (2, w1 + w2);
|
||||
|
||||
// TODO: does this implementation make sense? (area)
|
||||
double aa = a->parameter_value (3);
|
||||
double ab = b->parameter_value (3);
|
||||
double aa = a->parameter_value (3).to_double ();
|
||||
double ab = b->parameter_value (3).to_double ();
|
||||
a->set_parameter_value (3, aa + ab);
|
||||
|
||||
// TODO: does this implementation make sense? (perimeter)
|
||||
double pa = a->parameter_value (4);
|
||||
double pb = b->parameter_value (4);
|
||||
double pa = a->parameter_value (4).to_double ();
|
||||
double pb = b->parameter_value (4).to_double ();
|
||||
a->set_parameter_value (4, pa + pb);
|
||||
}
|
||||
|
||||
void serial (Device *a, Device *b) const
|
||||
{
|
||||
double va = a->parameter_value (0);
|
||||
double vb = b->parameter_value (0);
|
||||
double va = a->parameter_value (0).to_double ();
|
||||
double vb = b->parameter_value (0).to_double ();
|
||||
a->set_parameter_value (0, va + vb);
|
||||
|
||||
// parallel length is sum of both, width is the one that gives the same value of resistance
|
||||
|
|
@ -158,22 +158,22 @@ public:
|
|||
// R1 = L1/W1
|
||||
// R2 = L2/W2
|
||||
// -> W = ((L1+L2)*W1*W2)/(W1*L2+W2*L1)
|
||||
double l1 = a->parameter_value (1);
|
||||
double w1 = a->parameter_value (2);
|
||||
double l2 = b->parameter_value (1);
|
||||
double w2 = b->parameter_value (2);
|
||||
double l1 = a->parameter_value (1).to_double ();
|
||||
double w1 = a->parameter_value (2).to_double ();
|
||||
double l2 = b->parameter_value (1).to_double ();
|
||||
double w2 = b->parameter_value (2).to_double ();
|
||||
a->set_parameter_value (1, l1 + l2);
|
||||
double dnom = (l2 * w1 + l1 * w2);
|
||||
if (fabs (dnom) > 1e-15) {
|
||||
a->set_parameter_value (2, (w1 * w2 * (l1 + l2)) / dnom);
|
||||
}
|
||||
|
||||
double aa = a->parameter_value (3);
|
||||
double ab = b->parameter_value (3);
|
||||
double aa = a->parameter_value (3).to_double ();
|
||||
double ab = b->parameter_value (3).to_double ();
|
||||
a->set_parameter_value (3, aa + ab);
|
||||
|
||||
double pa = a->parameter_value (4);
|
||||
double pb = b->parameter_value (4);
|
||||
double pa = a->parameter_value (4).to_double ();
|
||||
double pb = b->parameter_value (4).to_double ();
|
||||
a->set_parameter_value (4, pa + pb);
|
||||
}
|
||||
};
|
||||
|
|
@ -202,33 +202,33 @@ class CapacitorDeviceCombiner
|
|||
public:
|
||||
void serial (Device *a, Device *b) const
|
||||
{
|
||||
double va = a->parameter_value (0);
|
||||
double vb = b->parameter_value (0);
|
||||
double va = a->parameter_value (0).to_double ();
|
||||
double vb = b->parameter_value (0).to_double ();
|
||||
a->set_parameter_value (0, va + vb < 1e-30 ? 0.0 : va * vb / (va + vb));
|
||||
|
||||
// TODO: does this implementation make sense?
|
||||
double aa = a->parameter_value (1);
|
||||
double ab = b->parameter_value (1);
|
||||
double aa = a->parameter_value (1).to_double ();
|
||||
double ab = b->parameter_value (1).to_double ();
|
||||
a->set_parameter_value (1, aa + ab);
|
||||
|
||||
// TODO: does this implementation make sense?
|
||||
double pa = a->parameter_value (2);
|
||||
double pb = b->parameter_value (2);
|
||||
double pa = a->parameter_value (2).to_double ();
|
||||
double pb = b->parameter_value (2).to_double ();
|
||||
a->set_parameter_value (2, pa + pb);
|
||||
}
|
||||
|
||||
void parallel (Device *a, Device *b) const
|
||||
{
|
||||
double va = a->parameter_value (0);
|
||||
double vb = b->parameter_value (0);
|
||||
double va = a->parameter_value (0).to_double ();
|
||||
double vb = b->parameter_value (0).to_double ();
|
||||
a->set_parameter_value (0, va + vb);
|
||||
|
||||
double aa = a->parameter_value (1);
|
||||
double ab = b->parameter_value (1);
|
||||
double aa = a->parameter_value (1).to_double ();
|
||||
double ab = b->parameter_value (1).to_double ();
|
||||
a->set_parameter_value (1, aa + ab);
|
||||
|
||||
double pa = a->parameter_value (2);
|
||||
double pb = b->parameter_value (2);
|
||||
double pa = a->parameter_value (2).to_double ();
|
||||
double pb = b->parameter_value (2).to_double ();
|
||||
a->set_parameter_value (2, pa + pb);
|
||||
}
|
||||
};
|
||||
|
|
@ -257,15 +257,15 @@ class InductorDeviceCombiner
|
|||
public:
|
||||
void parallel (Device *a, Device *b) const
|
||||
{
|
||||
double va = a->parameter_value (0);
|
||||
double vb = b->parameter_value (0);
|
||||
double va = a->parameter_value (0).to_double ();
|
||||
double vb = b->parameter_value (0).to_double ();
|
||||
a->set_parameter_value (0, va + vb < 1e-30 ? 0.0 : va * vb / (va + vb));
|
||||
}
|
||||
|
||||
void serial (Device *a, Device *b) const
|
||||
{
|
||||
double va = a->parameter_value (0);
|
||||
double vb = b->parameter_value (0);
|
||||
double va = a->parameter_value (0).to_double ();
|
||||
double vb = b->parameter_value (0).to_double ();
|
||||
a->set_parameter_value (0, va + vb);
|
||||
}
|
||||
};
|
||||
|
|
@ -284,8 +284,8 @@ public:
|
|||
// only parallel diodes can be combined and their areas will add
|
||||
if (na1 == nb1 && na2 == nb2) {
|
||||
|
||||
a->set_parameter_value (0, a->parameter_value (0) + b->parameter_value (0));
|
||||
a->set_parameter_value (1, a->parameter_value (1) + b->parameter_value (1));
|
||||
a->set_parameter_value (0, a->parameter_value (0).to_double () + b->parameter_value (0).to_double ());
|
||||
a->set_parameter_value (1, a->parameter_value (1).to_double () + b->parameter_value (1).to_double ());
|
||||
|
||||
a->join_terminals (0, b, 0);
|
||||
a->join_terminals (1, b, 1);
|
||||
|
|
@ -340,11 +340,11 @@ public:
|
|||
|
||||
void combine_parameters (Device *a, Device *b) const
|
||||
{
|
||||
a->set_parameter_value (1, a->parameter_value (1) + b->parameter_value (1));
|
||||
a->set_parameter_value (2, a->parameter_value (2) + b->parameter_value (2));
|
||||
a->set_parameter_value (3, a->parameter_value (3) + b->parameter_value (3));
|
||||
a->set_parameter_value (4, a->parameter_value (4) + b->parameter_value (4));
|
||||
a->set_parameter_value (5, a->parameter_value (5) + b->parameter_value (5));
|
||||
a->set_parameter_value (1, a->parameter_value (1).to_double () + b->parameter_value (1).to_double ());
|
||||
a->set_parameter_value (2, a->parameter_value (2).to_double () + b->parameter_value (2).to_double ());
|
||||
a->set_parameter_value (3, a->parameter_value (3).to_double () + b->parameter_value (3).to_double ());
|
||||
a->set_parameter_value (4, a->parameter_value (4).to_double () + b->parameter_value (4).to_double ());
|
||||
a->set_parameter_value (5, a->parameter_value (5).to_double () + b->parameter_value (5).to_double ());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -423,9 +423,9 @@ public:
|
|||
|
||||
void combine_parameters (Device *a, Device *b) const
|
||||
{
|
||||
a->set_parameter_value (DeviceClassBJT3Transistor::param_id_AE, a->parameter_value (DeviceClassBJT3Transistor::param_id_AE) + b->parameter_value (DeviceClassBJT3Transistor::param_id_AE));
|
||||
a->set_parameter_value (DeviceClassBJT3Transistor::param_id_PE, a->parameter_value (DeviceClassBJT3Transistor::param_id_PE) + b->parameter_value (DeviceClassBJT3Transistor::param_id_PE));
|
||||
a->set_parameter_value (DeviceClassBJT3Transistor::param_id_NE, a->parameter_value (DeviceClassBJT3Transistor::param_id_NE) + b->parameter_value (DeviceClassBJT3Transistor::param_id_NE));
|
||||
a->set_parameter_value (DeviceClassBJT3Transistor::param_id_AE, a->parameter_value (DeviceClassBJT3Transistor::param_id_AE).to_double () + b->parameter_value (DeviceClassBJT3Transistor::param_id_AE).to_double ());
|
||||
a->set_parameter_value (DeviceClassBJT3Transistor::param_id_PE, a->parameter_value (DeviceClassBJT3Transistor::param_id_PE).to_double () + b->parameter_value (DeviceClassBJT3Transistor::param_id_PE).to_double ());
|
||||
a->set_parameter_value (DeviceClassBJT3Transistor::param_id_NE, a->parameter_value (DeviceClassBJT3Transistor::param_id_NE).to_double () + b->parameter_value (DeviceClassBJT3Transistor::param_id_NE).to_double ());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -639,7 +639,7 @@ DeviceClassMOS3Transistor::is_drain_terminal (size_t tid) const
|
|||
bool
|
||||
DeviceClassMOS3Transistor::lengths_are_identical (const db::Device *a, const db::Device *b)
|
||||
{
|
||||
return (fabs (a->parameter_value (DeviceClassMOS3Transistor::param_id_L) - b->parameter_value (DeviceClassMOS3Transistor::param_id_L)) < 1e-6);
|
||||
return (fabs (a->parameter_value (DeviceClassMOS3Transistor::param_id_L).to_double () - b->parameter_value (DeviceClassMOS3Transistor::param_id_L).to_double ()) < 1e-6);
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
|||
|
|
@ -460,7 +460,7 @@ private:
|
|||
}
|
||||
|
||||
std::map<size_t, std::map<unsigned int, std::set<db::NetShape> > > geometry;
|
||||
std::map<size_t, double> parameters;
|
||||
std::map<size_t, tl::Variant> parameters;
|
||||
};
|
||||
|
||||
typedef std::map<unsigned int, std::vector<db::NetShape> > geometry_per_layer_type;
|
||||
|
|
|
|||
|
|
@ -587,8 +587,11 @@ NetlistSpiceReaderDelegate::apply_parameter_scaling (db::Device *device) const
|
|||
|
||||
const std::vector<db::DeviceParameterDefinition> &pd = device->device_class ()->parameter_definitions ();
|
||||
for (auto i = pd.begin (); i != pd.end (); ++i) {
|
||||
double pv = device->parameter_value (i->id ());
|
||||
device->set_parameter_value (i->id (), pv / i->si_scaling () * pow (m_options.scale, i->geo_scaling_exponent ()));
|
||||
const tl::Variant &pv = device->parameter_value (i->id ());
|
||||
// @@@ only in case of double???
|
||||
if (pv.is_double ()) {
|
||||
device->set_parameter_value (i->id (), pv.to_double () / i->si_scaling () * pow (m_options.scale, i->geo_scaling_exponent ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -231,12 +231,13 @@ std::string NetlistSpiceWriterDelegate::format_params (const db::Device &dev, si
|
|||
double sis = i->si_scaling ();
|
||||
os << " " << i->name () << "=";
|
||||
// for compatibility
|
||||
// @@@ other parameter types!!!
|
||||
if (fabs (sis * 1e6 - 1.0) < 1e-10) {
|
||||
os << tl::to_string (dev.parameter_value (i->id ())) << "U";
|
||||
} else if (fabs (sis * 1e12 - 1.0) < 1e-10) {
|
||||
os << tl::to_string (dev.parameter_value (i->id ())) << "P";
|
||||
} else {
|
||||
os << tl::to_string (dev.parameter_value (i->id ()) * sis);
|
||||
os << tl::to_string (dev.parameter_value (i->id ()).to_double () * sis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -432,17 +432,18 @@ Class<db::Device> decl_dbDevice (decl_dbNetlistObject, "db", "Device",
|
|||
"@brief Disconnects the given terminal from any net.\n"
|
||||
"This version accepts a terminal name. If the name is not a valid terminal name, an exception is raised."
|
||||
) +
|
||||
gsi::method ("parameter", (double (db::Device::*) (size_t) const) &db::Device::parameter_value, gsi::arg ("param_id"),
|
||||
gsi::method ("parameter", (const tl::Variant &(db::Device::*) (size_t) const) &db::Device::parameter_value, gsi::arg ("param_id"),
|
||||
"@brief Gets the parameter value for the given parameter ID."
|
||||
) +
|
||||
gsi::method ("set_parameter", (void (db::Device::*) (size_t, double)) &db::Device::set_parameter_value, gsi::arg ("param_id"), gsi::arg ("value"),
|
||||
gsi::method ("set_parameter", (void (db::Device::*) (size_t, const tl::Variant &)) &db::Device::set_parameter_value, gsi::arg ("param_id"), gsi::arg ("value"),
|
||||
"@brief Sets the parameter value for the given parameter ID."
|
||||
) +
|
||||
gsi::method ("parameter", (double (db::Device::*) (const std::string &) const) &db::Device::parameter_value, gsi::arg ("param_name"),
|
||||
gsi::method ("parameter", (const tl::Variant &(db::Device::*) (const std::string &) const) &db::Device::parameter_value, gsi::arg ("param_name"),
|
||||
"@brief Gets the parameter value for the given parameter name.\n"
|
||||
"If the parameter name is not valid, an exception is thrown."
|
||||
) +
|
||||
gsi::method ("set_parameter", (void (db::Device::*) (const std::string &, double)) &db::Device::set_parameter_value, gsi::arg ("param_name"), gsi::arg ("value"),
|
||||
// @@@ set_parameter_create!!!
|
||||
gsi::method ("set_parameter", (void (db::Device::*) (const std::string &, const tl::Variant &)) &db::Device::set_parameter_value, gsi::arg ("param_name"), gsi::arg ("value"),
|
||||
"@brief Sets the parameter value for the given parameter name.\n"
|
||||
"If the parameter name is not valid, an exception is thrown."
|
||||
),
|
||||
|
|
|
|||
|
|
@ -507,20 +507,20 @@ TEST(4_CircuitDevices)
|
|||
EXPECT_EQ (c->device_by_name (d2a->name ()) == d2a, true);
|
||||
EXPECT_EQ (c->device_by_name ("doesnt_exist") == 0, true);
|
||||
|
||||
EXPECT_EQ (d1->parameter_value (0), 1.0);
|
||||
EXPECT_EQ (d1->parameter_value (1), 2.0);
|
||||
EXPECT_EQ (d2a->parameter_value (0), 2.0);
|
||||
EXPECT_EQ (d2a->parameter_value (1), 1.0);
|
||||
EXPECT_EQ (d1->parameter_value (0).to_double (), 1.0);
|
||||
EXPECT_EQ (d1->parameter_value (1).to_double (), 2.0);
|
||||
EXPECT_EQ (d2a->parameter_value (0).to_double (), 2.0);
|
||||
EXPECT_EQ (d2a->parameter_value (1).to_double (), 1.0);
|
||||
d1->set_parameter_value (1, 1.5);
|
||||
EXPECT_EQ (d1->parameter_value (0), 1.0);
|
||||
EXPECT_EQ (d1->parameter_value (1), 1.5);
|
||||
EXPECT_EQ (d1->parameter_value (0).to_double (), 1.0);
|
||||
EXPECT_EQ (d1->parameter_value (1).to_double (), 1.5);
|
||||
d1->set_parameter_value (0, 0.5);
|
||||
EXPECT_EQ (d1->parameter_value (0), 0.5);
|
||||
EXPECT_EQ (d1->parameter_value (1), 1.5);
|
||||
EXPECT_EQ (d1->parameter_value (0).to_double (), 0.5);
|
||||
EXPECT_EQ (d1->parameter_value (1).to_double (), 1.5);
|
||||
|
||||
d2a->set_parameter_value (0, -1.0);
|
||||
EXPECT_EQ (d2a->parameter_value (0), -1.0);
|
||||
EXPECT_EQ (d2a->parameter_value (1), 1.0);
|
||||
EXPECT_EQ (d2a->parameter_value (0).to_double (), -1.0);
|
||||
EXPECT_EQ (d2a->parameter_value (1).to_double (), 1.0);
|
||||
|
||||
EXPECT_EQ (netlist2 (*c),
|
||||
"c:\n"
|
||||
|
|
|
|||
|
|
@ -262,6 +262,16 @@ std::string formatted_value (double v)
|
|||
}
|
||||
}
|
||||
|
||||
static
|
||||
std::string formatted_value (const tl::Variant &v)
|
||||
{
|
||||
if (v.is_double ()) {
|
||||
return formatted_value (v.to_double ());
|
||||
} else {
|
||||
return v.to_string ();
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
std::string device_parameter_string (const db::Device *device)
|
||||
{
|
||||
|
|
@ -277,7 +287,6 @@ std::string device_parameter_string (const db::Device *device)
|
|||
|
||||
for (std::vector<db::DeviceParameterDefinition>::const_iterator p = pd.begin (); p != pd.end (); ++p) {
|
||||
if (p->is_primary ()) {
|
||||
double v = device->parameter_value (p->id ());
|
||||
if (first) {
|
||||
s += " [";
|
||||
} else {
|
||||
|
|
@ -285,7 +294,7 @@ std::string device_parameter_string (const db::Device *device)
|
|||
}
|
||||
s += p->name ();
|
||||
s += "=";
|
||||
s += formatted_value (v);
|
||||
s += formatted_value (device->parameter_value (p->id ()));
|
||||
term = "]";
|
||||
first = false;
|
||||
}
|
||||
|
|
@ -294,8 +303,7 @@ std::string device_parameter_string (const db::Device *device)
|
|||
bool first_sec = true;
|
||||
|
||||
for (std::vector<db::DeviceParameterDefinition>::const_iterator p = pd.begin (); p != pd.end (); ++p) {
|
||||
double v = device->parameter_value (p->id ());
|
||||
std::string vs = formatted_value (v);
|
||||
std::string vs = formatted_value (device->parameter_value (p->id ()));
|
||||
std::string vs_def = formatted_value (p->default_value ());
|
||||
if (! p->is_primary () && vs != vs_def) {
|
||||
if (first) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue