diff --git a/py3dm/__init__.py b/py3dm/__init__.py index bfe6373..ff5d19d 100644 --- a/py3dm/__init__.py +++ b/py3dm/__init__.py @@ -3,6 +3,7 @@ Geometry, Layer, LayerTable, + LayerView, Line, LineCurve, ModelComponent, @@ -12,8 +13,9 @@ OpenNURBSObject, Model, PlotColorSource, - PointGeometry, + Point, PointTable, + PointView, Point3d, TextLog ) diff --git a/py3dm/__init__.pyi b/py3dm/__init__.pyi index b32a61a..bc8a42b 100644 --- a/py3dm/__init__.pyi +++ b/py3dm/__init__.pyi @@ -83,6 +83,32 @@ class CurveTable: """ ... + def get_by_uuid_exclusive(self, object_uuid: UUID) -> LineCurve | None: + """Returns the object with the given ``object_uuid`` or ``None`` if + ``object_uuid`` is not found. + """ + ... + + +class CurveView: + """Tiny wrapper to read-only access `Curve` (``ON_Curve``) objects. + """ + def get_uuid(self) -> UUID: + """Returns the object UUID. + """ + ... + + def is_closed(self) -> bool: + """`True` if the curve is closed (either curve has clamped end knots + and euclidean location of start CV = euclidean location of end CV, or + curve is periodic. + """ + ... + + def is_linear(self) -> bool: ... + + def is_Valid(self, text_log: TextLog) -> bool: ... + class Geometry(OpenNURBSObject): """Python bindings for the openNURBS ``ON_Geometry`` class. @@ -130,9 +156,6 @@ class Layer(ModelComponent): @color.setter def color(self, color: tuple[int, int, int, int]) -> None: ... - @property - def full_path(self) -> str: ... - @property def iges_level(self) -> int: ... @iges_level.setter @@ -242,9 +265,9 @@ class LayerTable: ``LayerTable`` does not own the underlying data; it operates on the associated ``ONX_Model`` instance. """ - def __getitem__(self, index: int) -> Layer: ... + def __getitem__(self, index: int) -> LayerView: ... - def __iter__(self) -> Iterator[Layer]: ... + def __iter__(self) -> Iterator[LayerView]: ... def __len__(self) -> int: ... @@ -290,9 +313,9 @@ class LayerTable: """ ... - def get_by_index(self, layer_index: int) -> Layer | None: - """Returns the ``Layer`` if the given ``index`` is found in the table, - ``None`` otherwise. + def get_by_index(self, layer_index: int) -> LayerView | None: + """Returns a view of the ``Layer`` if the given ``index`` is found in + the table, ``None`` otherwise. Raises ------ @@ -301,13 +324,19 @@ class LayerTable: """ ... - def get_by_name(self, full_name: str) -> Layer | None: - """Returns the ``Layer`` if the given ``full_name`` is found in the - table, ``None`` otherwise. + def get_by_name(self, full_name: str) -> LayerView | None: + """Returns a view of the ``Layer`` if the given ``full_name`` is found + in the table, ``None`` otherwise. """ ... - def get_by_uuid(self, layer_uuid: UUID) -> Layer | None: + def get_by_uuid(self, layer_uuid: UUID) -> LayerView | None: + """Returns a view of the ``Layer`` if the given ``layer_uuid`` is found + in the table, ``None`` otherwise. + """ + ... + + def get_by_uuid_exclusive(self, layer_uuid: UUID) -> Layer | None: """Returns the ``Layer`` if the given ``layer_uuid`` is found in the table, ``None`` otherwise. """ @@ -338,6 +367,95 @@ class LayerTable: ... +class LayerView: + """Tiny wrapper to read-only access `Layer` (``ON_Layer``) objects. + """ + # properties + @property + def color(self) -> tuple[int, int, int, int]: ... + + @property + def iges_level(self) -> int: ... + + @property + def is_expanded(self) -> bool: ... + + @property + def is_locked(self) -> bool: ... + + @property + def is_visible(self) -> bool: ... + + @property + def layer_uuid(self) -> UUID: ... + + @property + def line_type_index(self) -> int: ... + + @property + def name(self) -> str: ... + + @property + def parent_uuid(self) -> UUID: ... + + @property + def parent_uuid_is_not_null(self) -> bool: ... + + @property + def parent_uuid_is_null(self) -> bool: ... + + @property + def path_separator(self) -> str: ... + + @property + def plot_color(self) -> tuple[int, int, int, int]: ... + + @property + def plot_weight(self) -> float: ... + + @property + def render_material_index(self) -> int: ... + + @overload + def index(self) -> int: + """Value of the runtime model component index attribute. + + Notes + ----- + If the component is in a model, then the index is unique for all + components of identical type in the model and is locked. If the index + has not been set, ``ON_UNSET_INT_INDEX`` is returned. The + ``index()`` value can change when saved in an archive (.3dm file). + Use the `get_uuid()` when you need to reference model components in an + archive. + """ + ... + + @overload + def index(self, unset_index_value: int) -> int: + """Value of the runtime model component index attribute. + + Parameters + ---------- + unset_index_value: int + Value to return if the index has not been set. + ``ON_UNSET_INT_INDEX`` or indices of default components are often + used for this parameter. + + Notes + ----- + If the component is in a model, then the index is unique for all + components of identical type in the model and is locked. If the index + has not been set, ``unset_index_value`` is returned. The + ``index()`` value can change when saved in an archive (.3dm file). + Use the `get_uuid()` when you need to reference model components in an + archive. + """ + ... + + def is_valid(self, text_log: TextLog | None = None) -> bool: ... + + class Line: """Python bindings for the openNURBS ``ON_Line`` class. """ @@ -616,7 +734,7 @@ class ModelComponent(OpenNURBSObject): Parameters ---------- - index: int + unset_index_value: int Value to return if the index has not been set. ``ON_UNSET_INT_INDEX`` or indices of default components are often used for this parameter. @@ -625,7 +743,7 @@ class ModelComponent(OpenNURBSObject): ----- If the component is in a model, then the index is unique for all components of identical type in the model and is locked. If the index - has not been set, ``ON_UNSET_INT_INDEX`` is returned. The + has not been set, ``unset_index_value`` is returned. The ``get_index()`` value can change when saved in an archive (.3dm file). Use the `get_uuid()` when you need to reference model components in an archive. @@ -894,7 +1012,7 @@ class PlotColorSource(Enum): from_parent = 3 -class PointGeometry(Geometry): +class Point(Geometry): """Python bindings for the openNURBS ``ON_Point`` class. """ # read-write member variables @@ -929,7 +1047,7 @@ class PointTable: associated ``ONX_Model`` instance. """ # dunder methods - def __iter__(self) -> Iterator[PointGeometry]: ... + def __iter__(self) -> Iterator[PointView]: ... def __len__(self) -> int: ... @@ -951,7 +1069,7 @@ class PointTable: @overload def add( self, - point: PointGeometry, + point: Point, obj_attr: None | ObjectAttributes = None ) -> UUID: """Returns the ``UUID`` of the point in case of successful addition, or @@ -978,9 +1096,79 @@ class PointTable: """ ... - def get_by_uuid(self, object_uuid: UUID) -> PointGeometry | None: + def get_by_uuid(self, object_uuid: UUID) -> PointView | None: + """Returns the object with the given ``object_uuid`` or ``None`` if + ``object_uuid`` is not found. + """ + ... + + def get_by_uuid_exclusive(self, object_uuid: UUID) -> Point | None: """Returns the object with the given ``object_uuid`` or ``None`` if ``object_uuid`` is not found. + + Notes + ----- + From opennurbs documentation + (``ON_ModelGeometryComponent::ExclusiveGeometry()``): + Get a pointer to geometry that can be used to modify the geometry. The + returned pointer is not shared at the time it is returned and will not + be shared until a copy of this ``ON_ModelGeometryComponent`` is + created. If this ``ON_ModelGeometryComponent`` is the only reference to + the geometry, then a pointer to the geometry is returned. Otherwise, + ``nullptr`` is returned. + """ + ... + + +class PointView: + """Tiny wrapper to read-only access `Point` (``ON_Point``) objects. + """ + # dunder methods + def __eq__(self, other: object) -> bool: ... + + def __ne__(self, other: object) -> bool: ... + + # properties + @property + def obj_uuid(self) -> UUID: ... + + @property + def x(self) -> float: ... + + @property + def y(self) -> float: ... + + @property + def z(self) -> float: ... + + # other methods + def distance_to(self, point: Point3d) -> float: + """Returns the distance between the two points. + """ + ... + + def is_coincident(self, point: Point3d) -> bool: + """In openNURBS points within ``ON_ZERO_TOLERANCE`` are generally + considered to be the same. + + Returns + ------- + is_coindent: bool + ``True`` if for each coordinate pair + ``|a - b| <= ON_ZERO_TOLERANCE`` or + ``|a - b| <= (abs(a) + abs(b)) * ON_RELATIVE_TOLERANCE``. + + Notes + ----- + ``ON_ZERO_TOLERANCE`` is set to 2.3283064365386962890625e-10 + + ``ON_RELATIVE_TOLERANCE`` is set to 2.27373675443232059478759765625e-13 + """ + ... + + def is_valid(self, text_log: TextLog | None = None) -> bool: + """Returns ``False`` if any coordinate is infinite, a nan, or + ``ON_UNSET_VALUE``. """ ... @@ -1033,6 +1221,12 @@ class Point3d: ``True`` if for each coordinate pair ``|a - b| <= ON_ZERO_TOLERANCE`` or ``|a - b| <= (abs(a) + abs(b)) * ON_RELATIVE_TOLERANCE``. + + Notes + ----- + ``ON_ZERO_TOLERANCE`` is set to 2.3283064365386962890625e-10 + + ``ON_RELATIVE_TOLERANCE`` is set to 2.27373675443232059478759765625e-13 """ ... diff --git a/src/bindings/bindings.cpp b/src/bindings/bindings.cpp index 86f0a9f..47cfe99 100644 --- a/src/bindings/bindings.cpp +++ b/src/bindings/bindings.cpp @@ -9,22 +9,21 @@ #include "geometry_bindings.h" #include "layer_bindings.h" #include "layer_table_bindings.h" +#include "layer_view_bindings.h" #include "line_bindings.h" #include "line_curve_bindings.h" #include "model_bindings.h" #include "model_component_bindings.h" #include "object_attributes_bindings.h" #include "object_bindings.h" -#include "point_geometry_bindings.h" +#include "point_bindings.h" #include "point_table_bindings.h" +#include "point_view_bindings.h" #include "point3d_bindings.h" #include "text_log_bindings.h" NB_MODULE(_py3dm, m) { - m.attr("__author__") = "StudioWEngineers"; - m.attr("__email__") = "studio.w.engineers@gmail.com"; - m.attr("__maintainer__") = "StudioWEngineers"; m.doc() = "Python bindings for openNURBS using nanobind."; ON::Begin(); @@ -41,9 +40,12 @@ NB_MODULE(_py3dm, m) { LayerTableBindings(m); LineCurveBindings(m); ModelBindings(m); - PointGeometryBindings(m); + PointBindings(m); TextLogBindings(m); + LayerViewBindings(m); + PointViewBindings(m); + CurveTableBindings(m); PointTableBindings(m); } diff --git a/src/bindings/layer_table_bindings.cpp b/src/bindings/layer_table_bindings.cpp index 8ebf3f5..b5150a9 100644 --- a/src/bindings/layer_table_bindings.cpp +++ b/src/bindings/layer_table_bindings.cpp @@ -13,9 +13,9 @@ void LayerTableBindings(nb::module_& m) { .def("__iter__", [](LayerTable::Iterator &it) -> LayerTable::Iterator& { return it; }) - .def("__next__", [](LayerTable::Iterator &it) { + .def("__next__", [](LayerTable::Iterator &it) -> LayerView* { while (!it.IsOver()) { - ON_Layer* layer = *it; + LayerView* layer = *it; ++it; if (layer != nullptr) { @@ -40,6 +40,7 @@ void LayerTableBindings(nb::module_& m) { .def("get_by_index", &LayerTable::GetByIndex, nb::rv_policy::reference_internal) .def("get_by_name", &LayerTable::GetByName, nb::rv_policy::reference_internal) .def("get_by_uuid", &LayerTable::GetByUUID, nb::rv_policy::reference_internal) + .def("get_by_uuid_exclusive", &LayerTable::GetByUUIDExclusive, nb::rv_policy::reference_internal) /*other methods*/ .def("add", &LayerTable::Add) diff --git a/src/bindings/layer_view_bindings.cpp b/src/bindings/layer_view_bindings.cpp new file mode 100644 index 0000000..dd5eda0 --- /dev/null +++ b/src/bindings/layer_view_bindings.cpp @@ -0,0 +1,41 @@ +// System includes + +// External includes + +// Project includes +#include "layer_view_bindings.h" + +#include "casters/color_caster.h" +#include "casters/on_wstring_caster.h" +#include "casters/uuid_caster.h" + +#include "../views/layer_view.h" + + +void LayerViewBindings(nb::module_& m) { + nb::class_(m, "LayerView") + /*read-only static member variables*/ + .def_ro_static("path_separator", &ON_ModelComponent::NamePathSeparator) + + /*read-only properties*/ + .def_prop_ro("color", &LayerView::Color) + .def_prop_ro("iges_level", &LayerView::IgesLevel) + .def_prop_ro("is_expanded", &LayerView::IsExpanded) + .def_prop_ro("is_locked", &LayerView::IsLocked) + .def_prop_ro("is_visible", &LayerView::IsVisible) + .def_prop_ro("layer_uuid", &LayerView::GetUUID) + .def_prop_ro("line_type_index", &LayerView::LineTypeIndex) + .def_prop_ro("name", &LayerView::Name) + .def_prop_ro("parent_uuid", &LayerView::GetParentUUID) + .def_prop_ro("parent_uuid_is_not_null", &LayerView::ParentIdIsNotNil) + .def_prop_ro("parent_uuid_is_null", &LayerView::ParentIdIsNil) + .def_prop_ro("plot_color", &LayerView::PlotColor) + .def_prop_ro("plot_weight", &LayerView::PlotWeight) + .def_prop_ro("render_material_index", &LayerView::RenderMaterialIndex) + + /*other methods*/ + .def("index", nb::overload_cast<>(&LayerView::Index, nb::const_)) + .def("index", nb::overload_cast(&LayerView::Index, nb::const_)) + .def("is_valid", &LayerView::IsValid, nb::arg("text_log") = nullptr) + ; +} diff --git a/src/bindings/layer_view_bindings.h b/src/bindings/layer_view_bindings.h new file mode 100644 index 0000000..b166870 --- /dev/null +++ b/src/bindings/layer_view_bindings.h @@ -0,0 +1,19 @@ +/* + src/bindings/layer_bindings.h: Exposing LayerView to Python + + Copyright (c) 2026 Studio W Engineers + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ +#pragma once + +// System includes + +// External includes + +// Project includes +#include "bindings.h" + + +void LayerViewBindings(nb::module_& m); diff --git a/src/bindings/point_geometry_bindings.cpp b/src/bindings/point_bindings.cpp similarity index 68% rename from src/bindings/point_geometry_bindings.cpp rename to src/bindings/point_bindings.cpp index ab33882..ef7afd6 100644 --- a/src/bindings/point_geometry_bindings.cpp +++ b/src/bindings/point_bindings.cpp @@ -4,15 +4,15 @@ // Project includes #include "geometry_bindings.h" -#include "point_geometry_bindings.h" +#include "point_bindings.h" -void PointGeometryBindings(nb::module_& m) { - nb::class_(m, "PointGeometry") +void PointBindings(nb::module_& m) { + nb::class_(m, "Point") /*magic methods*/ .def(nb::init<>()) .def(nb::init()) - .def(nb::init()) + .def(nb::init()) /*read-write member variables*/ .def_rw("point", &ON_Point::point) diff --git a/src/bindings/point_geometry_bindings.h b/src/bindings/point_bindings.h similarity index 70% rename from src/bindings/point_geometry_bindings.h rename to src/bindings/point_bindings.h index 78e5839..e69f67b 100644 --- a/src/bindings/point_geometry_bindings.h +++ b/src/bindings/point_bindings.h @@ -1,5 +1,5 @@ /* - src/bindings/point_geometry_bindings.h: Exposing ON_Point to Python + src/bindings/point_bindings.h: Exposing ON_Point to Python Copyright (c) 2025 Studio W Engineers @@ -16,4 +16,4 @@ #include "bindings.h" -void PointGeometryBindings(nb::module_& m); +void PointBindings(nb::module_& m); diff --git a/src/bindings/point_table_bindings.cpp b/src/bindings/point_table_bindings.cpp index f77ecc5..ebc9723 100644 --- a/src/bindings/point_table_bindings.cpp +++ b/src/bindings/point_table_bindings.cpp @@ -8,17 +8,14 @@ void PointTableBindings(nb::module_& m) { - nb::class_(m, "__CurveTableIterator") - .def("__iter__", [](PointTable::Iterator& it) -> PointTable::Iterator& { - return it; - }) - .def("__next__", [](PointTable::Iterator& it) { + nb::class_(m, "__PointTableIterator") + .def("__iter__", [](PointTable::Iterator& it) -> PointTable::Iterator& {return it;}) + .def("__next__", [](PointTable::Iterator& it) -> PointView* { while (!it.IsOver()) { - ON_Object* object = *it; + PointView* point = *it; ++it; - - if (object != nullptr) { - return object; + if (point) { + return point; } } throw nb::stop_iteration(); @@ -54,7 +51,8 @@ void PointTableBindings(nb::module_& m) { ) /*getters*/ - .def("get_by_uuid", &PointTable::GetbyUUID, nb::rv_policy::reference_internal) + .def("get_by_uuid", &PointTable::GetByUUID, nb::rv_policy::reference_internal) + .def("get_by_uuid_exclusive", &PointTable::GetByUUIDExclusive, nb::rv_policy::reference_internal) /*other methods*/ .def("count", &PointTable::Count) diff --git a/src/bindings/point_view_bindings.cpp b/src/bindings/point_view_bindings.cpp new file mode 100644 index 0000000..8981c60 --- /dev/null +++ b/src/bindings/point_view_bindings.cpp @@ -0,0 +1,50 @@ +// System includes + +// External includes + +// Project includes +#include "casters/uuid_caster.h" +#include "../views/point_view.h" +#include "point_view_bindings.h" + + +void PointViewBindings(nb::module_& m) { + nb::class_(m, "PointView") + /*magic methods*/ + .def( + "__eq__", + [] (const PointView& self, const ON_Point& other) { + return self == other; + } + ) + .def( + "__eq__", + [] (const PointView& self, const ON_3dPoint& other) { + return self == other; + } + ) + .def( + "__ne__", + [] (const PointView& self, const ON_Point& other) { + return self != other; + } + ) + .def( + "__ne__", + [] (const PointView& self, const ON_3dPoint& other) { + return self != other; + } + ) + + /*read-only properties*/ + .def_prop_ro("obj_uuid", &PointView::GetUUID) + .def_prop_ro("x", &PointView::X) + .def_prop_ro("y", &PointView::Y) + .def_prop_ro("z", &PointView::Z) + + /*other methods*/ + .def("distance_to", &PointView::DistanceTo) + .def("is_coincident", &PointView::IsCoincident) + .def("is_valid", &PointView::IsValid, nb::arg("text_log") = nullptr) + ; +} diff --git a/src/bindings/point_view_bindings.h b/src/bindings/point_view_bindings.h new file mode 100644 index 0000000..7fc6f3b --- /dev/null +++ b/src/bindings/point_view_bindings.h @@ -0,0 +1,19 @@ +/* + src/bindings/point_view?bindings.h: Exposing PointView to Python + + Copyright (c) 2025 Studio W Engineers + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ +#pragma once + +// System includes + +// External includes + +// Project includes +#include "bindings.h" + + +void PointViewBindings(nb::module_& m); diff --git a/src/layer_table.cpp b/src/layer_table.cpp index 9fa1b45..bb93b8b 100644 --- a/src/layer_table.cpp +++ b/src/layer_table.cpp @@ -7,45 +7,58 @@ LayerTable::LayerTable(std::shared_ptr model) { } /*deleters*/ -bool LayerTable::DeleteByName(ON_wString layer_name) { - return LayerTable::DeleteByUUID(LayerTable::GetUUID(layer_name)); +bool LayerTable::DeleteByName(const ON_wString layer_name) const { + return DeleteByUUID(GetUUID(layer_name)); } -bool LayerTable::DeleteByUUID(ON_UUID on_uuid) { - return !m_model->RemoveModelComponent(ON_ModelComponent::Type::Layer, on_uuid).IsEmpty(); +bool LayerTable::DeleteByUUID(const ON_UUID on_uuid) const { + return !m_model->RemoveModelComponent( + ON_ModelComponent::Type::Layer, + on_uuid + ).IsEmpty(); } /*getters*/ -ON_Layer* LayerTable::GetByIndex(int index) { +LayerView* LayerTable::GetByIndex(int index) const { if (index < 0) { throw std::out_of_range("index must be greater than equal to 0!"); } - ON_ModelComponentReference comp_ref = m_model->ComponentFromIndex(ON_ModelComponent::Type::Layer, index); - if (comp_ref.IsEmpty()) { + ON_ModelComponentReference mcr = m_model->ComponentFromIndex(ON_ModelComponent::Type::Layer, index); + if (mcr.IsEmpty()) { return nullptr; } - ON_Layer* layer = const_cast(ON_Layer::Cast(comp_ref.ModelComponent())); - return layer; + return new LayerView(ON_Layer::Cast(mcr.ModelComponent())); } -ON_Layer* LayerTable::GetByName(ON_wString full_name) { - return LayerTable::GetByUUID(LayerTable::GetUUID(full_name)); +LayerView* LayerTable::GetByName(ON_wString full_name) const { + return GetByUUID(GetUUID(full_name)); } -ON_Layer* LayerTable::GetByUUID(ON_UUID on_uuid) { - ON_ModelComponentReference comp_ref = m_model->ComponentFromId(ON_ModelComponent::Type::Layer, on_uuid); - if (comp_ref.IsEmpty()) { +LayerView* LayerTable::GetByUUID(const ON_UUID on_uuid) const { + ON_ModelComponentReference mcr = m_model->ComponentFromId(ON_ModelComponent::Type::Layer, on_uuid); + if (mcr.IsEmpty()) { return nullptr; } - ON_Layer* layer = const_cast(ON_Layer::Cast(comp_ref.ModelComponent())); - return layer; + return new LayerView(ON_Layer::Cast(mcr.ModelComponent())); +} + +ON_Layer* LayerTable::GetByUUIDExclusive(const ON_UUID on_uuid) const { + const ON_ModelComponentReference& mcr = m_model->ComponentFromRuntimeSerialNumber( + GetRuntimeSerialNumber(on_uuid) + ); + + if (mcr.IsEmpty()) { + return nullptr; + } + + return ON_Layer::Cast(mcr.ExclusiveModelComponent()); } /*other methods*/ -const ON_UUID LayerTable::Add(const ON_Layer& layer) { +const ON_UUID LayerTable::Add(const ON_Layer& layer) const { const ON_Layer* m_layer = ON_Layer::FromModelComponentRef(m_model->AddModelComponent(layer), nullptr); return (nullptr != m_layer) ? m_layer->Id() : ON_nil_uuid; @@ -64,8 +77,8 @@ const ON_wString LayerTable::GetFullPath(const ON_Layer* layer) const { ON_wString full_name = layer->Name(); ON_UUID parent_id = layer->ParentId(); while (ON_UuidIsNotNil(parent_id)) { - ON_ModelComponentReference comp_ref = model->LayerFromId(parent_id); - const ON_Layer* layer = ON_Layer::Cast(comp_ref.ModelComponent()); + ON_ModelComponentReference mcr = model->LayerFromId(parent_id); + const ON_Layer* layer = ON_Layer::Cast(mcr.ModelComponent()); if (layer == nullptr) { break; } @@ -78,20 +91,20 @@ const ON_wString LayerTable::GetFullPath(const ON_Layer* layer) const { return full_name.Array(); } -int LayerTable::GetIndex(ON_wString full_name) { +int LayerTable::GetIndex(ON_wString full_name) const { const int max_index = m_model->Manifest().ComponentIndexLimit(ON_ModelComponent::Type::Layer); for (unsigned int i = 0; i < max_index; ++i) { - ON_ModelComponentReference comp_ref = m_model->ComponentFromIndex(ON_ModelComponent::Type::Layer, i); - if (comp_ref.IsEmpty()) + ON_ModelComponentReference mcr = m_model->ComponentFromIndex(ON_ModelComponent::Type::Layer, i); + if (mcr.IsEmpty()) continue; - const ON_Layer* layer = ON_Layer::Cast(comp_ref.ModelComponent()); + const ON_Layer* layer = ON_Layer::Cast(mcr.ModelComponent()); if (layer == nullptr) { continue; } - if (full_name == LayerTable::GetFullPath(LayerTable::GetByIndex(i))) { + if (full_name == GetFullPath(layer)) { return layer->Index(); } } @@ -99,15 +112,22 @@ int LayerTable::GetIndex(ON_wString full_name) { return ON_UNSET_INT_INDEX; } -const ON_UUID LayerTable::GetUUID(ON_wString full_name) { +ON__UINT64 LayerTable::GetRuntimeSerialNumber(const ON_UUID on_uuid) const { + return m_model->Manifest().ItemFromId( + ON_ModelComponent::Type::Layer, + on_uuid + ).ComponentRuntimeSerialNumber(); +} + +const ON_UUID LayerTable::GetUUID(ON_wString full_name) const { const int count = m_model->Manifest().ComponentIndexLimit(ON_ModelComponent::Type::Layer); for (unsigned int i = 0; i < count; ++i) { - ON_ModelComponentReference comp_ref = m_model->ComponentFromIndex(ON_ModelComponent::Type::Layer, i); - if (comp_ref.IsEmpty()) + ON_ModelComponentReference mcr = m_model->ComponentFromIndex(ON_ModelComponent::Type::Layer, i); + if (mcr.IsEmpty()) continue; - const ON_Layer* layer = ON_Layer::Cast(comp_ref.ModelComponent()); + const ON_Layer* layer = ON_Layer::Cast(mcr.ModelComponent()); if (!layer) continue; @@ -115,20 +135,19 @@ const ON_UUID LayerTable::GetUUID(ON_wString full_name) { return layer->Id(); } } - return ON_nil_uuid; } -bool LayerTable::Has(ON_wString full_name) { +bool LayerTable::Has(ON_wString full_name) const { const int max_index = m_model->Manifest().ComponentIndexLimit(ON_ModelComponent::Type::Layer); for (unsigned int i = 0; i < max_index; ++i) { - ON_ModelComponentReference comp_ref = m_model->ComponentFromIndex(ON_ModelComponent::Type::Layer, i); - if (comp_ref.IsEmpty()) { + ON_ModelComponentReference mcr = m_model->ComponentFromIndex(ON_ModelComponent::Type::Layer, i); + if (mcr.IsEmpty()) { continue; } - const ON_Layer* layer = ON_Layer::Cast(comp_ref.ModelComponent()); + const ON_Layer* layer = ON_Layer::Cast(mcr.ModelComponent()); if (layer == nullptr) continue; @@ -139,8 +158,8 @@ bool LayerTable::Has(ON_wString full_name) { ON_wString name = layer->Name(); ON_UUID parent_uuid = layer->ParentId(); while (ON_UuidIsNotNil(parent_uuid)) { - ON_ModelComponentReference comp_ref = m_model->LayerFromId(parent_uuid); - const ON_Layer* layer = ON_Layer::Cast(comp_ref.ModelComponent()); + ON_ModelComponentReference mcr = m_model->LayerFromId(parent_uuid); + const ON_Layer* layer = ON_Layer::Cast(mcr.ModelComponent()); if (layer == nullptr) { break; } @@ -153,9 +172,7 @@ bool LayerTable::Has(ON_wString full_name) { if (full_name == name.Array()) { return true; } - } - return false; } @@ -167,7 +184,7 @@ int LayerTable::MaxIndex() const { LayerTable::Iterator::Iterator(LayerTable* table, int index) : m_table(table), m_index(index), m_count(table->MaxIndex()) {} -ON_Layer* LayerTable::Iterator::operator*() const { +LayerView* LayerTable::Iterator::operator*() const { return m_table->GetByIndex(m_index); } diff --git a/src/layer_table.h b/src/layer_table.h index b6b1106..8c272fb 100644 --- a/src/layer_table.h +++ b/src/layer_table.h @@ -15,40 +15,55 @@ // Project includes #include "../lib/opennurbs/opennurbs.h" +#include "views/layer_view.h" + class LayerTable { public: - /*constructors*/ + /*constructor*/ LayerTable(std::shared_ptr model); + /*destructor*/ + ~LayerTable() = default; + /*deleters*/ - bool DeleteByName(ON_wString full_name); - bool DeleteByUUID(ON_UUID on_uuid); + bool DeleteByName(const ON_wString full_name) const; + bool DeleteByUUID(const ON_UUID on_uuid) const; /*getters*/ - ON_Layer* GetByIndex(int index); - ON_Layer* GetByName(ON_wString full_name); - ON_Layer* GetByUUID(ON_UUID on_uuid); + LayerView* GetByIndex(int index) const; + LayerView* GetByName(ON_wString full_name) const; + LayerView* GetByUUID(const ON_UUID on_uuid) const; + ON_Layer* GetByUUIDExclusive(const ON_UUID on_uuid) const; /*other methods*/ - const ON_UUID Add(const ON_Layer& layer); + const ON_UUID Add(const ON_Layer& layer) const; int Count() const; const ON_wString GetFullPath(const ON_Layer* layer) const; - int GetIndex(ON_wString full_name); - const ON_UUID GetUUID(ON_wString full_name); - bool Has(ON_wString full_name); + int GetIndex(ON_wString full_name) const; + ON__UINT64 GetRuntimeSerialNumber(const ON_UUID on_uuid) const; + const ON_UUID GetUUID(ON_wString full_name) const; + bool Has(ON_wString full_name) const; int MaxIndex() const; /*LayerTable Iterator*/ class Iterator { public: + /*constructor*/ Iterator(LayerTable* table, int index); - ON_Layer* operator*() const; + /*destructor*/ + ~Iterator() = default; + + /*operators*/ + LayerView* operator*() const; Iterator& operator++(); + + /*other methods*/ bool IsOver() const; private: + /*member variables*/ LayerTable* m_table; unsigned int m_index; unsigned int m_count; diff --git a/src/point_table.cpp b/src/point_table.cpp index ec1af00..754ba79 100644 --- a/src/point_table.cpp +++ b/src/point_table.cpp @@ -14,7 +14,7 @@ ON_UUID PointTable::Add(double x, double y, double z, const ON_3dmObjectAttribut ON_Point point(x, y, z); const ON_ModelComponent* mc = m_model->AddModelGeometryComponent(&point, obj_attr).ModelComponent(); - return (mc != nullptr) ? mc->Id() : ON_nil_uuid; + return mc ? mc->Id() : ON_nil_uuid; } ON_UUID PointTable::Add(const ON_3dPoint& point, const ON_3dmObjectAttributes* obj_attr) const { @@ -26,24 +26,47 @@ ON_UUID PointTable::Add(const ON_Point& point, const ON_3dmObjectAttributes* obj } /*getters*/ -ON_Point* PointTable::GetbyUUID(const ON_UUID on_uuid) { - const ON_ModelComponent* mc = m_model->ComponentFromId(ON_ModelComponent::Type::ModelGeometry, on_uuid).ModelComponent(); +PointView* PointTable::GetByUUID(const ON_UUID obj_uuid) const { + const ON_ModelComponent* mc = m_model->ComponentFromId( + ON_ModelComponent::Type::ModelGeometry, + obj_uuid + ).ModelComponent(); + const ON_ModelGeometryComponent* mgc = ON_ModelGeometryComponent::Cast(mc); - if (!IsPoint(mc)) { + if (!IsPoint(mgc)) { return nullptr; } + return new PointView(ON_Point::Cast(mgc->Geometry(nullptr)), mgc->ModelObjectId()); +} - const ON_ModelGeometryComponent* mgc = ON_ModelGeometryComponent::Cast(mc); - return const_cast(ON_Point::Cast(mgc->Geometry(nullptr))); +ON_Point* PointTable::GetByUUIDExclusive(const ON_UUID obj_uuid) const { + const ON_ModelComponentReference& mcr = m_model->ComponentFromRuntimeSerialNumber( + GetRuntimeSerialNumber(obj_uuid) + ); + ON_ModelGeometryComponent* mgc = ON_ModelGeometryComponent::Cast( + mcr.ExclusiveModelComponent() + ); + + if (!IsPoint(mgc)) { + return nullptr; + } + return ON_Point::Cast(mgc->ExclusiveGeometry()); +} + +ON__UINT64 PointTable::GetRuntimeSerialNumber(const ON_UUID obj_uuid) const { + return m_model->Manifest().ItemFromId( + ON_ModelComponent::Type::ModelGeometry, + obj_uuid + ).ComponentRuntimeSerialNumber(); } /*other methods*/ -int PointTable::Count() { +int PointTable::Count() const { int count = 0; ONX_ModelComponentIterator mci(*m_model.get(), ON_ModelComponent::Type::ModelGeometry); ON_ModelComponentReference mcr = mci.FirstComponentReference(); while (!mcr.IsEmpty()) { - if (PointTable::IsPoint(mcr.ModelComponent())) { + if (IsPoint(ON_ModelGeometryComponent::Cast(mcr.ModelComponent()))) { ++count; } mcr = mci.NextComponentReference(); @@ -52,12 +75,10 @@ int PointTable::Count() { return count; } -bool PointTable::IsPoint(const ON_ModelComponent* mc) { - const ON_ModelGeometryComponent* mgc = ON_ModelGeometryComponent::Cast(mc); - if (mgc == nullptr) { +bool PointTable::IsPoint(const ON_ModelGeometryComponent* mgc) { + if (!mgc) { return false; } - const ON_Geometry* geom = mgc->Geometry(nullptr); return (geom && geom->ObjectType() == ON::point_object); } @@ -69,8 +90,8 @@ PointTable::Iterator::Iterator(PointTable* table) m_current = m_iterator.FirstComponentReference(); } -ON_Point* PointTable::Iterator::operator*() const { - return m_table->GetbyUUID(m_current.ModelComponentId()); +PointView* PointTable::Iterator::operator*() const { + return m_table->GetByUUID(m_current.ModelComponentId()); } PointTable::Iterator& PointTable::Iterator::operator++() { diff --git a/src/point_table.h b/src/point_table.h index 0572edd..80af153 100644 --- a/src/point_table.h +++ b/src/point_table.h @@ -14,31 +14,37 @@ // Project includes #include "../lib/opennurbs/opennurbs.h" +#include "views/point_view.h" class PointTable { public: - /*constructors*/ + /*constructor*/ PointTable(std::shared_ptr model); + /*destructor*/ + ~PointTable() = default; + /*add methods*/ ON_UUID Add(double x, double y, double z, const ON_3dmObjectAttributes* obj_attr) const; ON_UUID Add(const ON_Point& point, const ON_3dmObjectAttributes* obj_attr) const; ON_UUID Add(const ON_3dPoint& point, const ON_3dmObjectAttributes* obj_attr) const; /*getters*/ - ON_Point* GetbyUUID(const ON_UUID obj_uuid); + PointView* GetByUUID(const ON_UUID obj_uuid) const; + ON_Point* GetByUUIDExclusive(const ON_UUID obj_uuid) const; + ON__UINT64 GetRuntimeSerialNumber(const ON_UUID obj_uuid) const; /*other methods*/ - int Count(); - bool IsPoint(const ON_ModelComponent* mc); + int Count() const; + static bool IsPoint(const ON_ModelGeometryComponent* mgc); /*PointTable Iterator*/ class Iterator { public: Iterator(PointTable* table); - ON_Point* operator*() const; + PointView* operator*() const; Iterator& operator++(); bool IsOver() const; diff --git a/src/views/curve_view.cpp b/src/views/curve_view.cpp new file mode 100644 index 0000000..4bea455 --- /dev/null +++ b/src/views/curve_view.cpp @@ -0,0 +1,24 @@ +#include "curve_view.h" + +/*constructor*/ +CurveView::CurveView( + const ON_Curve* source, + const ON_UUID on_uuid +) : m_line_curve(source), m_uuid(on_uuid) {} + +/*other methods*/ +ON_UUID CurveView::GetUUID() const { + return m_uuid; +} + +bool CurveView::IsClosed() const { + return m_line_curve->IsClosed(); +} + +bool CurveView::IsLinear() const { + return m_line_curve->IsLinear(); +} + +bool CurveView::IsValid(ON_TextLog* text_log) const { + return m_line_curve->IsValid(text_log); +} diff --git a/src/views/curve_view.h b/src/views/curve_view.h new file mode 100644 index 0000000..f5de434 --- /dev/null +++ b/src/views/curve_view.h @@ -0,0 +1,37 @@ +/* + src/curve_view.h: Tiny wrapper to read-only access ON_Curve objects + + Copyright (c) 2026 Studio W Engineers + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ +#pragma once + +// System includes + +// External includes + +// Project includes +#include "../../lib/opennurbs/opennurbs.h" + + +class CurveView { +public: + /*constructor*/ + CurveView(const ON_Curve* source, const ON_UUID on_uuid); + + /*destructor*/ + ~CurveView() = default; + + /*other methods*/ + ON_UUID GetUUID() const; + bool IsClosed() const; + bool IsLinear() const; + bool IsValid(ON_TextLog* text_log) const; + +private: + /*member variables*/ + const ON_Curve* m_line_curve; + const ON_UUID m_uuid; +}; diff --git a/src/views/layer_view.cpp b/src/views/layer_view.cpp new file mode 100644 index 0000000..1b88618 --- /dev/null +++ b/src/views/layer_view.cpp @@ -0,0 +1,73 @@ +#include "layer_view.h" + +/*constructor*/ +LayerView::LayerView(const ON_Layer* source) : m_layer(source) {} + +/*other methods*/ +ON_Color LayerView::Color() const { + return m_layer->Color(); +} + +ON_UUID LayerView::GetParentUUID() const { + return m_layer->ParentId(); +} + +ON_UUID LayerView::GetUUID() const { + return m_layer->Id(); +} + +int LayerView::IgesLevel() const { + return m_layer->IgesLevel(); +} + +int LayerView::Index() const { + return m_layer->Index(); +} + +int LayerView::Index(int return_value) const { + return m_layer->Index(return_value); +} + +bool LayerView::IsExpanded() const { + return m_layer->m_bExpanded; +} + +bool LayerView::IsLocked() const { + return m_layer->IsLocked(); +} + +bool LayerView::IsValid(ON_TextLog* text_log) const { + return m_layer->IsValid(text_log); +} + +bool LayerView::IsVisible() const { + return m_layer->IsVisible(); +} + +int LayerView::LineTypeIndex() const { + return m_layer->m_linetype_index; +} + +ON_wString LayerView::Name() const { + return m_layer->Name(); +} + +bool LayerView::ParentIdIsNil() const { + return m_layer->ParentIdIsNil(); +} + +bool LayerView::ParentIdIsNotNil() const { + return m_layer->ParentIdIsNotNil(); +} + +ON_Color LayerView::PlotColor() const { + return m_layer->PlotColor(); +} + +double LayerView::PlotWeight() const { + return m_layer->PlotWeight(); +} + +int LayerView::RenderMaterialIndex() const { + return m_layer->RenderMaterialIndex(); +} diff --git a/src/views/layer_view.h b/src/views/layer_view.h new file mode 100644 index 0000000..e25e1db --- /dev/null +++ b/src/views/layer_view.h @@ -0,0 +1,49 @@ +/* + src/point_view.h: Tiny wrapper to read-only access ON_Layer objects + + Copyright (c) 2026 Studio W Engineers + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ +#pragma once + +// System includes + +// External includes + +// Project includes +#include "../../lib/opennurbs/opennurbs.h" + + +class LayerView { +public: + /*constructor*/ + LayerView(const ON_Layer* source); + + /*destructor*/ + ~LayerView() = default; + + /*other methods*/ + ON_Color Color() const; + ON_UUID GetParentUUID() const; + ON_UUID GetUUID() const; + int IgesLevel() const; + int Index() const; + int Index(int return_value) const; + bool IsExpanded() const; + bool IsLocked() const; + bool IsValid(ON_TextLog* text_log) const; + bool IsVisible() const; + int LineTypeIndex() const; + ON_wString Name() const; + bool ParentIdIsNil() const; + bool ParentIdIsNotNil() const; + ON_Color PlotColor() const; + double PlotWeight() const; + int RenderMaterialIndex() const; + +private: + /*member variables*/ + const ON_Layer* m_layer; +}; diff --git a/src/views/point_view.cpp b/src/views/point_view.cpp new file mode 100644 index 0000000..3f71507 --- /dev/null +++ b/src/views/point_view.cpp @@ -0,0 +1,53 @@ +#include "point_view.h" + +/*constructor*/ +PointView::PointView( + const ON_Point* source, + const ON_UUID on_uuid +) : m_point(source), m_uuid(on_uuid) {} + +/*operators*/ +bool PointView::operator==(const ON_Point& other) const { + return m_point->point == other; +} + +bool PointView::operator==(const ON_3dPoint& other) const { + return m_point->point == other; +} + +bool PointView::operator!=(const ON_Point& other) const { + return m_point->point != other; +} + +bool PointView::operator!=(const ON_3dPoint& other) const { + return m_point->point != other; +} + +/*other methods*/ +double PointView::DistanceTo(const ON_3dPoint& point) const { + return m_point->point.DistanceTo(point); +} + +ON_UUID PointView::GetUUID() const { + return m_uuid; +} + +bool PointView::IsCoincident(const ON_3dPoint& point) const { + return m_point->point.IsCoincident(point); +} + +bool PointView::IsValid(ON_TextLog* text_log) const { + return m_point->IsValid(text_log); +} + +double PointView::X () const { + return m_point->point.x; +} + +double PointView::Y () const { + return m_point->point.y; +} + +double PointView::Z () const { + return m_point->point.z; +} diff --git a/src/views/point_view.h b/src/views/point_view.h new file mode 100644 index 0000000..dee7dda --- /dev/null +++ b/src/views/point_view.h @@ -0,0 +1,46 @@ +/* + src/point_view.h: Tiny wrapper to read-only access ON_Point objects + + Copyright (c) 2026 Studio W Engineers + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ +#pragma once + +// System includes + +// External includes + +// Project includes +#include "../../lib/opennurbs/opennurbs.h" + + +class PointView { +public: + /*constructor*/ + PointView(const ON_Point* source, const ON_UUID on_uuid); + + /*destructor*/ + ~PointView() = default; + + /*operators*/ + bool operator==(const ON_Point& other) const; + bool operator==(const ON_3dPoint& other) const; + bool operator!=(const ON_Point& other) const; + bool operator!=(const ON_3dPoint& other) const; + + /*other methods*/ + double DistanceTo(const ON_3dPoint& point) const; + ON_UUID GetUUID() const; + bool IsCoincident(const ON_3dPoint& point) const; + bool IsValid(ON_TextLog* text_log) const; + double X() const; + double Y() const; + double Z() const; + +private: + /*member variables*/ + const ON_Point* m_point; + const ON_UUID m_uuid; +}; diff --git a/tests/test_layer.py b/tests/test_layer.py index 2131231..f137b7f 100644 --- a/tests/test_layer.py +++ b/tests/test_layer.py @@ -213,9 +213,11 @@ def setUp(self) -> None: self.model = Model() layer_uuid = self.model.layer_table.add(Layer()) - # workaround to silent mypy complains - layer = self.model.layer_table.get_by_uuid(layer_uuid) - self.layer = cast(Layer, layer) + # NOTE workaround to silent type checkers complains + self.layer = cast( + Layer, + self.model.layer_table.get_by_uuid_exclusive(layer_uuid) + ) def test_get_and_set_color(self) -> None: with self.subTest(msg="Layer color before assignment"): diff --git a/tests/test_layer_table.py b/tests/test_layer_table.py index d8a295c..5e59a48 100644 --- a/tests/test_layer_table.py +++ b/tests/test_layer_table.py @@ -31,13 +31,13 @@ def test_add(self) -> None: with self.subTest(msg="Name of 1st layer"): self.assertEqual( - self.model.layer_table.get_by_uuid(layer_1_uuid).get_name(), # type: ignore + self.model.layer_table.get_by_uuid(layer_1_uuid).name, # type: ignore "Layer A" ) with self.subTest(msg="Name of 2nd layer"): self.assertEqual( - self.model.layer_table.get_by_uuid(layer_2_uuid).get_name(), # type: ignore + self.model.layer_table.get_by_uuid(layer_2_uuid).name, # type: ignore "layer 2" ) @@ -98,7 +98,7 @@ def test_getitem(self) -> None: self.assertIsNone(self.model.layer_table[3]) with self.subTest(msg="Existing index"): - self.assertEqual(self.model.layer_table[1].get_name(), "Layer 02") + self.assertEqual(self.model.layer_table[1].name, "Layer 02") with self.subTest(msg="Negative index"): with self.assertRaises(IndexError): @@ -122,7 +122,7 @@ def test_get_by_index(self) -> None: self.assertIsNone(self.model.layer_table.get_by_index(3)) with self.subTest(msg="Name of 2nd layer"): - self.assertEqual(rl_2.get_name(), "layer 2") # type: ignore + self.assertEqual(rl_2.name, "layer 2") # type: ignore with self.subTest(msg="Color of 1st layer"): self.assertEqual(rl_1.color, (255, 0, 255, 255)) # type: ignore @@ -149,7 +149,7 @@ def test_get_by_name(self) -> None: self.assertIsNone(self.model.layer_table.get_by_name("dummy")) with self.subTest(msg="Name of 2nd layer"): - self.assertEqual(rl_2.get_name(), "layer 2") # type: ignore + self.assertEqual(rl_2.name, "layer 2") # type: ignore with self.subTest(msg="Color of 1st layer"): self.assertEqual(rl_1.color, (255, 0, 255, 255)) # type: ignore @@ -172,7 +172,7 @@ def test_get_by_uuid(self) -> None: self.assertIsNone(self.model.layer_table.get_by_uuid(UUID(int=0))) with self.subTest(msg="Name of 2nd layer"): - self.assertEqual(rl_2.get_name(), "layer 2") # type: ignore + self.assertEqual(rl_2.name, "layer 2") # type: ignore with self.subTest(msg="Color of 1st layer"): self.assertEqual(rl_1.color, (255, 0, 255, 255)) # type: ignore @@ -266,20 +266,7 @@ def test_iter(self) -> None: for layer_index, layer in enumerate(model.layer_table): with self.subTest(layer_index=layer_index): - self.assertEqual(layer.get_name(), f"Layer 0{layer_index + 1}") + self.assertEqual(layer.name, f"Layer 0{layer_index + 1}") with self.subTest(layer_index=layer_index): - self.assertEqual(layer.get_index(), layer_index) - - def test_modify_layer_in_iter(self) -> None: - model = Model() - - model.layer_table.add(Layer()) - model.layer_table.add(Layer()) - - for layer_index, layer in enumerate(model.layer_table): - with self.subTest(layer_index=layer_index): - layer.set_name(f"new_layer_{layer_index}") - - with self.subTest(layer_index=layer_index): - self.assertEqual(layer.get_name(), f"new_layer_{layer_index}") + self.assertEqual(layer.index(), layer_index) diff --git a/tests/test_layer_view.py b/tests/test_layer_view.py new file mode 100644 index 0000000..a380305 --- /dev/null +++ b/tests/test_layer_view.py @@ -0,0 +1,84 @@ +# standard library imports +from typing import cast +from unittest import TestCase +from uuid import UUID + +# third party library imports +from py3dm import Layer, LayerView, Model + +# local library specific imports + + +class LayerViewTestSuite(TestCase): + def setUp(self) -> None: + model = Model() + + # create a layer and set some properties + layer = Layer() + layer.color = (100, 50, 10, 255) + layer.plot_color = (100, 50, 10, 255) + layer.plot_weight = 1.1 + layer.iges_level = 10 + layer.set_name("My new layer") + self.layer_uuid = model.layer_table.add(layer) + + # NOTE: workaround to silent type checker complains + self.layer_view = cast( + LayerView, + model.layer_table.get_by_uuid(self.layer_uuid) + ) + + def test_color(self) -> None: + self.assertEqual(self.layer_view.color, (100, 50, 10, 255)) + + def test_get_and_set_iges_level(self) -> None: + self.assertEqual(self.layer_view.iges_level, 10) + + def test_get_index(self) -> None: + with self.subTest(msg=""): + self.assertEqual(self.layer_view.index(), 0) + + with self.subTest(msg="overload variant"): + self.assertEqual(self.layer_view.index(2), 0) + + def test_is_expanded(self) -> None: + self.assertTrue(self.layer_view.is_expanded) + + def test_is_locked(self) -> None: + self.assertFalse(self.layer_view.is_locked) + + def test_is_visible(self) -> None: + self.assertTrue(self.layer_view.is_visible) + + def test_uuid(self) -> None: + self.assertEqual(self.layer_view.layer_uuid, self.layer_uuid) + + def test_line_type_index(self) -> None: + self.assertEqual(self.layer_view.line_type_index, -1) + + def test_name(self) -> None: + self.assertEqual(self.layer_view.name, "My new layer") + + def test_parent_uuid(self) -> None: + self.assertEqual(self.layer_view.parent_uuid, UUID(int=0)) + + def test_plot_color(self) -> None: + self.assertEqual(self.layer_view.plot_color, (100, 50, 10, 255)) + + def test_plot_weight(self) -> None: + self.assertEqual(self.layer_view.plot_weight, 1.1) + + def test_render_material_index(self) -> None: + self.assertEqual(self.layer_view.render_material_index, -1) + + def test_parent_uuid_is_not_null(self) -> None: + self.assertFalse(self.layer_view.parent_uuid_is_not_null) + + def test_parent_uuid_is_null(self) -> None: + self.assertTrue(self.layer_view.parent_uuid_is_null) + + def test_path_separator(self) -> None: + self.assertEqual(self.layer_view.path_separator, "::") + + def test_is_valid(self) -> None: + self.assertTrue(self.layer_view.is_valid()) diff --git a/tests/test_point_geometry.py b/tests/test_point.py similarity index 61% rename from tests/test_point_geometry.py rename to tests/test_point.py index 3f3f85c..538a59b 100644 --- a/tests/test_point_geometry.py +++ b/tests/test_point.py @@ -2,30 +2,30 @@ from unittest import TestCase # third party library imports -from py3dm import PointGeometry, Point3d +from py3dm import Point, Point3d # local library specific imports -class PointGeometryTestSuite(TestCase): +class PointTestSuite(TestCase): def test_constructor(self) -> None: with self.subTest(msg="Default constructor"): - self.assertEqual(PointGeometry().point, Point3d(0, 0, 0)) + self.assertEqual(Point().point, Point3d(0, 0, 0)) with self.subTest(msg="Constructor with coordinates"): self.assertEqual( - PointGeometry(1, 2, 1.1).point, + Point(1, 2, 1.1).point, Point3d(1, 2, 1.1) ) with self.subTest(msg="Constructor with Point3d"): self.assertEqual( - PointGeometry(Point3d(1, 2, 1.1)).point, + Point(Point3d(1, 2, 1.1)).point, Point3d(1, 2, 1.1) ) def test_is_valid(self) -> None: - self.assertTrue(PointGeometry().is_valid()) + self.assertTrue(Point().is_valid()) def test_point_member_variable(self) -> None: - self.assertIsInstance(PointGeometry().point, Point3d) + self.assertIsInstance(Point().point, Point3d) diff --git a/tests/test_point_table.py b/tests/test_point_table.py index 8d0d60d..b3e3163 100644 --- a/tests/test_point_table.py +++ b/tests/test_point_table.py @@ -3,7 +3,7 @@ from uuid import UUID # third party library imports -from py3dm import Model, ObjectAttributes, PointGeometry, Point3d +from py3dm import Model, ObjectAttributes, Point, Point3d # local library specific imports @@ -21,17 +21,17 @@ def test_add_with_attributes(self) -> None: with self.subTest(msg="add with Point3d part II"): point = self.table.get_by_uuid(point_id) - self.assertEqual(point.point, Point3d(0, 1, 0)) # type: ignore + self.assertTrue(point == Point3d(0, 1, 0)) - point_geo = PointGeometry(0, 2, 0) + point_geo = Point(0, 2, 0) point_geo_id = self.table.add(point_geo, ObjectAttributes()) - with self.subTest(msg="add with PointGeometry part I"): + with self.subTest(msg="add with Point part I"): self.assertNotEqual(point_geo_id, UUID(int=0)) - with self.subTest(msg="add with PointGeometry part II"): + with self.subTest(msg="add with Point part II"): point = self.table.get_by_uuid(point_geo_id) - self.assertEqual(point.point, Point3d(0, 2, 0)) # type: ignore + self.assertEqual(point.y, 2) # type: ignore def test_count(self) -> None: with self.subTest(msg="empty table"): @@ -46,7 +46,7 @@ def test_get_by_uuid(self) -> None: obj_uuid = self.table.add(Point3d(0, 0, 3)) point = self.table.get_by_uuid(obj_uuid) - self.assertEqual(point.point, Point3d(0, 0, 3)) # type: ignore + self.assertEqual(point.z, 3) # type: ignore class PointTableIteratorTestSuite(TestCase): @@ -56,12 +56,9 @@ def setUp(self) -> None: self.model.point_table.add(0, 1, 2) self.model.curve_table.add(Point3d(0, 0, 1), Point3d(1, 1, 1)) self.model.curve_table.add(Point3d(2, 0, 1), Point3d(2, 1, 1)) - self.model.point_table.add(2, 1, 2) + self.model.point_table.add(1, 1, 2) def test_point_iterator(self) -> None: - for point in self.model.point_table: - point.point.x = 1 - for point_index, point in enumerate(self.model.point_table): with self.subTest(point_index=point_index): - self.assertEqual(point.point.x, 1) + self.assertEqual(point.x, point_index) diff --git a/tests/test_point_view.py b/tests/test_point_view.py new file mode 100644 index 0000000..3fcb2a9 --- /dev/null +++ b/tests/test_point_view.py @@ -0,0 +1,51 @@ +# standard library imports +from typing import cast +from unittest import TestCase + +# third party library imports +from py3dm import Model, Point, PointView, Point3d + +# local library specific imports + + +class PointViewTestSuite(TestCase): + def setUp(self) -> None: + model = Model() + self.table = model.point_table + self.uuid_1 = self.table.add(0, 1, 2) + self.point_1 = cast(PointView, self.table.get_by_uuid(self.uuid_1)) + + def test_distance_to(self) -> None: + self.assertEqual(self.point_1.distance_to(Point3d(0, 2, 2)), 1) + + def test_eq(self) -> None: + with self.subTest(msg="overload with Point"): + self.assertTrue(self.point_1 == Point(0, 1, 2)) + + with self.subTest(msg="overload with Point3d"): + self.assertTrue(self.point_1 == Point3d(0, 1, 2)) + + def test_is_coincident(self) -> None: + self.assertTrue(self.point_1.is_coincident(Point3d(0, 1, 2))) + + def test_is_valid(self) -> None: + self.assertTrue(self.point_1.is_valid()) + + def test_ne(self) -> None: + with self.subTest(msg="overload with Point"): + self.assertTrue(self.point_1 != Point(1, 1, 2)) + + with self.subTest(msg="overload with Point3d"): + self.assertTrue(self.point_1 != Point3d(0, 2, 2)) + + def test_uuid(self) -> None: + self.assertEqual(self.uuid_1, self.point_1.obj_uuid) + + def test_x(self) -> None: + self.assertEqual(0, self.point_1.x) + + def test_y(self) -> None: + self.assertEqual(1, self.point_1.y) + + def test_z(self) -> None: + self.assertEqual(2, self.point_1.z)