diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..ff0a23f9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.associations": { + "cmath": "cpp", + "algorithm": "cpp", + "format": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xutility": "cpp", + "system_error": "cpp" + } +} \ No newline at end of file diff --git a/include/gdstk/layername.hpp b/include/gdstk/layername.hpp new file mode 100644 index 00000000..c095510a --- /dev/null +++ b/include/gdstk/layername.hpp @@ -0,0 +1,90 @@ +/* +Copyright 2020 Lucas Heitzmann Gabrielli. +This file is part of gdstk, distributed under the terms of the +Boost Software License - Version 1.0. See the accompanying +LICENSE file or +*/ + +#ifndef GDSTK_HEADER_LAYERNAME +#define GDSTK_HEADER_LAYERNAME + +#define __STDC_FORMAT_MACROS 1 +#define _USE_MATH_DEFINES + +#include +#include + +#include "utils.hpp" +// #include "oasis.hpp" + +namespace gdstk { + +struct OasisStream; +struct OasisState; + +// based roughly on property.hpp, please see that file for someone who knows what they're doing + +// the spec lists tag types 11 and 12 which are representing layer and textlayer data respectively +enum struct LayerNameType : uint8_t { + DATA = static_cast(OasisRecord::LAYERNAME_DATA), + TEXT = static_cast(OasisRecord::LAYERNAME_TEXT), +}; +// only bound type index 4 (bound to bound) requires two bound values +struct Interval { + OasisInterval type; + union { + uint32_t bound; + struct { + uint32_t bound_a; + uint32_t bound_b; + }; + }; +}; + +// Properties are stored as a NULL-terminated linked list. Their name is a +// NULL-terminated string. Property names in the OASIS should be strings of +// characters within the range 0x21 and 0x7E with at least 1 character. +struct LayerName { + LayerNameType type; + char* name; + Interval* LayerInterval; + Interval* TypeInterval; + LayerName* next; +}; + +// The set_layername functions add layer mappings to the library with the +// given name. A new one is created if none exists or create_new == true. +// New values are added to the start of the value list, so in the OASIS file, +// they will appear in reverse of the insertion order (last added will appear +// first). The NULL byte at the end of string is NOT included in the layer +// name. +void set_layername(LayerName*& layer_names, const char* name, uint32_t layertype, + uint32_t datatype); +void set_textlayername(LayerName*& layer_names, const char* name, uint32_t layertype, + uint32_t datatype); +void set_layername(LayerName*& layer_names, const char* name, Interval* layertype, + Interval* datatype); +void set_textlayername(LayerName*& layer_names, const char* name, Interval* layertype, + Interval* datatype); +// Still thinking about how to deal with bounds that aren't type 3 + +// removes first instance of layername present in the linked list or all occourances if +// all occourences is set +void remove_layername(LayerName*& layer_names, const char* name, bool all_occurences); + +LayerName* get_mapped_layers(LayerName* layer_names, const char* name); + +bool layername_contains_layer_and_type(const LayerName* layer_name, uint32_t layertype, + uint32_t datatype); + +LayerName* get_names_from_layers(LayerName* layer_names, uint32_t layertype, uint32_t datatype); + +void layernames_clear(LayerName*& layer_names); + +// These functions output the properties in the OASIS format. They +// are not supposed to be called by the user. +ErrorCode layernames_to_oas(const LayerName* layer_names, OasisStream& out, OasisState& state); + +} // namespace gdstk + +#endif diff --git a/include/gdstk/library.hpp b/include/gdstk/library.hpp index 412e3414..2f85c766 100644 --- a/include/gdstk/library.hpp +++ b/include/gdstk/library.hpp @@ -16,6 +16,7 @@ LICENSE file or #include "array.hpp" #include "cell.hpp" +#include "layername.hpp" namespace gdstk { @@ -38,6 +39,8 @@ struct Library { Array rawcell_array; Property* properties; + + LayerName* layer_names; // Used by the python interface to store the associated PyObject* (if any). // No functions in gdstk namespace should touch this value! @@ -57,6 +60,7 @@ struct Library { cell_array.clear(); rawcell_array.clear(); properties_clear(properties); + layernames_clear(layer_names); } // Clear and free the memory of the whole library (this should be used with diff --git a/python/docstrings.cpp b/python/docstrings.cpp index fda7924f..da4dd9ec 100644 --- a/python/docstrings.cpp +++ b/python/docstrings.cpp @@ -2910,6 +2910,9 @@ PyDoc_STRVAR(library_object_layers_and_texttypes_doc, R"!(layers_and_texttypes() Return a set of tuples with the layer and text types in the library.)!"); +PyDoc_STRVAR(library_object_layer_datatype_names_doc, R"!(layer_datatype_names() -> list +Return a list of tuples with name, layer range and datatype ranges library.)!"); + PyDoc_STRVAR(library_object_remap_doc, R"!(remap(layer_type_map) -> self Remap layers and data/text types for all elements in this library. diff --git a/python/library_object.cpp b/python/library_object.cpp index 3a620e06..664564f7 100644 --- a/python/library_object.cpp +++ b/python/library_object.cpp @@ -334,6 +334,11 @@ static PyObject* library_object_layers_and_texttypes(LibraryObject* self, PyObje return result; } +static PyObject* library_object_layer_datatype_names(LibraryObject* self, PyObject*) { + PyObject* result = build_layername_list(tags); + return result; +} + static PyObject* library_object_remap(LibraryObject* self, PyObject* args, PyObject* kwds) { PyObject* py_map = NULL; const char* keywords[] = {"layer_type_map", NULL}; @@ -492,6 +497,8 @@ static PyMethodDef library_object_methods[] = { library_object_layers_and_datatypes_doc}, {"layers_and_texttypes", (PyCFunction)library_object_layers_and_texttypes, METH_NOARGS, library_object_layers_and_texttypes_doc}, + {"layer_datatype_names", (PyCFunction)library_object_layer_datatype_names, METH_NOARGS, + library_object_layer_datatype_names_doc}, {"remap", (PyCFunction)library_object_remap, METH_VARARGS | METH_KEYWORDS, library_object_remap_doc}, {"write_gds", (PyCFunction)library_object_write_gds, METH_VARARGS | METH_KEYWORDS, diff --git a/python/parsing.cpp b/python/parsing.cpp index baa5e9cd..790c979a 100644 --- a/python/parsing.cpp +++ b/python/parsing.cpp @@ -633,3 +633,81 @@ static PyObject* build_tag_set(const Set& tags) { } return result; } + +// not sure if range or slcie is better, using range since you can use the +// in keword with it to determine membership +PyObject* create_range_object(long start, long stop, long step) { + // Import the built-in "range" type + + PyObject* range_func = PyObject_GetAttrString(PyEval_GetBuiltins(), "range"); + if (!range_func || !PyCallable_Check(range_func)) { + PyErr_SetString(PyExc_RuntimeError, "Could not find built-in 'range'"); + Py_XDECREF(range_func); + return NULL; + } + + // Call range(start, stop, step) + PyObject* range_args = Py_BuildValue("(lll)", start, stop, step); + PyObject* range_obj = PyObject_CallObject(range_func, range_args); + + // Cleanup + Py_DECREF(range_func); + Py_DECREF(range_args); + + return range_obj; // May be nullptr if call failed +} + +static PyObject* build_layer_interval(const Interval* interval){ + + if (interval == NULL) { + PyErr_SetString(PyExc_RuntimeError, "Invalid interval."); + return NULL; + } + if (interval->type == OasisInterval::AllValues){ + return create_range_object(0,65536,1); + } else if (interval->type == OasisInterval::UpperBound){ + return create_range_object(0,interval->bound,1); + } else if (interval->type == OasisInterval::LowerBound){ + return create_range_object(interval->bound,65536,1); + } else if (interval->type == OasisInterval::SingleValue){ + return create_range_object(interval->bound,interval->bound+1,1); + } else if (interval->type == OasisInterval::Bounded){ + return create_range_object(interval->bound_a,interval->bound_b+1,1); + } else { + PyErr_SetString(PyExc_RuntimeError, "Unknown interval type."); + return NULL; + } +} + + + +static PyObject* build_layername_list(const LayerName* layer_names) { + PyObject* result = PyList_New(NULL); + if (!result) { + PyErr_SetString(PyExc_RuntimeError, "Unable to create dict object."); + return NULL; + } + while(layer_names){ + PyObject* entry = PyTuple_New(3); + if (!entry) { + PyErr_SetString(PyExc_RuntimeError, "Unable to create (name, layer, type) tuple."); + Py_DECREF(result); + return NULL; + } + // this might need to be a byte string instead of unicode + PyTuple_SET_ITEM(entry, 0, PyUnicode_FromString(layer_names->name)); + PyTuple_SET_ITEM(entry, 1, build_layer_interval(&layer_names->layer)); + PyTuple_SET_ITEM(entry, 2, build_layer_interval(&layer_names->datatype)); + if (PyList_Append(result, entry) < 0) { + PyErr_SetString(PyExc_RuntimeError, "Unable to add item to list."); + Py_DECREF(entry); + Py_DECREF(result); + return NULL; + } + Py_DECREF(entry); + layer_names = layer_names->next; + } + return result; +} + + \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a1da39ce..d2ad951d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,6 +34,7 @@ set(HEADER_LIST "${gdstk_SOURCE_DIR}/include/gdstk/gdstk.hpp" "${gdstk_SOURCE_DIR}/include/gdstk/gdswriter.hpp" "${gdstk_SOURCE_DIR}/include/gdstk/label.hpp" + "${gdstk_SOURCE_DIR}/include/gdstk/layername.hpp" "${gdstk_SOURCE_DIR}/include/gdstk/library.hpp" "${gdstk_SOURCE_DIR}/include/gdstk/map.hpp" "${gdstk_SOURCE_DIR}/include/gdstk/oasis.hpp" @@ -59,6 +60,7 @@ set(SOURCE_LIST flexpath.cpp gdsii.cpp label.cpp + layername.cpp library.cpp oasis.cpp polygon.cpp diff --git a/src/layername.cpp b/src/layername.cpp new file mode 100644 index 00000000..257e68a1 --- /dev/null +++ b/src/layername.cpp @@ -0,0 +1,221 @@ +/* +Copyright 2020 Lucas Heitzmann Gabrielli. +This file is part of gdstk, distributed under the terms of the +Boost Software License - Version 1.0. See the accompanying +LICENSE file or +*/ + +#define __STDC_FORMAT_MACROS 1 +#define _USE_MATH_DEFINES + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// #include "layername.hpp" + +namespace gdstk { + + + +void set_layername(LayerName*& layer_names, const char* name, uint64_t layertype, + uint64_t datatype) { + Interval* layertype_interval = (Interval*)allocate_clear(sizeof(Interval)); + layertype_interval->type = OasisInterval::SingleValue; + layertype_interval->bound = layertype; + Interval* datatype_interval = (Interval*)allocate_clear(sizeof(Interval)); + datatype_interval->type = OasisInterval::SingleValue; + datatype_interval->bound = datatype; + set_layername(layer_names, name, layertype_interval, datatype_interval); +} +void set_textlayername(LayerName*& layer_names, const char* name, uint64_t layertype, + uint64_t datatype) { + Interval* layertype_interval = (Interval*)allocate_clear(sizeof(Interval)); + layertype_interval->type = OasisInterval::SingleValue; + layertype_interval->bound = layertype; + Interval* datatype_interval = (Interval*)allocate_clear(sizeof(Interval)); + datatype_interval->type = OasisInterval::SingleValue; + datatype_interval->bound = datatype; + set_textlayername(layer_names, name, layertype_interval, datatype_interval); +} + +void set_layername(LayerName*& layer_names, const char* name, Interval* layertype, + Interval* datatype) { + LayerName* ln = (LayerName*)allocate_clear(sizeof(LayerName)); + ln->type = LayerNameType::DATA; + ln->name = copy_string(name, NULL); + ln->LayerInterval = layertype; + ln->TypeInterval = datatype; + ln->next = layer_names; + layer_names = ln; +} +void set_textlayername(LayerName*& layer_names, const char* name, Interval* layertype, + Interval* datatype) { + LayerName* ln = (LayerName*)allocate_clear(sizeof(LayerName)); + ln->type = LayerNameType::TEXT; + ln->name = copy_string(name, NULL); + ln->LayerInterval = layertype; + ln->TypeInterval = datatype; + ln->next = layer_names; + layer_names = ln; +} + +void remove_layername(LayerName*& layer_names, const char* target_name, bool all_occurences) { + if (layer_names == NULL) return; + LayerName* current = layer_names; + LayerName* previous = NULL; + while (current) { + if (strcmp(current->name, target_name) == 0) { + if (previous) { + previous->next = current->next; + } else { + layer_names = current->next; // Update head if needed + } + free_allocation(current->name); + free_allocation(current->LayerInterval); + free_allocation(current->TypeInterval); + LayerName* to_delete = current; + current = current->next; // Move to next before deleting + free_allocation(to_delete); + if (!all_occurences) { + return; // Exit if only the first occurrence should be removed + } + } else { + previous = current; + current = current->next; + } + } +} +// this is suspect as it only returns the first match +// I may have to create a new data structure to hold multiple matches +LayerName* get_mapped_layers(LayerName* layer_names, const char* name) { + while (layer_names && strcmp(layer_names->name, name) != 0) layer_names = layer_names->next; + return layer_names; +} + +bool layername_contains_layer_and_type(const LayerName* layer_name, uint64_t layertype, + uint64_t datatype) { + bool matches = false; + + // Check layertype + switch (layer_name->LayerInterval->type) { + case OasisInterval::AllValues: + matches = true; + break; + case OasisInterval::UpperBound: + matches = (layertype <= layer_name->LayerInterval->bound); + break; + case OasisInterval::LowerBound: + matches = (layertype >= layer_name->LayerInterval->bound); + break; + case OasisInterval::SingleValue: + matches = (layer_name->LayerInterval->bound == layertype); + break; + case OasisInterval::Bounded: + matches = (layertype >= layer_name->LayerInterval->bound_a && + layertype <= layer_name->LayerInterval->bound_b); + break; + } + if (matches == false) { + return false; + } + switch (layer_name->TypeInterval->type) { + case OasisInterval::AllValues: + matches = true; + break; + case OasisInterval::UpperBound: + matches = (datatype <= layer_name->TypeInterval->bound); + break; + case OasisInterval::LowerBound: + matches = (datatype >= layer_name->TypeInterval->bound); + break; + case OasisInterval::SingleValue: + matches = (layer_name->TypeInterval->bound == datatype); + break; + case OasisInterval::Bounded: + matches = (layertype >= layer_name->TypeInterval->bound_a && + layertype <= layer_name->TypeInterval->bound_b); + break; + } + return matches; +} + +LayerName* get_names_from_layers(LayerName* layer_names, uint64_t layertype, uint64_t datatype) { + LayerName* result_head = NULL; + LayerName* result_tail = NULL; + + while (layer_names) { + // this might not need a deep copy since it just returning a result + // the dealocaiton might be messy though + if (layername_contains_layer_and_type(layer_names, layertype, datatype)) { + // Create a new LayerName node for the result list + LayerName* new_node = (LayerName*)allocate_clear(sizeof(LayerName)); + new_node->type = layer_names->type; + new_node->name = copy_string(layer_names->name, NULL); + new_node->LayerInterval = (Interval*)allocate_clear(sizeof(Interval)); + *(new_node->LayerInterval) = *(layer_names->LayerInterval); // Deep copy + new_node->TypeInterval = (Interval*)allocate_clear(sizeof(Interval)); + *(new_node->TypeInterval) = *(layer_names->TypeInterval); // Deep copy + new_node->next = NULL; + + // Append to the result list + if (result_tail) { + result_tail->next = new_node; + result_tail = new_node; + } else { + result_head = new_node; + result_tail = new_node; + } + } + } + return result_head; +} + +void layernames_clear(LayerName*& layer_names) { + while (layer_names) { + free_allocation(layer_names->name); + free_allocation(layer_names->LayerInterval); + free_allocation(layer_names->TypeInterval); + LayerName* next = layer_names->next; + free_allocation(layer_names); + layer_names = next; + } +} +// TODO figure out where this needs to be used +ErrorCode layernames_to_oas(const LayerName* layer_names, OasisStream& out, OasisState& state) { + while (layer_names) { + oasis_write_integer(out, (uint8_t)layer_names->type); + size_t name_length = strlen(layer_names->name); + oasis_write_unsigned_integer(out, (uint64_t)name_length); + oasis_write(layer_names->name, 1, name_length, out); + + // write layertype interval + oasis_putc((uint8_t)layer_names->LayerInterval->type, out); + if (layer_names->LayerInterval->type == OasisInterval::Bounded) { + oasis_write_unsigned_integer(out, layer_names->LayerInterval->bound_a); + oasis_write_unsigned_integer(out, layer_names->LayerInterval->bound_b); + } else if (layer_names->LayerInterval->type != OasisInterval::AllValues) { + oasis_write_unsigned_integer(out, layer_names->LayerInterval->bound); + } + + oasis_putc((uint8_t)layer_names->TypeInterval->type, out); + if (layer_names->TypeInterval->type == OasisInterval::Bounded) { + oasis_write_unsigned_integer(out, layer_names->TypeInterval->bound_a); + oasis_write_unsigned_integer(out, layer_names->TypeInterval->bound_b); + } else if (layer_names->TypeInterval->type != OasisInterval::AllValues) { + oasis_write_unsigned_integer(out, layer_names->TypeInterval->bound); + } + + layer_names = layer_names->next; + } + return ErrorCode::NoError; +} + +} // namespace gdstk diff --git a/src/library.cpp b/src/library.cpp index 381bd493..50db6790 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -442,7 +442,10 @@ ErrorCode Library::write_oas(const char* filename, double circle_tolerance, state.scaling = unit / precision; oasis_write_real(out, 1e-6 / precision); oasis_putc(1, out); // flag indicating that table-offsets will be stored in the END record - + + uint64_t layer_name_offset = layer_names ? ftell(out.file) : 0; + layernames_to_oasis(layer_names,out,state) + if (state.config_flags & OASIS_CONFIG_PROPERTY_TOP_LEVEL) { remove_property(properties, s_top_level_property_name, true); Array top_cells = {}; @@ -881,7 +884,7 @@ ErrorCode Library::write_oas(const char* filename, double circle_tolerance, oasis_putc(1, out); oasis_write_unsigned_integer(out, prop_string_offset); oasis_putc(1, out); - oasis_putc(0, out); // LAYERNAME table + oasis_write_unsigned_integer(out, layer_name_offset); // LAYERNAME table oasis_putc(1, out); oasis_putc(0, out); // XNAME table @@ -1171,7 +1174,7 @@ Library read_gds(const char* filename, double unit, double tolerance, const Set< if (reference->repetition.type != RepetitionType::None) { Repetition* repetition = &reference->repetition; if (reference->rotation == 0 && !reference->x_reflection && - data32[3] == 0 && data32[4] == 0) { + data32[3] == 0 && data32[4] == 0) { repetition->spacing.x = (factor * data32[2] - origin.x) / repetition->columns; repetition->spacing.y = @@ -1695,16 +1698,113 @@ Library read_oas(const char* filename, double unit, double tolerance, ErrorCode* next_property = &property_value_table[ref_number].properties; } break; case OasisRecord::LAYERNAME_DATA: + uint8_t* name = oasis_read_string(in, true, len); + Interval* layertype_interval = (Interval*)allocate_clear(sizeof(Interval)); + layertype_interval->type = (OasisInterval)oasis_read_unsigned_integer(in); + + switch (layertype_interval->type) { + case OasisInterval::UpperBound: + case OasisInterval::LowerBound: + case OasisInterval::SingleValue: { + layertype_interval->bound = (uint32_t)oasis_read_unsigned_integer(in); + + } break; + case OasisInterval::Bounded: { + layertype_interval->bound_a = (uint32_t)oasis_read_unsigned_integer(in); + layertype_interval->bound_b = (uint32_t)oasis_read_unsigned_integer(in); + } break; + case OasisInterval::AllValues: + break; + default: + if (error_logger) + fprintf( + error_logger, + "[GDSTK] Unsupported layertype interval type %u in LAYERNAME_DATA record.\n", + layertype_interval->type); + if (error_code) *error_code = ErrorCode::UnsupportedRecord; + } + Interval* datatype_interval = (Interval*)allocate_clear(sizeof(Interval)); + datatype_interval->type = (OasisInterval)oasis_read_unsigned_integer(in); + switch (datatype_interval->type) { + case OasisInterval::UpperBound: + case OasisInterval::LowerBound: + case OasisInterval::SingleValue: { + datatype_interval->bound = (uint32_t)oasis_read_unsigned_integer(in); + + } break; + case OasisInterval::Bounded: { + datatype_interval->bound_a = (uint32_t)oasis_read_unsigned_integer(in); + datatype_interval->bound_b = (uint32_t)oasis_read_unsigned_integer(in); + } break; + case OasisInterval::AllValues: + break; + // handle default case with error + default: + if (error_logger) + fprintf( + error_logger, + "[GDSTK] Unsupported datatype interval type %u in LAYERNAME_DATA record.\n", + datatype_interval->type); + if (error_code) *error_code = ErrorCode::UnsupportedRecord; + } + set_layername(library.layer_names, (char*)name, layertype_interval, + datatype_interval); + free_allocation(name); + break; + case OasisRecord::LAYERNAME_TEXT: - // Unused record - free_allocation(oasis_read_string(in, false, len)); - for (uint32_t i = 2; i > 0; i--) { - uint64_t type = oasis_read_unsigned_integer(in); - if (type > 0) { - if (type == 4) oasis_read_unsigned_integer(in); - oasis_read_unsigned_integer(in); - } - } + uint8_t* name = oasis_read_string(in, true, len); + Interval* layertype_interval = (Interval*)allocate_clear(sizeof(Interval)); + layertype_interval->type = (OasisInterval)oasis_read_unsigned_integer(in); + + switch (layertype_interval->type) { + case OasisInterval::UpperBound: + case OasisInterval::LowerBound: + case OasisInterval::SingleValue: { + layertype_interval->bound = (uint32_t)oasis_read_unsigned_integer(in); + + } break; + case OasisInterval::Bounded: { + layertype_interval->bound_a = (uint32_t)oasis_read_unsigned_integer(in); + layertype_interval->bound_b = (uint32_t)oasis_read_unsigned_integer(in); + } break; + case OasisInterval::AllValues: + break; + default: + if (error_logger) + fprintf( + error_logger, + "[GDSTK] Unsupported layertype interval type %u in LAYERNAME_DATA record.\n", + layertype_interval->type); + if (error_code) *error_code = ErrorCode::UnsupportedRecord; + } + Interval* datatype_interval = (Interval*)allocate_clear(sizeof(Interval)); + datatype_interval->type = (OasisInterval)oasis_read_unsigned_integer(in); + switch (datatype_interval->type) { + case OasisInterval::UpperBound: + case OasisInterval::LowerBound: + case OasisInterval::SingleValue: { + datatype_interval->bound = (uint32_t)oasis_read_unsigned_integer(in); + + } break; + case OasisInterval::Bounded: { + datatype_interval->bound_a = (uint32_t)oasis_read_unsigned_integer(in); + datatype_interval->bound_b = (uint32_t)oasis_read_unsigned_integer(in); + } break; + case OasisInterval::AllValues: + break; + // handle default case with error + default: + if (error_logger) + fprintf( + error_logger, + "[GDSTK] Unsupported datatype interval type %u in LAYERNAME_DATA record.\n", + datatype_interval->type); + if (error_code) *error_code = ErrorCode::UnsupportedRecord; + } + set_text_layername(library.layer_names, (char*)name, layertype_interval, + datatype_interval); + free_allocation(name); break; case OasisRecord::CELL_REF_NUM: case OasisRecord::CELL: {