diff --git a/cpp/modmesh/universe/World.hpp b/cpp/modmesh/universe/World.hpp index 3abdb132..2ada6367 100644 --- a/cpp/modmesh/universe/World.hpp +++ b/cpp/modmesh/universe/World.hpp @@ -32,11 +32,702 @@ #include #include -#include +#include +#include +#include +#include +#include +#include namespace modmesh { +// Forward declaration for Shape::draw(). +template +class World; + +// --------------------------------------------------------------------------- +// Shape base class +// --------------------------------------------------------------------------- + +/** + * Abstract base for every shape managed by World. + * + * All concrete shapes must implement the pure-virtual methods below. Shapes + * live on the z = 0 plane; Point3d is used for compatibility with the + * existing rendering pipeline. + */ +template +class Shape + : public std::enable_shared_from_this> +{ + +public: + + using value_type = T; + using point_type = Point3d; + + virtual ~Shape() = default; + + int32_t id() const { return m_id; } + void set_id(int32_t v) { m_id = v; } + bool closed() const { return m_closed; } + + /// Number of vertices that define the shape boundary. + virtual size_t nvertex() const = 0; + + /// Return the *i*-th vertex (bounds-checked). + virtual point_type vertex(size_t i) const = 0; + + // -- transforms --------------------------------------------------------- + + virtual void translate(T dx, T dy) = 0; + virtual void scale(T factor, T cx = 0, T cy = 0) = 0; + virtual void rotate(T angle_deg, T cx = 0, T cy = 0) = 0; + + // -- rendering ---------------------------------------------------------- + + /// Write the shape as segments into *world*. + virtual void draw(World & world) const = 0; + + // -- utilities ---------------------------------------------------------- + + virtual std::shared_ptr> clone() const = 0; + virtual std::string type_name() const = 0; + +protected: + + explicit Shape(bool closed_flag) + : m_closed(closed_flag) + { + } + + int32_t m_id = -1; + bool m_closed; +}; + +// --------------------------------------------------------------------------- +// Concrete shape classes +// --------------------------------------------------------------------------- + +/** + * Closed polygon defined by an arbitrary vertex list (>= 3 vertices). + */ +template +class Polygon + : public Shape +{ + +public: + + using typename Shape::value_type; + using typename Shape::point_type; + + explicit Polygon(std::vector vertices) + : Shape(/* closed */ true) + , m_vertices(std::move(vertices)) + { + if (m_vertices.size() < 3) + { + throw std::invalid_argument( + "Polygon requires at least 3 vertices"); + } + } + + void set_vertices(std::vector const & vertices) + { + // TODO(drawing API): implement + throw std::runtime_error("Polygon::set_vertices: not yet implemented"); + } + + size_t nvertex() const override + { + // TODO(drawing API): implement + throw std::runtime_error("Polygon::nvertex: not yet implemented"); + } + + point_type vertex(size_t i) const override + { + // TODO(drawing API): implement + throw std::runtime_error("Polygon::vertex: not yet implemented"); + } + + void translate(T dx, T dy) override + { + // TODO(drawing API): implement + throw std::runtime_error("Polygon::translate: not yet implemented"); + } + + void scale(T factor, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Polygon::scale: not yet implemented"); + } + + void rotate(T angle_deg, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Polygon::rotate: not yet implemented"); + } + + void draw(World & world) const override + { + // TODO(drawing API): implement -- emit closed polygon as segments + throw std::runtime_error("Polygon::draw: not yet implemented"); + } + + std::shared_ptr> clone() const override + { + // TODO(drawing API): implement + throw std::runtime_error("Polygon::clone: not yet implemented"); + } + + std::string type_name() const override { return "Polygon"; } + +private: + + std::vector m_vertices; + +}; /* end class Polygon */ + +/** + * Axis-aligned rectangle (before transforms) defined by lower-left corner + * and dimensions. + */ +template +class Rectangle + : public Shape +{ + +public: + + using typename Shape::value_type; + using typename Shape::point_type; + + Rectangle(T x, T y, T width, T height) + : Shape(/* closed */ true) + , m_x(x) + , m_y(y) + , m_width(width) + , m_height(height) + { + } + + T x() const { return m_x; } + T y() const { return m_y; } + T width() const { return m_width; } + T height() const { return m_height; } + + void set_width(T v) { m_width = v; } + void set_height(T v) { m_height = v; } + + size_t nvertex() const override + { + // TODO(drawing API): implement -- always 4 + throw std::runtime_error("Rectangle::nvertex: not yet implemented"); + } + + point_type vertex(size_t i) const override + { + // TODO(drawing API): implement + throw std::runtime_error("Rectangle::vertex: not yet implemented"); + } + + void translate(T dx, T dy) override + { + // TODO(drawing API): implement + throw std::runtime_error("Rectangle::translate: not yet implemented"); + } + + void scale(T factor, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Rectangle::scale: not yet implemented"); + } + + void rotate(T angle_deg, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Rectangle::rotate: not yet implemented"); + } + + void draw(World & world) const override + { + // TODO(drawing API): implement -- emit 4 segments + throw std::runtime_error("Rectangle::draw: not yet implemented"); + } + + std::shared_ptr> clone() const override + { + // TODO(drawing API): implement + throw std::runtime_error("Rectangle::clone: not yet implemented"); + } + + std::string type_name() const override { return "Rectangle"; } + +private: + + T m_x; + T m_y; + T m_width; + T m_height; + +}; /* end class Rectangle */ + +/** + * Triangle defined by three vertices. + */ +template +class Triangle + : public Shape +{ + +public: + + using typename Shape::value_type; + using typename Shape::point_type; + + Triangle(point_type p0, point_type p1, point_type p2) + : Shape(/* closed */ true) + , m_p0(p0) + , m_p1(p1) + , m_p2(p2) + { + } + + Triangle(T x0, T y0, T x1, T y1, T x2, T y2) + : Triangle( + point_type(x0, y0, 0), + point_type(x1, y1, 0), + point_type(x2, y2, 0)) + { + } + + size_t nvertex() const override + { + // TODO(drawing API): implement -- always 3 + throw std::runtime_error("Triangle::nvertex: not yet implemented"); + } + + point_type vertex(size_t i) const override + { + // TODO(drawing API): implement + throw std::runtime_error("Triangle::vertex: not yet implemented"); + } + + void translate(T dx, T dy) override + { + // TODO(drawing API): implement + throw std::runtime_error("Triangle::translate: not yet implemented"); + } + + void scale(T factor, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Triangle::scale: not yet implemented"); + } + + void rotate(T angle_deg, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Triangle::rotate: not yet implemented"); + } + + void draw(World & world) const override + { + // TODO(drawing API): implement -- emit 3 segments + throw std::runtime_error("Triangle::draw: not yet implemented"); + } + + std::shared_ptr> clone() const override + { + // TODO(drawing API): implement + throw std::runtime_error("Triangle::clone: not yet implemented"); + } + + std::string type_name() const override { return "Triangle"; } + +private: + + point_type m_p0; + point_type m_p1; + point_type m_p2; + +}; /* end class Triangle */ + +/** + * Circle approximated as a regular *nseg*-gon. + */ +template +class Circle + : public Shape +{ + +public: + + using typename Shape::value_type; + using typename Shape::point_type; + + Circle(T cx, T cy, T radius, size_t nseg = 64) + : Shape(/* closed */ true) + , m_cx(cx) + , m_cy(cy) + , m_radius(radius) + , m_nseg(nseg) + { + if (radius <= 0) + { + throw std::invalid_argument("Circle: radius must be positive"); + } + if (nseg < 3) + { + throw std::invalid_argument("Circle: nseg must be >= 3"); + } + } + + T cx() const { return m_cx; } + T cy() const { return m_cy; } + T radius() const { return m_radius; } + size_t nseg() const { return m_nseg; } + + void set_radius(T v) { m_radius = v; } + + size_t nvertex() const override + { + // TODO(drawing API): implement -- returns m_nseg + throw std::runtime_error("Circle::nvertex: not yet implemented"); + } + + point_type vertex(size_t i) const override + { + // TODO(drawing API): implement -- compute point on circle + throw std::runtime_error("Circle::vertex: not yet implemented"); + } + + void translate(T dx, T dy) override + { + // TODO(drawing API): implement + throw std::runtime_error("Circle::translate: not yet implemented"); + } + + void scale(T factor, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Circle::scale: not yet implemented"); + } + + void rotate(T angle_deg, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Circle::rotate: not yet implemented"); + } + + void draw(World & world) const override + { + // TODO(drawing API): implement -- emit nseg segments forming a circle + throw std::runtime_error("Circle::draw: not yet implemented"); + } + + std::shared_ptr> clone() const override + { + // TODO(drawing API): implement + throw std::runtime_error("Circle::clone: not yet implemented"); + } + + std::string type_name() const override { return "Circle"; } + +private: + + T m_cx; + T m_cy; + T m_radius; + size_t m_nseg; + +}; /* end class Circle */ + +/** + * Single line segment between two points (open, not closed). + */ +template +class Line + : public Shape +{ + +public: + + using typename Shape::value_type; + using typename Shape::point_type; + + Line(T x0, T y0, T x1, T y1) + : Shape(/* closed */ false) + , m_p0(point_type(x0, y0, 0)) + , m_p1(point_type(x1, y1, 0)) + { + } + + Line(point_type p0, point_type p1) + : Shape(/* closed */ false) + , m_p0(p0) + , m_p1(p1) + { + } + + T x0() const { return m_p0.x(); } + T y0() const { return m_p0.y(); } + T x1() const { return m_p1.x(); } + T y1() const { return m_p1.y(); } + + size_t nvertex() const override + { + // TODO(drawing API): implement -- always 2 + throw std::runtime_error("Line::nvertex: not yet implemented"); + } + + point_type vertex(size_t i) const override + { + // TODO(drawing API): implement + throw std::runtime_error("Line::vertex: not yet implemented"); + } + + void translate(T dx, T dy) override + { + // TODO(drawing API): implement + throw std::runtime_error("Line::translate: not yet implemented"); + } + + void scale(T factor, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Line::scale: not yet implemented"); + } + + void rotate(T angle_deg, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Line::rotate: not yet implemented"); + } + + void draw(World & world) const override + { + // TODO(drawing API): implement + throw std::runtime_error("Line::draw: not yet implemented"); + } + + std::shared_ptr> clone() const override + { + // TODO(drawing API): implement + throw std::runtime_error("Line::clone: not yet implemented"); + } + + std::string type_name() const override { return "Line"; } + +private: + + point_type m_p0; + point_type m_p1; + +}; /* end class Line */ + +/** + * Open polyline (not closed) defined by an ordered vertex list (>= 2). + */ +template +class Polyline + : public Shape +{ + +public: + + using typename Shape::value_type; + using typename Shape::point_type; + + explicit Polyline(std::vector vertices) + : Shape(/* closed */ false) + , m_vertices(std::move(vertices)) + { + if (m_vertices.size() < 2) + { + throw std::invalid_argument( + "Polyline requires at least 2 vertices"); + } + } + + void set_vertices(std::vector const & vertices) + { + // TODO(drawing API): implement + throw std::runtime_error("Polyline::set_vertices: not yet implemented"); + } + + size_t nvertex() const override + { + // TODO(drawing API): implement + throw std::runtime_error("Polyline::nvertex: not yet implemented"); + } + + point_type vertex(size_t i) const override + { + // TODO(drawing API): implement + throw std::runtime_error("Polyline::vertex: not yet implemented"); + } + + void translate(T dx, T dy) override + { + // TODO(drawing API): implement + throw std::runtime_error("Polyline::translate: not yet implemented"); + } + + void scale(T factor, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Polyline::scale: not yet implemented"); + } + + void rotate(T angle_deg, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Polyline::rotate: not yet implemented"); + } + + void draw(World & world) const override + { + // TODO(drawing API): implement -- emit (n-1) open segments + throw std::runtime_error("Polyline::draw: not yet implemented"); + } + + std::shared_ptr> clone() const override + { + // TODO(drawing API): implement + throw std::runtime_error("Polyline::clone: not yet implemented"); + } + + std::string type_name() const override { return "Polyline"; } + +private: + + std::vector m_vertices; + +}; /* end class Polyline */ + +/** + * Ellipse approximated as a closed *nseg*-gon. + */ +template +class Ellipse + : public Shape +{ + +public: + + using typename Shape::value_type; + using typename Shape::point_type; + + Ellipse(T cx, T cy, T a, T b, size_t nseg = 64) + : Shape(/* closed */ true) + , m_cx(cx) + , m_cy(cy) + , m_a(a) + , m_b(b) + , m_nseg(nseg) + { + if (a <= 0 || b <= 0) + { + throw std::invalid_argument( + "Ellipse: semi-axes a and b must be positive"); + } + if (nseg < 3) + { + throw std::invalid_argument("Ellipse: nseg must be >= 3"); + } + } + + T cx() const { return m_cx; } + T cy() const { return m_cy; } + T a() const { return m_a; } + T b() const { return m_b; } + size_t nseg() const { return m_nseg; } + + void set_a(T v) { m_a = v; } + void set_b(T v) { m_b = v; } + + size_t nvertex() const override + { + // TODO(drawing API): implement -- returns m_nseg + throw std::runtime_error("Ellipse::nvertex: not yet implemented"); + } + + point_type vertex(size_t i) const override + { + // TODO(drawing API): implement -- compute point on ellipse + throw std::runtime_error("Ellipse::vertex: not yet implemented"); + } + + void translate(T dx, T dy) override + { + // TODO(drawing API): implement + throw std::runtime_error("Ellipse::translate: not yet implemented"); + } + + void scale(T factor, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Ellipse::scale: not yet implemented"); + } + + void rotate(T angle_deg, T cx = 0, T cy = 0) override + { + // TODO(drawing API): implement + throw std::runtime_error("Ellipse::rotate: not yet implemented"); + } + + void draw(World & world) const override + { + // TODO(drawing API): implement -- emit nseg segments forming an ellipse + throw std::runtime_error("Ellipse::draw: not yet implemented"); + } + + std::shared_ptr> clone() const override + { + // TODO(drawing API): implement + throw std::runtime_error("Ellipse::clone: not yet implemented"); + } + + std::string type_name() const override { return "Ellipse"; } + +private: + + T m_cx; + T m_cy; + T m_a; + T m_b; + size_t m_nseg; + +}; /* end class Ellipse */ + +// Type aliases +template +using ShapePtr = std::shared_ptr>; + +using ShapeFp32 = Shape; +using ShapeFp64 = Shape; +using PolygonFp32 = Polygon; +using PolygonFp64 = Polygon; +using RectangleFp32 = Rectangle; +using RectangleFp64 = Rectangle; +using TriangleFp32 = Triangle; +using TriangleFp64 = Triangle; +using CircleFp32 = Circle; +using CircleFp64 = Circle; +using LineFp32 = Line; +using LineFp64 = Line; +using PolylineFp32 = Polyline; +using PolylineFp64 = Polyline; +using EllipseFp32 = Ellipse; +using EllipseFp64 = Ellipse; + +// --------------------------------------------------------------------------- +// World -- manages all geometry entities including shapes +// --------------------------------------------------------------------------- + /** * Manage all geometry entities. */ @@ -60,6 +751,8 @@ class World using point_type = Point3d; using segment_type = Segment3d; using bezier_type = Bezier3d; + using shape_type = Shape; + using shape_ptr = std::shared_ptr; using point_pad_type = PointPad; using segment_pad_type = SegmentPad; @@ -85,6 +778,8 @@ class World World & operator=(World &&) = delete; ~World() = default; + // -- points ------------------------------------------------------------- + void add_point(point_type const & vertex) { m_points->append(vertex); @@ -102,6 +797,8 @@ class World } std::shared_ptr const & points() { return m_points; } + // -- segments ----------------------------------------------------------- + void add_segment(segment_type const & segment) { m_segments->append(segment); @@ -119,6 +816,8 @@ class World } std::shared_ptr const & segments() { return m_segments; } + // -- bezier curves ------------------------------------------------------ + void add_bezier(bezier_type const & bezier) { m_curves->append(bezier); @@ -136,6 +835,103 @@ class World } std::shared_ptr const & curves() { return m_curves; } + // -- shapes ------------------------------------------------------------- + + /// Add a pre-built shape. Returns the assigned ID. + int32_t add_shape(shape_ptr shape) + { + // TODO(drawing API): implement + throw std::runtime_error("World::add_shape: not yet implemented"); + } + + int32_t add_polygon(std::vector const & vertices) + { + // TODO(drawing API): implement + throw std::runtime_error("World::add_polygon: not yet implemented"); + } + + int32_t add_rectangle(T x, T y, T width, T height) + { + // TODO(drawing API): implement + throw std::runtime_error("World::add_rectangle: not yet implemented"); + } + + int32_t add_triangle(T x0, T y0, T x1, T y1, T x2, T y2) + { + // TODO(drawing API): implement + throw std::runtime_error("World::add_triangle: not yet implemented"); + } + + int32_t add_circle(T cx, T cy, T radius, size_t nseg = 64) + { + // TODO(drawing API): implement + throw std::runtime_error("World::add_circle: not yet implemented"); + } + + int32_t add_line(T x0, T y0, T x1, T y1) + { + // TODO(drawing API): implement + throw std::runtime_error("World::add_line: not yet implemented"); + } + + int32_t add_polyline(std::vector const & vertices) + { + // TODO(drawing API): implement + throw std::runtime_error("World::add_polyline: not yet implemented"); + } + + int32_t add_ellipse(T cx, T cy, T a, T b, size_t nseg = 64) + { + // TODO(drawing API): implement + throw std::runtime_error("World::add_ellipse: not yet implemented"); + } + + /// Get shape by ID. Throws std::out_of_range if not found. + shape_ptr get_shape(int32_t shape_id) const + { + // TODO(drawing API): implement + throw std::runtime_error("World::get_shape: not yet implemented"); + } + + size_t nshape() const { return m_shapes.size(); } + + /// Return all managed shapes. + std::vector shapes() const + { + std::vector result; + result.reserve(m_shapes.size()); + for (auto const & [id, sp] : m_shapes) + { + result.push_back(sp); + } + return result; + } + + void remove_shape(int32_t shape_id) + { + // TODO(drawing API): implement -- erase shape + throw std::runtime_error("World::remove_shape: not yet implemented"); + } + + void clear_shapes() + { + // TODO(drawing API): implement -- remove all shapes + throw std::runtime_error("World::clear_shapes: not yet implemented"); + } + + // -- clear all ---------------------------------------------------------- + + /** + * Remove all geometry entities (points, segments, curves, shapes) + * from the world. + */ + void clear() + { + // TODO(drawing API): implement clearing points, segments, curves + m_shapes.clear(); + m_next_shape_id = 0; + } + private: void check_size(size_t i, size_t s, char const * msg) const @@ -149,6 +945,8 @@ class World std::shared_ptr m_points; std::shared_ptr m_segments; std::shared_ptr m_curves; + std::unordered_map m_shapes; + int32_t m_next_shape_id = 0; }; /* end class World */ diff --git a/cpp/modmesh/universe/pymod/wrap_World.cpp b/cpp/modmesh/universe/pymod/wrap_World.cpp index 0d544856..76e02608 100644 --- a/cpp/modmesh/universe/pymod/wrap_World.cpp +++ b/cpp/modmesh/universe/pymod/wrap_World.cpp @@ -28,6 +28,7 @@ #include // Must be the first include. #include +#include namespace modmesh { @@ -35,6 +36,270 @@ namespace modmesh namespace python { +// --------------------------------------------------------------------------- +// Shape base + concrete shapes +// --------------------------------------------------------------------------- + +template +class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapDrawingShape + : public WrapBase, Shape, std::shared_ptr>> +{ + +public: + + using base_type = WrapBase, Shape, std::shared_ptr>>; + using wrapped_type = typename base_type::wrapped_type; + using point_type = typename wrapped_type::point_type; + + friend typename base_type::root_base_type; + +protected: + + WrapDrawingShape(pybind11::module & mod, char const * pyname, char const * pydoc) + : base_type(mod, pyname, pydoc) + { + namespace py = pybind11; + + (*this) + .def_property_readonly("id", &wrapped_type::id) + .def_property_readonly("closed", &wrapped_type::closed) + .def_property_readonly("nvertex", &wrapped_type::nvertex) + .def("vertex", &wrapped_type::vertex, py::arg("i")) + .def("translate", &wrapped_type::translate, py::arg("dx"), py::arg("dy")) + .def("scale", &wrapped_type::scale, py::arg("factor"), py::arg("cx") = 0, py::arg("cy") = 0) + .def("rotate", &wrapped_type::rotate, py::arg("angle_deg"), py::arg("cx") = 0, py::arg("cy") = 0) + .def("type_name", &wrapped_type::type_name) + // + ; + } +}; + +template +class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapDrawingPolygon + : public WrapBase, Polygon, std::shared_ptr>, Shape> +{ + +public: + + using base_type = WrapBase, Polygon, std::shared_ptr>, Shape>; + using wrapped_type = typename base_type::wrapped_type; + using point_type = typename wrapped_type::point_type; + + friend typename base_type::root_base_type; + +protected: + + WrapDrawingPolygon(pybind11::module & mod, char const * pyname, char const * pydoc) + : base_type(mod, pyname, pydoc) + { + namespace py = pybind11; + + (*this) + .def(py::init>(), py::arg("vertices")) + .def("set_vertices", &wrapped_type::set_vertices, py::arg("vertices")) + // + ; + } +}; + +template +class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapDrawingRectangle + : public WrapBase, Rectangle, std::shared_ptr>, Shape> +{ + +public: + + using base_type = WrapBase, Rectangle, std::shared_ptr>, Shape>; + using wrapped_type = typename base_type::wrapped_type; + + friend typename base_type::root_base_type; + +protected: + + WrapDrawingRectangle(pybind11::module & mod, char const * pyname, char const * pydoc) + : base_type(mod, pyname, pydoc) + { + namespace py = pybind11; + + (*this) + .def(py::init(), py::arg("x"), py::arg("y"), py::arg("width"), py::arg("height")) + .def_property_readonly("x", &wrapped_type::x) + .def_property_readonly("y", &wrapped_type::y) + .def_property("width", &wrapped_type::width, &wrapped_type::set_width) + .def_property("height", &wrapped_type::height, &wrapped_type::set_height) + // + ; + } +}; + +template +class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapDrawingTriangle + : public WrapBase, Triangle, std::shared_ptr>, Shape> +{ + +public: + + using base_type = WrapBase, Triangle, std::shared_ptr>, Shape>; + using wrapped_type = typename base_type::wrapped_type; + + friend typename base_type::root_base_type; + +protected: + + WrapDrawingTriangle(pybind11::module & mod, char const * pyname, char const * pydoc) + : base_type(mod, pyname, pydoc) + { + namespace py = pybind11; + + (*this) + .def(py::init(), + py::arg("x0"), + py::arg("y0"), + py::arg("x1"), + py::arg("y1"), + py::arg("x2"), + py::arg("y2")) + // + ; + } +}; + +template +class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapDrawingCircle + : public WrapBase, Circle, std::shared_ptr>, Shape> +{ + +public: + + using base_type = WrapBase, Circle, std::shared_ptr>, Shape>; + using wrapped_type = typename base_type::wrapped_type; + + friend typename base_type::root_base_type; + +protected: + + WrapDrawingCircle(pybind11::module & mod, char const * pyname, char const * pydoc) + : base_type(mod, pyname, pydoc) + { + namespace py = pybind11; + + (*this) + .def(py::init(), + py::arg("cx"), + py::arg("cy"), + py::arg("radius"), + py::arg("nseg") = 64) + .def_property_readonly("cx", &wrapped_type::cx) + .def_property_readonly("cy", &wrapped_type::cy) + .def_property("radius", &wrapped_type::radius, &wrapped_type::set_radius) + .def_property_readonly("nseg", &wrapped_type::nseg) + // + ; + } +}; + +template +class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapDrawingLine + : public WrapBase, Line, std::shared_ptr>, Shape> +{ + +public: + + using base_type = WrapBase, Line, std::shared_ptr>, Shape>; + using wrapped_type = typename base_type::wrapped_type; + + friend typename base_type::root_base_type; + +protected: + + WrapDrawingLine(pybind11::module & mod, char const * pyname, char const * pydoc) + : base_type(mod, pyname, pydoc) + { + namespace py = pybind11; + + (*this) + .def(py::init(), + py::arg("x0"), + py::arg("y0"), + py::arg("x1"), + py::arg("y1")) + .def_property_readonly("x0", &wrapped_type::x0) + .def_property_readonly("y0", &wrapped_type::y0) + .def_property_readonly("x1", &wrapped_type::x1) + .def_property_readonly("y1", &wrapped_type::y1) + // + ; + } +}; + +template +class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapDrawingPolyline + : public WrapBase, Polyline, std::shared_ptr>, Shape> +{ + +public: + + using base_type = WrapBase, Polyline, std::shared_ptr>, Shape>; + using wrapped_type = typename base_type::wrapped_type; + using point_type = typename wrapped_type::point_type; + + friend typename base_type::root_base_type; + +protected: + + WrapDrawingPolyline(pybind11::module & mod, char const * pyname, char const * pydoc) + : base_type(mod, pyname, pydoc) + { + namespace py = pybind11; + + (*this) + .def(py::init>(), py::arg("vertices")) + .def("set_vertices", &wrapped_type::set_vertices, py::arg("vertices")) + // + ; + } +}; + +template +class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapDrawingEllipse + : public WrapBase, Ellipse, std::shared_ptr>, Shape> +{ + +public: + + using base_type = WrapBase, Ellipse, std::shared_ptr>, Shape>; + using wrapped_type = typename base_type::wrapped_type; + + friend typename base_type::root_base_type; + +protected: + + WrapDrawingEllipse(pybind11::module & mod, char const * pyname, char const * pydoc) + : base_type(mod, pyname, pydoc) + { + namespace py = pybind11; + + (*this) + .def(py::init(), + py::arg("cx"), + py::arg("cy"), + py::arg("a"), + py::arg("b"), + py::arg("nseg") = 64) + .def_property_readonly("cx", &wrapped_type::cx) + .def_property_readonly("cy", &wrapped_type::cy) + .def_property("a", &wrapped_type::a, &wrapped_type::set_a) + .def_property("b", &wrapped_type::b, &wrapped_type::set_b) + .def_property_readonly("nseg", &wrapped_type::nseg) + // + ; + } +}; + +// --------------------------------------------------------------------------- +// World +// --------------------------------------------------------------------------- + template class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapWorld : public WrapBase, World, std::shared_ptr>> @@ -51,6 +316,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapWorld using segment_pad_type = typename wrapped_type::segment_pad_type; using bezier_type = typename wrapped_type::bezier_type; using curve_pad_type = typename wrapped_type::curve_pad_type; + using shape_ptr = typename wrapped_type::shape_ptr; friend typename base_type::root_base_type; @@ -62,6 +328,7 @@ class MODMESH_PYTHON_WRAPPER_VISIBILITY WrapWorld WrapWorld & wrap_point(); WrapWorld & wrap_segment(); WrapWorld & wrap_bezier(); + WrapWorld & wrap_shape(); }; /* end class WrapWorld */ @@ -76,6 +343,7 @@ WrapWorld::WrapWorld(pybind11::module & mod, const char * pyname, const char .wrap_point() .wrap_segment() .wrap_bezier() + .wrap_shape() // ; } @@ -90,6 +358,7 @@ WrapWorld & WrapWorld::wrap_management() py::init( []() { return wrapped_type::construct(); })) + .def("clear", &wrapped_type::clear) // ; @@ -223,8 +492,60 @@ WrapWorld & WrapWorld::wrap_bezier() return *this; } +template +WrapWorld & WrapWorld::wrap_shape() +{ + namespace py = pybind11; + + (*this) + .def("add_shape", &wrapped_type::add_shape, py::arg("shape")) + .def("add_polygon", &wrapped_type::add_polygon, py::arg("vertices")) + .def("add_rectangle", &wrapped_type::add_rectangle, py::arg("x"), py::arg("y"), py::arg("width"), py::arg("height")) + .def("add_triangle", &wrapped_type::add_triangle, py::arg("x0"), py::arg("y0"), py::arg("x1"), py::arg("y1"), py::arg("x2"), py::arg("y2")) + .def("add_circle", &wrapped_type::add_circle, py::arg("cx"), py::arg("cy"), py::arg("radius"), py::arg("nseg") = 64) + .def("add_line", &wrapped_type::add_line, py::arg("x0"), py::arg("y0"), py::arg("x1"), py::arg("y1")) + .def("add_polyline", &wrapped_type::add_polyline, py::arg("vertices")) + .def("add_ellipse", &wrapped_type::add_ellipse, py::arg("cx"), py::arg("cy"), py::arg("a"), py::arg("b"), py::arg("nseg") = 64) + .def("get_shape", &wrapped_type::get_shape, py::arg("shape_id")) + .def_property_readonly("nshape", &wrapped_type::nshape) + .def_property_readonly("shapes", &wrapped_type::shapes) + .def("remove_shape", &wrapped_type::remove_shape, py::arg("shape_id")) + .def("clear_shapes", &wrapped_type::clear_shapes) + // + ; + + return *this; +} + void wrap_World(pybind11::module & mod) { + // Shape base (abstract -- needed for polymorphic return types) + WrapDrawingShape::commit(mod, "ShapeFp32", "ShapeFp32"); + WrapDrawingShape::commit(mod, "ShapeFp64", "ShapeFp64"); + + // Concrete shapes + WrapDrawingPolygon::commit(mod, "PolygonFp32", "PolygonFp32"); + WrapDrawingPolygon::commit(mod, "PolygonFp64", "PolygonFp64"); + + WrapDrawingRectangle::commit(mod, "RectangleFp32", "RectangleFp32"); + WrapDrawingRectangle::commit(mod, "RectangleFp64", "RectangleFp64"); + + WrapDrawingTriangle::commit(mod, "TriangleFp32", "TriangleFp32"); + WrapDrawingTriangle::commit(mod, "TriangleFp64", "TriangleFp64"); + + WrapDrawingCircle::commit(mod, "CircleFp32", "CircleFp32"); + WrapDrawingCircle::commit(mod, "CircleFp64", "CircleFp64"); + + WrapDrawingLine::commit(mod, "LineFp32", "LineFp32"); + WrapDrawingLine::commit(mod, "LineFp64", "LineFp64"); + + WrapDrawingPolyline::commit(mod, "PolylineFp32", "PolylineFp32"); + WrapDrawingPolyline::commit(mod, "PolylineFp64", "PolylineFp64"); + + WrapDrawingEllipse::commit(mod, "EllipseFp32", "EllipseFp32"); + WrapDrawingEllipse::commit(mod, "EllipseFp64", "EllipseFp64"); + + // World WrapWorld::commit(mod, "WorldFp32", "WorldFp32"); WrapWorld::commit(mod, "WorldFp64", "WorldFp64"); } diff --git a/modmesh/core.py b/modmesh/core.py index 59f1c232..3ba8d417 100644 --- a/modmesh/core.py +++ b/modmesh/core.py @@ -163,6 +163,22 @@ 'CurvePadFp64', 'WorldFp32', 'WorldFp64', + 'ShapeFp32', + 'ShapeFp64', + 'PolygonFp32', + 'PolygonFp64', + 'RectangleFp32', + 'RectangleFp64', + 'TriangleFp32', + 'TriangleFp64', + 'CircleFp32', + 'CircleFp64', + 'LineFp32', + 'LineFp64', + 'PolylineFp32', + 'PolylineFp64', + 'EllipseFp32', + 'EllipseFp64', 'PolygonPadFp32', 'PolygonPadFp64', 'Polygon3dFp32', diff --git a/tests/test_pilot_drawing.py b/tests/test_pilot_drawing.py new file mode 100644 index 00000000..6e4a5819 --- /dev/null +++ b/tests/test_pilot_drawing.py @@ -0,0 +1,84 @@ +# Copyright (c) 2026, An-Chi Liu +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# - Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + +""" +Demonstrate the World drawing API. + +Usage pattern:: + + world = mm.WorldFp64() + world.add_rectangle(0, 0, 10, 5) + shape = world.get_shape(0) + shape.translate(1, 2) + widget.updateWorld(world) +""" + +import unittest + +import modmesh as mm +import pytest + +pt = mm.Point3dFp64 + + +def _pt(x, y): + return pt(x, y, 0.0) + + +@pytest.mark.xfail(reason="Drawing API is prototype-only (stubs throw)") +class WorldShapeBasicTC(unittest.TestCase): + """Basic create / read / remove workflow.""" + + def test_add_and_get(self): + w = mm.WorldFp64() + sid = w.add_rectangle(x=0, y=0, width=4, height=3) + self.assertEqual(w.nshape, 1) + shape = w.get_shape(sid) + self.assertEqual(shape.type_name(), "Rectangle") + + def test_add_multiple_shapes(self): + w = mm.WorldFp64() + w.add_line(0, 0, 1, 1) + w.add_circle(cx=5, cy=5, radius=2, nseg=16) + w.add_polygon([_pt(0, 0), _pt(3, 0), _pt(3, 4)]) + self.assertEqual(w.nshape, 3) + + def test_remove(self): + w = mm.WorldFp64() + sid = w.add_line(0, 0, 1, 1) + w.remove_shape(sid) + self.assertEqual(w.nshape, 0) + + def test_clear_shapes(self): + w = mm.WorldFp64() + w.add_line(0, 0, 1, 1) + w.add_circle(cx=0, cy=0, radius=1) + w.clear_shapes() + self.assertEqual(w.nshape, 0) + + +# vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: