From 7275dc92e24aa857f6b0be2590968dc364290b79 Mon Sep 17 00:00:00 2001 From: tomvanmele Date: Tue, 19 Sep 2023 10:16:53 +0200 Subject: [PATCH] remove robots and update the docs --- CHANGELOG.md | 5 + docs/reference/compas.robots.rst | 101 -- docs/reference/compas_blender.conversions.rst | 33 +- docs/reference/compas_blender.rst | 1 - docs/reference/compas_blender.ui.rst | 5 - docs/reference/compas_ghpython.artists.rst | 59 + docs/reference/compas_rhino.artists.rst | 55 + docs/reference/compas_rhino.conversions.rst | 98 ++ docs/reference/compas_rhino.forms.rst | 29 + docs/reference/compas_rhino.geometry.rst | 63 + docs/userguide/tutorials.rst | 1 - docs/userguide/tutorials/robots.rst | 450 ------- src/compas/artists/__init__.py | 4 - src/compas/artists/robotmodelartist.py | 523 -------- src/compas/robots/__init__.py | 49 - src/compas/robots/configuration.py | 617 --------- src/compas/robots/model/__init__.py | 0 src/compas/robots/model/base.py | 118 -- src/compas/robots/model/geometry.py | 475 ------- src/compas/robots/model/joint.py | 779 ----------- src/compas/robots/model/link.py | 512 ------- src/compas/robots/model/robot.py | 1188 ----------------- src/compas/robots/model/tool.py | 180 --- src/compas/robots/resources/__init__.py | 0 src/compas/robots/resources/basic.py | 307 ----- src/compas/robots/resources/github.py | 131 -- src/compas_blender/artists/__init__.py | 4 - src/compas_blender/utilities/__init__.py | 77 -- src/compas_ghpython/artists/__init__.py | 77 -- .../artists/robotmodelartist.py | 56 - src/compas_ghpython/utilities/__init__.py | 57 - src/compas_rhino/artists/__init__.py | 89 -- src/compas_rhino/artists/robotmodelartist.py | 252 ---- src/compas_rhino/conduits/__init__.py | 26 - src/compas_rhino/conversions/__init__.py | 101 -- src/compas_rhino/forms/__init__.py | 33 - src/compas_rhino/geometry/__init__.py | 98 -- src/compas_rhino/utilities/__init__.py | 96 -- 38 files changed, 337 insertions(+), 6412 deletions(-) delete mode 100644 docs/reference/compas.robots.rst delete mode 100644 docs/reference/compas_blender.ui.rst create mode 100644 docs/reference/compas_ghpython.artists.rst create mode 100644 docs/reference/compas_rhino.artists.rst create mode 100644 docs/reference/compas_rhino.conversions.rst create mode 100644 docs/reference/compas_rhino.forms.rst create mode 100644 docs/reference/compas_rhino.geometry.rst delete mode 100644 docs/userguide/tutorials/robots.rst delete mode 100644 src/compas/artists/robotmodelartist.py delete mode 100644 src/compas/robots/__init__.py delete mode 100644 src/compas/robots/configuration.py delete mode 100644 src/compas/robots/model/__init__.py delete mode 100644 src/compas/robots/model/base.py delete mode 100644 src/compas/robots/model/geometry.py delete mode 100644 src/compas/robots/model/joint.py delete mode 100644 src/compas/robots/model/link.py delete mode 100644 src/compas/robots/model/robot.py delete mode 100644 src/compas/robots/model/tool.py delete mode 100644 src/compas/robots/resources/__init__.py delete mode 100644 src/compas/robots/resources/basic.py delete mode 100644 src/compas/robots/resources/github.py delete mode 100644 src/compas_ghpython/artists/robotmodelartist.py delete mode 100644 src/compas_rhino/artists/robotmodelartist.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 475b0af5dd9..9c184d0e6b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -285,6 +285,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Removed `compas_rhino.artists.VolMeshArtist.draw_edgelabels`. * Removed `compas_rhino.artists.VolMeshArtist.draw_facelabels`. * Removed `compas_rhino.artists.VolMeshArtist.draw_celllabels`. +* Removed `compas.robots`. +* Removed `compas.artists.robotmodelartist`. +* Removed `compas_blender.artists.robotmodelartist`. +* Removed `compas_ghpython.artists.robotmodelartist`. +* Removed `compas_rhino.artists.robotmodelartist`. ## [1.17.5] 2023-02-16 diff --git a/docs/reference/compas.robots.rst b/docs/reference/compas.robots.rst deleted file mode 100644 index 3b8dd2183e6..00000000000 --- a/docs/reference/compas.robots.rst +++ /dev/null @@ -1,101 +0,0 @@ -******************************************************************************** -compas.robots -******************************************************************************** - -.. currentmodule:: compas.robots - -.. warning:: - - Note that this package will be removed from :mod:`compas` and moved to a separate core extension, ``compas_robots``. - -.. rst-class:: lead - -This package provides classes for describing robots, their components, and their kinematic behaviour. - - -Model -===== - -The root of the model is the :class:`RobotModel` class, which -describes a robot consisting of a set of link elements, and a set of joint -elements connecting the links together. - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - RobotModel - Joint - Link - ToolModel - Configuration - - -Geometric description -===================== - -The robot itself as well as its links can be geometrically described -using the following classes. - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - Geometry - MeshDescriptor - Material - Texture - Color - - -Link -==== - -The link is described as a rigid body with inertial, visual and collision values. - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - Visual - Collision - Inertial - Mass - Inertia - - -Joint -===== - -The joint describes the kinematics and dynamics of the robot's joint. - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ParentLink - ChildLink - Calibration - Dynamics - Limit - Axis - Mimic - SafetyController - - -Resources -========= - -Model descriptions usually do not contain embedded geometry information but only -descriptions, filenames or URLs for externally hosted resources. -For that purpose, this package provides various loader classes that help automate -the processing of these resources. - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - AbstractMeshLoader - DefaultMeshLoader - GithubPackageMeshLoader - LocalPackageMeshLoader diff --git a/docs/reference/compas_blender.conversions.rst b/docs/reference/compas_blender.conversions.rst index 7b1bc8cf144..0ede5061e1d 100644 --- a/docs/reference/compas_blender.conversions.rst +++ b/docs/reference/compas_blender.conversions.rst @@ -8,13 +8,36 @@ compas_blender.conversions This package provides classes for converting between COMPAS geometry and Blender geometry. -Classes -======= +To Blender +========== .. autosummary:: :toctree: generated/ :nosignatures: - BlenderGeometry - BlenderCurve - BlenderMesh + circle_to_blender_curve + color_to_blender_material + cylinder_to_blender_mesh + line_to_blender_curve + mesh_to_blender + nurbscurve_to_blender_curve + nurbssurface_to_blender_surface + pointcloud_to_blender + polygon_to_blender_mesh + polyline_to_blender_curve + sphere_to_blender_mesh + transformation_to_blender + vertices_and_faces_to_blender_mesh + + +To COMPAS +========= + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + bmesh_to_compas + mesh_to_compas + monkey_to_compas + transformation_to_compas diff --git a/docs/reference/compas_blender.rst b/docs/reference/compas_blender.rst index 9c50acd14ff..cffd988ff35 100644 --- a/docs/reference/compas_blender.rst +++ b/docs/reference/compas_blender.rst @@ -19,7 +19,6 @@ Modules compas_blender.artists compas_blender.conversions compas_blender.geometry - compas_blender.ui compas_blender.utilities diff --git a/docs/reference/compas_blender.ui.rst b/docs/reference/compas_blender.ui.rst deleted file mode 100644 index ca4b5bc72ce..00000000000 --- a/docs/reference/compas_blender.ui.rst +++ /dev/null @@ -1,5 +0,0 @@ -******************************************************************************** -compas_blender.ui -******************************************************************************** - -.. currentmodule:: compas_blender.ui diff --git a/docs/reference/compas_ghpython.artists.rst b/docs/reference/compas_ghpython.artists.rst new file mode 100644 index 00000000000..2fe806b0b20 --- /dev/null +++ b/docs/reference/compas_ghpython.artists.rst @@ -0,0 +1,59 @@ +******************************************************************************** +compas_ghpython.artists +******************************************************************************** + +.. currentmodule:: compas_ghpython.artists + +.. rst-class:: lead + +Artists for visualizing (painting) COMPAS geometry, robots, and data structures in Grasshopper using the GH Python component. +The artists in this package provide plugins for the pluggable methods of the base artists in :mod:`compas.artists`. +Therefore, they can be used directly, from here, or through the base artists :class:`~compas.artists.Artist`. + + +Geometry Artists +================ + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + BoxArtist + CapsuleArtist + CircleArtist + ConeArtist + CurveArtist + CylinderArtist + FrameArtist + LineArtist + PointArtist + PolygonArtist + PolyhedronArtist + PolylineArtist + SphereArtist + SurfaceArtist + TorusArtist + VectorArtist + BrepArtist + + +Datastructure Artists +===================== + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + MeshArtist + NetworkArtist + VolMeshArtist + + +Base Classes +============ + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + GHArtist diff --git a/docs/reference/compas_rhino.artists.rst b/docs/reference/compas_rhino.artists.rst new file mode 100644 index 00000000000..4130b1c3f8e --- /dev/null +++ b/docs/reference/compas_rhino.artists.rst @@ -0,0 +1,55 @@ +""" +******************************************************************************** +compas_rhino.artists +******************************************************************************** + +.. currentmodule:: compas_rhino.artists + + +Geometry Artists +================ + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + BoxArtist + BrepArtist + CapsuleArtist + CircleArtist + ConeArtist + CurveArtist + CylinderArtist + FrameArtist + LineArtist + PlaneArtist + PointArtist + PolygonArtist + PolyhedronArtist + PolylineArtist + VectorArtist + SphereArtist + SurfaceArtist + TorusArtist + + +Datastructure Artists +===================== + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + MeshArtist + NetworkArtist + VolMeshArtist + + +Base Classes +============ + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + RhinoArtist diff --git a/docs/reference/compas_rhino.conversions.rst b/docs/reference/compas_rhino.conversions.rst new file mode 100644 index 00000000000..fad863588d7 --- /dev/null +++ b/docs/reference/compas_rhino.conversions.rst @@ -0,0 +1,98 @@ +******************************************************************************** +compas_rhino.conversions +******************************************************************************** + +.. currentmodule:: compas_rhino.conversions + +.. rst-class:: lead + +Conversions between Rhino geometry objects (:mod:`Rhino.Geometry`) and COMPAS geometry objects (:mod:`compas.geometry`). + +Exceptions +========== + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ConversionError + + +To Rhino +======== + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + arc_to_rhino + box_to_rhino + brep_to_rhino + capsule_to_rhino_brep + circle_to_rhino + circle_to_rhino_curve + cone_to_rhino + cone_to_rhino_brep + curve_to_rhino + cylinder_to_rhino + cylinder_to_rhino_brep + ellipse_to_rhino + ellipse_to_rhino_curve + frame_to_rhino + frame_to_rhino_plane + line_to_rhino + line_to_rhino_curve + mesh_to_rhino + plane_to_rhino + point_to_rhino + polygon_to_rhino + polyhedron_to_rhino + polyline_to_rhino + polyline_to_rhino_curve + sphere_to_rhino + surface_to_rhino + torus_to_rhino + torus_to_rhino_brep + transformation_to_rhino + transformation_matrix_to_rhino + vertices_and_faces_to_rhino + vector_to_rhino + + +To COMPAS +========= + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + arc_to_compas + box_to_compas + brep_to_compas_box + brep_to_compas_cone + brep_to_compas_cylinder + brep_to_compas_sphere + circle_to_compas + cone_to_compas + curve_to_compas_circle + curve_to_compas_ellipse + curve_to_compas_line + curve_to_compas_polyline + cylinder_to_compas + ellipse_to_compas + extrusion_to_compas_box + extrusion_to_compas_cylinder + extrusion_to_compas_torus + line_to_compas + mesh_to_compas + plane_to_compas + plane_to_compas_frame + point_to_compas + polygon_to_compas + polyline_to_compas + sphere_to_compas + surface_to_compas + surface_to_compas_data + surface_to_compas_mesh + surface_to_compas_quadmesh + vector_to_compas diff --git a/docs/reference/compas_rhino.forms.rst b/docs/reference/compas_rhino.forms.rst new file mode 100644 index 00000000000..34fef65c217 --- /dev/null +++ b/docs/reference/compas_rhino.forms.rst @@ -0,0 +1,29 @@ +******************************************************************************** +compas_rhino.forms +******************************************************************************** + +.. currentmodule:: compas_rhino.forms + + +Classes +======= + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + BrowserForm + ChartForm + ImageForm + SliderForm + TextForm + + +Base Classes +============ + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + BaseForm diff --git a/docs/reference/compas_rhino.geometry.rst b/docs/reference/compas_rhino.geometry.rst new file mode 100644 index 00000000000..e1fb5b57e5b --- /dev/null +++ b/docs/reference/compas_rhino.geometry.rst @@ -0,0 +1,63 @@ +******************************************************************************** +compas_rhino.geometry +******************************************************************************** + +.. currentmodule:: compas_rhino.geometry + +Classes +======= + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + RhinoCurve + RhinoNurbsCurve + RhinoNurbsSurface + + RhinoBrep + RhinoBrepVertex + RhinoBrepEdge + RhinoBrepFace + RhinoBrepLoop + RhinoBrepTrim + +Plugins +======= + +Booleans +-------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + booleans.boolean_difference_mesh_mesh + booleans.boolean_intersection_mesh_mesh + booleans.boolean_union_mesh_mesh + +Curves +------ + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + curves.new_curve + curves.new_nurbscurve + curves.new_nurbscurve_from_interpolation + curves.new_nurbscurve_from_parameters + curves.new_nurbscurve_from_points + curves.new_nurbscurve_from_step + +TriMesh +------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + trimesh.trimesh_gaussian_curvature + trimesh.trimesh_mean_curvature + trimesh.trimesh_principal_curvature + trimesh.trimesh_slice diff --git a/docs/userguide/tutorials.rst b/docs/userguide/tutorials.rst index 094f3199cf9..d80ae99c14f 100644 --- a/docs/userguide/tutorials.rst +++ b/docs/userguide/tutorials.rst @@ -8,7 +8,6 @@ Tutorials tutorials/data tutorials/geometry/index tutorials/datastructures/index - tutorials/robots tutorials/rpc tutorials/colors turorials/artists/index diff --git a/docs/userguide/tutorials/robots.rst b/docs/userguide/tutorials/robots.rst deleted file mode 100644 index 1a23ad19e70..00000000000 --- a/docs/userguide/tutorials/robots.rst +++ /dev/null @@ -1,450 +0,0 @@ -.. _robots: - -****** -Robots -****** - -.. highlight:: python - -.. rst-class:: lead - -COMPAS provides several fundamental structures and features that simplify working -with robots models, kinematic chains and coordinate frames. On top of this, -the `COMPAS FAB `_ extension -package provides additional functionality to connect these models with planning -and execution tools and libraries. - - -Coordinate frames -================= - -.. currentmodule:: compas.geometry - -One of the most basic concepts related to robotics that COMPAS provides are -coordinate frames, which are described using the :class:`compas.geometry.Frame` class. - -In any robotic setup, there exist multiple coordinate frames, and each one is defined -in relation to the next. Examples of typical coordinate frames are: - -* World coordinate frame (``WCF``) -* Robot coordinate frame (``RCF``) -* Tool0 coordinate frame (``T0CF``) -* Tool coordinate frame (``TCF``) -* Object coordinate frame (``OCF``) - -.. figure:: files/coordinate_frames.jpg - :figclass: figure - :class: figure-img img-fluid - - Coordinate frame convention of a robotic setup. - -A coordinate frame is defined as a point and two orthonormal base vectors -(``xaxis``, ``yaxis``). Both the point and the vectors can be defined -using simple lists of XYZ components or using classes. Frames are -right-handed coordinate systems. The following two examples are equivalent: - -:: - - >>> from compas.geometry import Frame, Point, Vector - -:: - - >>> frame = Frame([0, 0, 0], [1, 0, 0], [0, 1, 0]) - >>> frame.point - Point(0.000, 0.000, 0.000) - - >>> frame = Frame(Point(0, 0, 0), Vector(1, 0, 0), Vector(0, 1, 0)) - >>> frame.point - Point(0.000, 0.000, 0.000) - -There are shorthand constructors for the frames located at ``(0.0, 0.0, 0.0)``:: - - >>> f1 = Frame.worldXY() - >>> f2 = Frame.worldYZ() - >>> f3 = Frame.worldZX() - -And there are additional constructors to create coordinate frames -from alternative representations such as:: - - >>> f4 = Frame.from_axis_angle_vector([0, 0, 0], point=[0, 0, 0]) - >>> f5 = Frame.from_points([0, 0, 0], [1, 0, 0], [0, 1, 0]) - >>> f6 = Frame.from_quaternion([1, 0, 0, 0], point=[0, 0, 0]) - -The relationship between coordinate frames is expressed as a -:class:`compas.geometry.Transformation` between the two, for example: - -:: - - >>> from compas.geometry import Transformation - -:: - - >>> f1 = Frame([15, 15, 15], [0, 1, 0], [0, 0, 1]) - >>> f2 = Frame.worldXY() - >>> t = Transformation.from_frame_to_frame(f1, f2) - Transformation([[0.0, 1.0, 0.0, -15.0], [0.0, 0.0, 1.0, -15.0], [1.0, 0.0, 0.0, -15.0], [0.0, 0.0, 0.0, 1.0]]) - -A very common need is to describe the position and rotation of a object -(eg. point, vector, mesh, etc.) in relation to its local coordinate frame, -and then transform it to the world coordinate frame, and vice versa. -These operations are simplified with the methods ``to_local_coordinates`` -and ``to_world_coordinates`` of frames:: - - >>> f1 = Frame([130, 25, 80], [1, 0, 0], [0, 1, 0]) - >>> local_point = Point(10, 10, 10) - >>> f1.to_world_coordinates(local_point) - Point(140.000, 35.000, 90.000) - -Conversely, an object defined in world coordinate frame can be transformed to -a local coordinate frame using the ``to_local_coordinates`` method:: - - >>> p = Point(10, 10, 10) - >>> f1 = Frame([130, 25, 80], [1, 0, 0], [0, 1, 0]) - >>> f1.to_local_coordinates(p) - Point(-120.000, -15.000, -70.000) - - -Robot models -============ - -Robotic arms, like those typically used in digital fabrication, are fundamentally -kinematic chains of rigid bodies, i.e. **links**, connected by **joints** to -provide constrained motion. Kinematics is a subdomain of mechanics, and contrary -to dynamics, it concerns the laws of motion without considering forces. - -A robot model is a set of links and joints that form a tree structure where each -joint has a coordinate frame around which it rotates or translates, depending -on the joint type. - -COMPAS supports robot models defined in a standard robot description format -called ``URDF``, which originates in the ROS community. - -Links ------ - -Links are the rigid bodies in a robot model. They can have zero or more -geometries associated. Associated geometry can serve visual or collision purposes. -Collision geometry is generally a simplified version of visual geometry to speed up -the collision checking process. - -Joints ------- - -Joints are the connecting elements between links. There are four main types -of joints: - -* **Revolute**: A hinge joint that rotates along the axis and has a limited - range specified by the upper and lower limits. -* **Continuous**: A hinge joint that rotates along the axis and has no limits. -* **Prismatic**: A sliding joint that slides along the axis, and has a limited - range specified by the upper and lower limits. -* **Fixed**: Not really a joint because it cannot move, all degrees of freedom - are locked. - - -Visualizing Robots -================== - -Before jumping into how to build a robot model, let's first see how to visualize -one. This can be done with Blender, Rhino or Grasshopper using one of COMPAS's -artists. The basic procedure is the same in -any of the CAD software (aside from the import statement). Below you can find an example code for both Rhino and Blender. - - -.. tab-set:: - - .. tab-item:: Rhino - - Be sure to first install COMPAS for Rhino. While the following code is incomplete, - it can be used as a scaffolding for code to be run in a Python script editor within Rhino. - - .. code-block:: python - - import compas - from compas.artists import Artist - from compas.robots import RobotModel - - model = RobotModel('Robby') - - # Add some geometry to Robby here - - artist = Artist(model, layer='COMPAS::Example Robot') - artist.clear_layer() - artist.draw_visual() - - .. tab-item:: Blender - - .. code-block:: python - - import compas - from compas.artists import Artist - from compas.robots import RobotModel - import compas_blender - - compas_blender.clear() # Delete all objects in the Blender scene - - model = RobotModel('Robby') - - # Add some geometry to Robby here - - # Load the robot geometry into the blender scene - artist = Artist(model, collection='COMPAS::Example Robot') - - -See below for a complete example of how to programmatically create a Robotmodel. - - -Building robots models -====================== - -Robot models are represented by the :class:`compas.robots.RobotModel` class. -There are various ways to construct a robot model. The following snippet -shows how to construct one programmatically: - -:: - - >>> from compas.robots import Joint, Link, RobotModel - -:: - - >>> j1 = Joint('joint_1', 'revolute', parent='base', child='link_1') - >>> j2 = Joint('joint_2', 'revolute', parent='link_1', child='link_2') - >>> j3 = Joint('joint_3', 'revolute', parent='link_2', child='link_3') - >>> j4 = Joint('joint_4', 'revolute', parent='link_3', child='link_4') - >>> j5 = Joint('joint_5', 'revolute', parent='link_4', child='link_5') - >>> j6 = Joint('joint_6', 'revolute', parent='link_5', child='link_6') - >>> l0 = Link('base') - >>> l1 = Link('link_1') - >>> l2 = Link('link_2') - >>> l3 = Link('link_3') - >>> l4 = Link('link_4') - >>> l5 = Link('link_5') - >>> l6 = Link('link_6') - >>> links = [l0, l1, l2, l3, l4, l5, l6] - >>> joints = [j1, j2, j3, j4, j5, j6] - >>> robot = RobotModel('johnny-5', joints=joints, links=links) - >>> robot.get_configurable_joint_names() - ['joint_1', 'joint_2', 'joint_3', 'joint_4', 'joint_5', 'joint_6'] - -This approach can end up being very verbose, so the methods ``add_link`` -and ``add_joint`` of :class:`compas.robots.RobotModel` offer an alternative that -significantly reduces the amount of code required. Starting with an empty -robot model, adding a link in the shape of a box is as easy as: - -:: - - >>> from compas.geometry import Box, Frame - >>> from compas.robots import RobotModel - -:: - - >>> model = RobotModel(name='Boxy') - >>> box = Box(Frame.worldXY(), 1, 2, .5) - >>> model.add_link(name='box_link', visual_meshes=[box]) - -This code snippet can be modified and run in a Rhino python editor -to visualize Boxy. Throughout the rest of this tutorial, the code -snippets will include the lines for visualization in Rhino, but be -aware that the class :class:`compas.robots.RobotModel` can be used, -and is useful, outside of a CAD environment. - -.. code-block:: python - - from compas.artists import Artist - from compas.geometry import Box, Frame - from compas.robots import RobotModel - - model = RobotModel(name='Boxy') - box = Box(Frame.worldXY(), 1, 2, .5) - model.add_link(name='box_link', visual_meshes=[box]) - - artist = Artist(model, layer='COMPAS::Example Robot') - artist.clear_layer() - artist.draw_visual() - - -.. figure:: files/boxy_1.png - :figclass: figure - :class: figure-img img-fluid - -As can be seen, this has added a box of dimensions ``1`` x ``2`` x ``.5`` -whose geometric center and orientation coincides with the world XY frame. -The ``visual_meshes`` argument can be given a list containing COMPAS -primitives such as :class:`compas.geometry.Box` or the more complex -COMPAS meshes :class:`compas.geometry.Mesh`. For simplicity, -this tutorial uses only primitives. - -To reposition the box relative to the link's -origin, simply change the frame of the provided box. To move the box so that it sits -above the XY plane, the origin must be shifted in the z-direction by half the height -of the box. The box is also shifted slightly forward in the y-direction: - -.. code-block:: python - - from compas.artists import Artist - from compas.geometry import Box, Frame - from compas.robots import RobotModel - - model = RobotModel(name='Boxy') - box = Box(Frame([0, .5, .25], [1, 0, 0], [0, 1, 0]), 1, 2, .5) - model.add_link(name='box_link', visual_meshes=[box]) - - artist = Artist(model, layer='COMPAS::Example Robot') - artist.clear_layer() - artist.draw_visual() - -.. figure:: files/boxy_2.png - :figclass: figure - :class: figure-img img-fluid - -A link may have more than one geometric element associated to it. Now -there is a stack of two boxes: - -.. code-block:: python - - from compas.artists import Artist - from compas.geometry import Box, Frame - from compas.robots import RobotModel - - model = RobotModel(name='Boxy') - box_1 = Box(Frame([0, .5, .25], [1, 0, 0], [0, 1, 0]), 1, 2, .5) - box_2 = Box(Frame([0, 0, 4], [1, 0, 0], [0, 1, 0]), .5, 1, 7) - model.add_link(name='box_link', visual_meshes=[box_1, box_2]) - - artist = Artist(model, layer='COMPAS::Example Robot') - artist.clear_layer() - artist.draw_visual() - -.. figure:: files/boxy_3.png - :figclass: figure - :class: figure-img img-fluid - -Remember that the frame of the box is the geometric center of the box relative -to the link's origin (which, in this case, happens to be the world XY frame). -So to stack the boxes, the center of ``box_2`` must be placed at a height of -`` + 1/2 * = .5 + .5 * 7 = 4``. - -One link does not an interesting robot make. The following code snippet adds -a cylindrical second link as well as a joint connecting the two. - -.. code-block:: python - - from compas.artists import Artist - from compas.geometry import Box, Circle, Cylinder, Frame, Plane, Vector - from compas.robots import Joint, RobotModel - - model = RobotModel(name='Jointy') - box_1 = Box(Frame([2, .5, .25], [1, 0, 0], [0, 1, 0]), 1, 2, .5) - box_2 = Box(Frame([2, 0, 4], [1, 0, 0], [0, 1, 0]), .5, 1, 7) - box_link = model.add_link(name='box_link', visual_meshes=[box_1, box_2]) - - cylinder = Cylinder(Circle(Plane([0, 0, 0], [0, 0, 1]), .5), 8) - cylinder_link = model.add_link(name='cylinder_link', visual_meshes=[cylinder]) - - origin = Frame([0, 0, 7], [1, 0, 0], [0, 1, 0]) - axis = Vector(1, 0, 0) - model.add_joint( - name='box_cylinder_joint', - type=Joint.CONTINUOUS, - parent_link=box_link, - child_link=cylinder_link, - origin=origin, - axis=axis, - ) - - artist = Artist(model, layer='COMPAS::Example Robot') - artist.clear_layer() - artist.draw_visual() - - -.. figure:: files/jointy.png - :figclass: figure - :class: figure-img img-fluid - -There's a lot going on in this snippet, so let's break it down. First, the link -containing the stacked boxes is added, but the geometry has been shifted to the -side ``2`` units in the x-direction. Second, a cylindrical link is added. -The cylinder has height ``8`` and radius ``.5`` and is vertically oriented. Finally, a -joint is added. This is the most involved operation so far, so let's explore -this in detail. - -Several parameters must be defined for the joint to be sensible. -The required parameters for a minimally defined joint are the ``name``, ``type``, -``parent_link`` and ``child_link``. The location and orientation of the joint is defined -by the ``origin``, which specifies a frame relative to the origin of the ``parent_link``. -In this case, the location of the joint is located ``7`` units above (ie, -in the z-direction) the origin of the ``box_link``, making it ``.5`` units below the top -of the second box. The origin of the joint also specifies the origin of its -``child_link``. Here, this means that the center of the cylinder will coincide with -the location of the joint. Since the geometries of the boxes have been shifted, -the boxes and the cylinder do not overlap. If the joint origin is not specified, -it will default to coincide with the origin of the ``parent_link``. Since a continuous -joint was added, it makes sense that the axis of rotation would also need to be given. -Here, the joint will rotate about the joint origin's x-axis. - -Adding a bit more geometry and a few links with fixed joints and we arrive at a rough -model of the classic drinking bird toy (full code -:download:`here `). - -.. figure:: files/drinking_bird.png - :figclass: figure - :class: figure-img img-fluid - -While it can be useful to programmatically create a robot, more often than not, robot -models are loaded from URDF files. To load a URDF into a robot model, use the -``from_urdf_file`` method:: - - >>> model = RobotModel.from_urdf_file('ur5.urdf') - >>> print(model) - -Since a large number of robot models defined in URDF are available on Github, -there are specialized loaders that allow loading an entire model including -its linked geometry directly from a Github repository: - -:: - - >>> import compas - >>> from compas.robots import GithubPackageMeshLoader - >>> from compas.robots import RobotModel - -:: - - >>> # Set high precision to import meshes defined in meters - >>> compas.PRECISION = '12f' - >>> github = GithubPackageMeshLoader('ros-industrial/abb', 'abb_irb6600_support', 'kinetic-devel') - >>> model = RobotModel.from_urdf_file(github.load_urdf('irb6640.urdf')) - >>> model.load_geometry(github) - >>> print(model) - Robot name=abb_irb6640, Links=11, Joints=10 (6 configurable) - -Another common scenario is to load robot models from a running ROS system. -`ROS (Robot Operating System) `_ is a very complex and -mature tool, and its setup is beyond the scope of this tutorial, but -an overview of some of the installation options is available -`here `_. -Once ROS is configured on your system, the most convenient way to load the -robot model is to use **COMPAS FAB** and its ROS integration. -The following snippet shows how to load the robot model currently active in ROS: - -:: - - >>> from compas_fab.backends import RosClient - -:: - - >>> with RosClient() as ros: - ... robot = ros.load_robot(load_geometry=True) - ... print(robot.model) - - -FK, IK & Path Planning -====================== - -Robot models are the base for a large number of additional features that are -provided via extension packages. In particular, features such as -forward and inverse kinematic solvers and path planning are built on top of -these robot models, but are integrated into -`COMPAS FAB `_. - -For further details about these features, check the detailed examples in -COMPAS FAB documentation. diff --git a/src/compas/artists/__init__.py b/src/compas/artists/__init__.py index 48d33bb17f3..85cd6a8141b 100644 --- a/src/compas/artists/__init__.py +++ b/src/compas/artists/__init__.py @@ -8,15 +8,12 @@ from .meshartist import MeshArtist from .networkartist import NetworkArtist from .geometryartist import GeometryArtist -from .robotmodelartist import RobotModelArtist from .volmeshartist import VolMeshArtist from .artist import clear # noqa: F401 from .artist import redraw # noqa: F401 from .artist import register_artists # noqa: F401 -BaseRobotModelArtist = RobotModelArtist - __all__ = [ "DataArtistNotRegistered", @@ -25,6 +22,5 @@ "MeshArtist", "NetworkArtist", "GeometryArtist", - "RobotModelArtist", "VolMeshArtist", ] diff --git a/src/compas/artists/robotmodelartist.py b/src/compas/artists/robotmodelartist.py deleted file mode 100644 index 4b9ea5afed4..00000000000 --- a/src/compas/artists/robotmodelartist.py +++ /dev/null @@ -1,523 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import itertools - -from compas.geometry import Frame -from compas.geometry import Scale -from compas.geometry import Transformation -from compas.robots import Geometry -from compas.robots.model.link import LinkItem - -from .artist import Artist - - -class AbstractRobotModelArtist(object): - def transform(self, geometry, transformation): - """Transforms a CAD-specific geometry using a Transformation. - - Parameters - ---------- - geometry : object - A CAD-specific (i.e. native) geometry object as returned by :meth:`create_geometry`. - transformation : :class:`~compas.geometry.Transformation` - Transformation to update the geometry object. - - """ - raise NotImplementedError - - def create_geometry(self, geometry, name=None, color=None): - """Draw geometry in the respective CAD environment. - - Parameters - ---------- - geometry : :class:`~compas.datastructures.Mesh` - Instance of a mesh data structure - name : str, optional - The name of the mesh to draw. - - Returns - ------- - object - CAD-specific geometry - - Notes - ----- - This is an abstract method that needs to be implemented by derived classes. - - """ - raise NotImplementedError - - -class RobotModelArtist(AbstractRobotModelArtist, Artist): - """Provides common functionality to most robot model artist implementations. - - In COMPAS, "artists" are classes that assist with the visualization of - datastructures and models, in a way that maintains the data separated from the - specific CAD interfaces, while providing a way to leverage native performance - of the CAD environment. - - There are two methods that implementers of this base class should provide, one - is concerned with the actual creation of geometry in the native format of the - CAD environment (:meth:`create_geometry`) and the other is one to apply a transformation - to geometry (:meth:`transform`). - - Attributes - ---------- - model : :class:`~compas.robots.RobotModel` - Instance of a robot model. - - """ - - def __init__(self, model, **kwargs): - super(RobotModelArtist, self).__init__(item=model, **kwargs) - self.model = model - self.create() - self.scale_factor = 1.0 - self.attached_tool_models = {} - self.attached_items = {} - - @property - def attached_tool_model(self): - """ - For backwards compatibility. Returns the tool attached to the first end effector link or, - if not available, the first tool from the dictionary. - Returns None if no tool are attached. - - Returns - ------- - :class: `~compas.robots.model.ToolModel` - - """ - tool_model = None - if self.attached_tool_models: - link_name = self.model.get_end_effector_link() - tool_model = self.attached_tool_models.get(link_name) or list(self.attached_tool_models.values())[0] - return tool_model - - def attach_tool_model(self, tool_model): - """Attach a tool to the robot artist for visualization. - - Parameters - ---------- - tool_model : :class:`~compas.robots.ToolModel` - The tool that should be attached to the robot's flange. - - """ - self.create(tool_model.root, "attached_tool") - - if not tool_model.link_name: - link = self.model.get_end_effector_link() - tool_model.link_name = link.name - else: - link = self.model.get_link_by_name(tool_model.link_name) - - # don't attach twice on the same link - self.attached_tool_models[tool_model.link_name] = tool_model - - ee_frame = link.parent_joint.origin.copy() - initial_transformation = Transformation.from_frame_to_frame(Frame.worldXY(), ee_frame) - - sample_geometry = link.collision[0] if link.collision else link.visual[0] if link.visual else None - - if hasattr(sample_geometry, "current_transformation"): - relative_transformation = sample_geometry.current_transformation - else: - relative_transformation = Transformation() - - transformation = relative_transformation.concatenated(initial_transformation) - - self.update_tool(tool=tool_model, transformation=transformation) - - tool_model.parent_joint_name = link.parent_joint.name - - def detach_tool_model(self, tool_model=None): - """ - Detach tool_model from this robot model. - If tool_model is None, all attached tools are detached. - - Parameters - ---------- - tool_model : :class:`~compas.robots.ToolModel` - The tool that should be detached from the robot's flange. - If None, all attached tools tools are removed. - """ - if tool_model: - del self.attached_tool_models[tool_model.link_name] - else: - self.attached_tool_models.clear() - - def attach_mesh(self, mesh, name, link=None, frame=None): - """Rigidly attaches a compas mesh to a given link for visualization. - - Parameters - ---------- - mesh : :class:`~compas.datastructures.Mesh` - The mesh to attach to the robot model. - name : str - The identifier of the mesh. - link : :class:`~compas.robots.Link` - The link within the robot model or tool model to attach the mesh to. Optional. - Defaults to the model's end effector link. - frame : :class:`~compas.geometry.Frame` - The frame of the mesh. Defaults to :meth:`compas.geometry.Frame.worldXY`. - - Returns - ------- - None - - """ - if not link: - link = self.model.get_end_effector_link() - transformation = Transformation.from_frame(frame) if frame else Transformation() - - sample_geometry = None - - while sample_geometry is None: - sample_geometry = link.collision[0] if link.collision else link.visual[0] if link.visual else None - link = self.model.get_link_by_name(link.parent_joint.parent.link) - - native_mesh = self.create_geometry(mesh) - init_transformation = transformation * sample_geometry.init_transformation - self.transform(native_mesh, sample_geometry.current_transformation * init_transformation) - - item = LinkItem() - item.native_geometry = [native_mesh] - item.init_transformation = init_transformation - item.current_transformation = sample_geometry.current_transformation - - self.attached_items.setdefault(link.name, {})[name] = item - - def detach_mesh(self, name): - """Removes attached collision meshes with a given name. - - Parameters - ---------- - name : str - The identifier of the mesh. - - Returns - ------- - None - - """ - for _, items in self.attached_items: - items.pop(name, None) - - def create(self, link=None, context=None): - """Recursive function that triggers the drawing of the robot model's geometry. - - This method delegates the geometry drawing to the :meth:`create_geometry` - method. It transforms the geometry based on the saved initial - transformation from the robot model. - - Parameters - ---------- - link : :class:`~compas.robots.Link`, optional - Link instance to create. Defaults to the robot model's root. - context : str, optional - Subdomain identifier to insert in the mesh names. - - Returns - ------- - None - - """ - if link is None: - link = self.model.root - - for item in itertools.chain(link.visual, link.collision): - meshes = Geometry._get_item_meshes(item) - - if meshes: - is_visual = hasattr(item, "get_color") - color = item.get_color() if is_visual else None - - native_geometry = [] - for i, mesh in enumerate(meshes): - mesh_type = "visual" if is_visual else "collision" - if not context: - mesh_name_components = [ - self.model.name, - mesh_type, - link.name, - str(i), - ] - else: - mesh_name_components = [ - self.model.name, - mesh_type, - context, - link.name, - str(i), - ] - mesh_name = ".".join(mesh_name_components) - native_mesh = self.create_geometry(mesh, name=mesh_name, color=color) - - self.transform(native_mesh, item.init_transformation) - - native_geometry.append(native_mesh) - - item.native_geometry = native_geometry - item.current_transformation = Transformation() - - for child_joint in link.joints: - self.create(child_joint.child_link) - - def meshes(self, link=None, visual=True, collision=False, attached_meshes=True): - """Returns all compas meshes of the model. - - Parameters - ---------- - link : :class:`~compas.robots.Link`, optional - Base link instance. - Defaults to the robot model's root. - visual : bool, optional - Whether to include the robot's visual meshes. - collision : bool, optional - Whether to include the robot's collision meshes. - attached_meshes : bool, optional - Whether to include the robot's attached meshes. - - Returns - ------- - list[:class:`~compas.datastructures.Mesh`] - - """ - if link is None: - link = self.model.root - - meshes = [] - items = [] - if visual: - items += link.visual - if collision: - items += link.collision - if attached_meshes: - items += list(self.attached_items.get(link.name, {}).values()) - for item in items: - new_meshes = Geometry._get_item_meshes(item) - for mesh in new_meshes: - mesh.transform(item.current_transformation) - meshes += new_meshes - for child_joint in link.joints: - meshes += self.meshes(child_joint.child_link, visual, collision, attached_meshes) - return meshes - - def scale(self, factor): - """Scales the robot model's geometry by factor (absolute). - - Parameters - ---------- - factor : float - The factor to scale the robot with. - - Returns - ------- - None - - """ - self.model.scale(factor) - - relative_factor = factor / self.scale_factor - transformation = Scale.from_factors([relative_factor] * 3) - self.scale_link(self.model.root, transformation) - self.scale_factor = factor - - def scale_link(self, link, transformation): - """Recursive function to apply the scale transformation on each link. - - Parameters - ---------- - link : :class:`~compas.robots.Link` - A link. - transformation : :class:`~compas.geometry.Transformation` - A transformation to apply to th link's geometry. - - Returns - ------- - None - - """ - self._scale_link_helper(link, transformation) - - for tool in self.attached_tool_models.values(): - self._scale_link_helper(tool.root, transformation) - - def _scale_link_helper(self, link, transformation): - for item in itertools.chain(link.visual, link.collision): - # Some links have only collision geometry, not visual. These meshes - # have not been loaded. - if item.native_geometry: - for geometry in item.native_geometry: - self.transform(geometry, transformation) - - for child_joint in link.joints: - self._scale_link_helper(child_joint.child_link, transformation) - - def _apply_transformation_on_transformed_link(self, item, transformation): - """Applies a transformation on a link that is already transformed. - - Calculates the relative transformation and applies it to the link - geometry. This is to prevent the recreation of large meshes. - - Parameters - ---------- - item: :class:`~compas.robots.Visual` | :class:`~compas.robots.Collision` - The visual or collidable object of a link. - transformation: :class:`~compas.geometry.Transformation` - The (absolute) transformation to apply onto the link's geometry. - - Returns - ------- - None - - """ - if getattr(item, "current_transformation"): - relative_transformation = transformation * item.current_transformation.inverse() - else: - relative_transformation = transformation - for native_geometry in item.native_geometry or []: - self.transform(native_geometry, relative_transformation) - item.current_transformation = transformation - - def update(self, joint_state, visual=True, collision=True): - """Triggers the update of the robot geometry. - - Parameters - ---------- - joint_state : :class:`~compas.robots.Configuration` | dict[str, float] - A dictionary with joint names as keys and joint positions as values. - visual : bool, optional - If True, the visual geometry will also be updated. - collision : bool, optional - If True, the collision geometry will also be updated. - - Returns - ------- - None - - """ - _ = self._update(self.model, joint_state, visual, collision) - for tool in self.attached_tool_models.values(): - frame = self.model.forward_kinematics(joint_state, link_name=tool.link_name) - self.update_tool( - tool=tool, - visual=visual, - collision=collision, - transformation=Transformation.from_frame_to_frame(Frame.worldXY(), frame), - ) - - def _update( - self, - model, - joint_state, - visual=True, - collision=True, - parent_transformation=None, - ): - transformations = model.compute_transformations(joint_state, parent_transformation=parent_transformation) - for j in model.iter_joints(): - self._transform_link_geometry(j.child_link, transformations[j.name], collision) - return transformations - - def _transform_link_geometry(self, link, transformation, collision=True): - for item in link.visual: - self._apply_transformation_on_transformed_link(item, transformation) - if collision: - for item in link.collision: - # some links have only collision geometry, not visual. These meshes have not been loaded. - if item.native_geometry: - self._apply_transformation_on_transformed_link(item, transformation) - for item in self.attached_items.get(link.name, {}).values(): - self._apply_transformation_on_transformed_link(item, transformation) - - def update_tool(self, tool, joint_state=None, visual=True, collision=True, transformation=None): - """Triggers the update of the robot geometry of the tool. - - Parameters - ---------- - joint_state : :class:`~compas.robots.Configuration` | dict[str, float], optional - A dictionary with joint names as keys and joint positions as values. - Defaults to an empty dictionary. - transformation : :class:`~compas.geometry.Transformation`, optional - The (absolute) transformation to apply to the entire tool's geometry. - If None is given, no additional transformation will be applied. - Defaults to None. - visual : bool, optional - If True, the visual geometry will also be updated. - collision : bool, optional - If True, the collision geometry will also be updated. - - Returns - ------- - None - - """ - joint_state = joint_state or {} - - if transformation is None: - transformation = tool.current_transformation - self._transform_link_geometry(tool.root, transformation, collision) - self._update(tool, joint_state, visual, collision, transformation) - tool.current_transformation = transformation - - def draw_visual(self): - """Draws all visual geometry of the robot model. - - Returns - ------- - list[object] - A list of context-specific mesh representations. - - """ - visual = [] - for native_geometry in self._iter_geometry(self.model, "visual"): - visual.append(native_geometry) - for tool in self.attached_tool_models.values(): - for native_geometry in self._iter_geometry(tool, "visual"): - visual.append(native_geometry) - return visual - - def draw_collision(self): - """Draws all collision geometry of the robot model. - - Returns - ------- - list[object] - A list of context-specific mesh representations. - - """ - visual = [] - for native_geometry in self._iter_geometry(self.model, "collision"): - visual.append(native_geometry) - for tool in self.attached_tool_models.values(): - for native_geometry in self._iter_geometry(tool, "collision"): - visual.append(native_geometry) - - return visual - - def draw_attached_meshes(self): - """Draws all meshes attached to the robot model. - - Returns - ------- - list[object] - A list of context-specific mesh representations. - - """ - visual = [] - for items in self.attached_items.values(): - for item in items.values(): - for native_mesh in item.native_geometry: - visual.append(native_mesh) - return visual - - @staticmethod - def _iter_geometry(model, geometry_type): - for link in model.iter_links(): - for item in getattr(link, geometry_type): - if item.native_geometry: - for native_geometry in item.native_geometry: - yield native_geometry diff --git a/src/compas/robots/__init__.py b/src/compas/robots/__init__.py deleted file mode 100644 index cd0c807387f..00000000000 --- a/src/compas/robots/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -from __future__ import absolute_import - -from .configuration import Configuration - -from .resources.basic import AbstractMeshLoader, DefaultMeshLoader, LocalPackageMeshLoader -from .resources.github import GithubPackageMeshLoader - -# from .model.base import FrameProxy, ProxyObject -from .model.geometry import Box, Capsule, Color, Cylinder, Geometry, Material, MeshDescriptor, Origin, Sphere, Texture -from .model.joint import Axis, Calibration, ChildLink, Dynamics, Joint, Limit, Mimic, ParentLink, SafetyController -from .model.link import Collision, Inertia, Inertial, Link, Mass, Visual -from .model.robot import RobotModel -from .model.tool import ToolModel - -__all__ = [ - "Geometry", - "MeshDescriptor", - "Color", - "Texture", - "Material", - "Joint", - "ParentLink", - "ChildLink", - "Calibration", - "Dynamics", - "Limit", - "Axis", - "Mimic", - "SafetyController", - "Link", - "Inertial", - "Visual", - "Collision", - "Mass", - "Inertia", - "RobotModel", - "ToolModel", - "AbstractMeshLoader", - "DefaultMeshLoader", - "LocalPackageMeshLoader", - "GithubPackageMeshLoader", - "Configuration", - # Deprecated aliases - "Origin", - "Box", - "Capsule", - "Cylinder", - "Sphere", -] diff --git a/src/compas/robots/configuration.py b/src/compas/robots/configuration.py deleted file mode 100644 index b052b589e6a..00000000000 --- a/src/compas/robots/configuration.py +++ /dev/null @@ -1,617 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from math import pi -from copy import deepcopy - -from compas.data import Data - -# These joint types known to configuration are a match -# to the ones defined in `Joint` class, but we redefine here -# to avoid a circular dependency -_JOINT_REVOLUTE = 0 -_JOINT_CONTINUOUS = 1 -_JOINT_PRISMATIC = 2 -_JOINT_PLANAR = 5 - - -def joint_names_validator(joint_names, key=None, value=None): - new_joint_names = list(joint_names) - if key is not None and value is not None: - new_joint_names.__setitem__(key, value) - if len(new_joint_names) != len(set(new_joint_names)): - raise ValueError("joint_names cannot have repeated values.") - - -class FixedLengthList(list): - """Restriction of the standard Python list to prevent changes in length - while maintaining the ability to change values. An optional ``validator`` - can be passed to the constructor which would be used to validate changes - to the values. - - Parameters - ---------- - validator : :obj:`function` - A boolean function with the same signature as __setiem__, ie - validator(fll: FixedLengthList, key: slice, value: Any) -> bool - """ - - def __init__(self, *args, **kwargs): - super(FixedLengthList, self).__init__(*args) - self.validator = kwargs.get("validator") - if self.validator: - self.validator(self) - - def __setitem__(self, key, value): - # included to obstruct the all too common `l[1:1] = range(100)`-type usage - value_length = len(value) if hasattr(value, "__len__") else 1 - slice_length = len(range(*key.indices(self.__len__()))) if isinstance(key, slice) else 1 - if slice_length != value_length: - raise TypeError("Cannot change length of FixedLengthList") - if self.validator: - self.validator(self, key, value) - super(FixedLengthList, self).__setitem__(key, value) - - def __setslice__(self, i, j, sequence): - # for ironpython - slice_end = min(j, self.__len__()) - slice_length = slice_end - i - value_length = len(sequence) - if slice_length != value_length: - raise TypeError("Cannot change length of FixedLengthList") - if self.validator: - self.validator(self, slice(i, j), sequence) - super(FixedLengthList, self).__setslice__(i, j, sequence) - - def __copy__(self): - return FixedLengthList(self) - - def __deepcopy__(self, memodict={}): - return FixedLengthList([deepcopy(value, memodict) for value in self]) - - def append(self, item): - raise TypeError("Cannot change length of FixedLengthList") - - def extend(self, other): - raise TypeError("Cannot change length of FixedLengthList") - - def insert(self, i, item): - raise TypeError("Cannot change length of FixedLengthList") - - def remove(self, item): - raise TypeError("Cannot change length of FixedLengthList") - - def pop(self, i=-1): - raise TypeError("Cannot change length of FixedLengthList") - - def clear(self): - raise TypeError("Cannot change length of FixedLengthList") - - -class Configuration(Data): - """Represents the configuration of a robot based on the state of its joints. - If the names of joints are also provided, the configuration behaves as a - dictionary of joint name-value pairs. - - This concept is also refered to as \"Joint State\". - - Parameters - ---------- - joint_values : list of float - Joint values expressed in radians or meters, depending on the respective - type. - joint_types : list of :attr:`compas.robots.Joint.SUPPORTED_TYPES` - Joint types, e.g. a list of :attr:`compas.robots.Joint.REVOLUTE` for - revolute joints. - joint_names : list[str], optional - List of joint names. - - Attributes - ---------- - joint_values : list of float - Joint values expressed in radians or meters, depending on the respective - type. - joint_types : list of :attr:`compas.robots.Joint.SUPPORTED_TYPES` - Joint types, e.g. a list of :attr:`compas.robots.Joint.REVOLUTE` for - revolute joints. - joint_names : list[str] - List of joint names. - data : dict - The data representing the configuration. - prismatic_values : list of float - Prismatic joint values in meters. - revolute_values : list of float - Revolute joint values in radians. - - Examples - -------- - >>> config = Configuration.from_revolute_values([math.pi/2, 0., 0.]) - >>> config.joint_values - [1.5707963267948966, 0.0, 0.0] - - >>> config = Configuration.from_prismatic_and_revolute_values([8.312], [math.pi/2, 0., 0., 0., 2*math.pi, 0.8]) - >>> str(config) - 'Configuration((8.312, 1.571, 0.000, 0.000, 0.000, 6.283, 0.800), (2, 0, 0, 0, 0, 0, 0))' - - >>> from compas.robots import Joint - >>> config = Configuration([math.pi/2, 3., 0.1], [Joint.REVOLUTE, Joint.PRISMATIC, Joint.PLANAR]) - >>> str(config) - 'Configuration((1.571, 3.000, 0.100), (0, 2, 5))' - - """ - - def __init__(self, joint_values=None, joint_types=None, joint_names=None, **kwargs): - super(Configuration, self).__init__(**kwargs) - - joint_values = FixedLengthList(joint_values or []) - joint_types = FixedLengthList(joint_types or []) - joint_names = FixedLengthList(joint_names or [], validator=joint_names_validator) - - if len(joint_values) != len(joint_types): - raise ValueError( - "{} joint_values must have {} joint_types, but {} given.".format( - len(joint_values), len(joint_values), len(joint_types) - ) - ) - - if joint_names and len(joint_names) != len(joint_values): - raise ValueError( - "{} joint_values must have either 0 or {} names, but {} given".format( - len(joint_values), len(joint_values), len(joint_names) - ) - ) - - self._precision = "3f" - self._joint_values = joint_values - self._joint_types = joint_types - self._joint_names = joint_names - - @property - def joint_values(self): - """Joint values expressed in radians or meters, depending on the respective - type. - - Returns - ------- - list of float - """ - return self._joint_values - - @joint_values.setter - def joint_values(self, values): - if len(self._joint_values) != len(values): - raise Exception( - "joint_values must have length {}, object of length {} given".format( - len(self._joint_values), len(values) - ) - ) - self._joint_values = FixedLengthList(values) - - @property - def joint_types(self): - """Joint joint_types, e.g. a list of :attr:`compas.robots.Joint.REVOLUTE` for - revolute joints. - - Returns - ------- - list of :attr:`compas.robots.Joint.SUPPORTED_TYPES` - """ - return self._joint_types - - @joint_types.setter - def joint_types(self, joint_types): - if len(self._joint_types) != len(joint_types): - raise Exception( - "joint_types must have length {}, object of length {} given".format( - len(self._joint_types), len(joint_types) - ) - ) - self._joint_types = FixedLengthList(joint_types) - - @property - def joint_names(self): - """ "List of joint names. - - Returns - ------- - list[str] - """ - return self._joint_names - - @joint_names.setter - def joint_names(self, names): - if names and len(self._joint_values) != len(names): - raise ValueError( - "joint_types must have length {}, object of length {} given".format(len(self._joint_values), len(names)) - ) - self._joint_names = FixedLengthList(names, validator=joint_names_validator) - - def __str__(self): - """Return a human-readable string representation of the instance.""" - v_str = ("(" + ", ".join(["%." + self._precision] * len(self.joint_values)) + ")") % tuple(self.joint_values) - if len(self.joint_names): - return "Configuration({}, {}, {})".format(v_str, tuple(self.joint_types), tuple(self.joint_names)) - else: - return "Configuration({}, {})".format(v_str, tuple(self.joint_types)) - - def __repr__(self): - """Printable representation of :class:`Configuration`.""" - return self.__str__() - - def __getitem__(self, item): - for name, value in zip(self.joint_names, self.joint_values): - if name == item: - return value - raise KeyError(item) - - def __setitem__(self, item, value): - i = self.joint_names.index(item) - if i < 0: - raise KeyError(item) - self.joint_values[i] = value - - def __bool__(self): - # If __bool__ is not overwritten, then calls to bool default to the value given by __len__. - # Since __len__ should reflect the number of items given by iterating over the object, - # __len__ returns len(self.joint_names). However, there are valid Configurations that lack - # joint names, and should not be falsy. - return True - - def __nonzero__(self): - # ironpython's version of __bool__ - return self.__bool__() - - def __len__(self): - return len(self.joint_names) - - def __iter__(self): - return iter(self.joint_names) - - def items(self): - return zip(self.joint_names, self.joint_values) - - def keys(self): - return iter(self.joint_names) - - def values(self): - return iter(self.joint_values) - - def get(self, key, default=None): - try: - value = self[key] - except KeyError: - value = default - return value - - @classmethod - def from_revolute_values(cls, values, joint_names=None): - """Construct a configuration from revolute joint values in radians. - - Parameters - ---------- - values : list of float - Joint values expressed in radians. - joint_names : list[str], optional - List of joint names. - - Returns - ------- - :class:`Configuration` - An instance of :class:`Configuration` instance. - """ - values = list(values) - joint_names = list(joint_names or []) - return cls.from_data( - { - "joint_values": values, - "joint_types": [_JOINT_REVOLUTE] * len(values), - "joint_names": joint_names, - } - ) - - @classmethod - def from_prismatic_and_revolute_values(cls, prismatic_values, revolute_values, joint_names=None): - """Construct a configuration from prismatic and revolute joint values. - - Parameters - ---------- - prismatic_values : list of float - Positions on the external axis system in meters. - revolute_values : list of float - Joint values expressed in radians. - joint_names : list[str], optional - List of joint names. - - Returns - ------- - :class:`Configuration` - An instance of :class:`Configuration`. - """ - # Force iterables into lists - prismatic_values = list(prismatic_values) - revolute_values = list(revolute_values) - joint_names = list(joint_names or []) - values = prismatic_values + revolute_values - joint_types = [_JOINT_PRISMATIC] * len(prismatic_values) + [_JOINT_REVOLUTE] * len(revolute_values) - return cls.from_data( - { - "joint_values": values, - "joint_types": joint_types, - "joint_names": joint_names, - } - ) - - @property - def data(self): - """dict : The data representing the configuration. - - By assigning a data dictionary to this property, the current data of - the configuration will be replaced by the data in the dict. The - data getter and setter should always be used in combination with each - other. - """ - return { - "joint_values": self.joint_values, - "joint_types": self.joint_types, - "joint_names": self.joint_names, - } - - @data.setter - def data(self, data): - self._joint_values = FixedLengthList(data.get("joint_values") or data.get("values") or []) - self._joint_types = FixedLengthList(data.get("joint_types") or data.get("types") or []) - self._joint_names = FixedLengthList(data.get("joint_names") or []) - - @property - def prismatic_values(self): - """list of float : Prismatic joint values in meters. - - E.g. positions on the external axis system. - """ - return [v for i, v in enumerate(self.joint_values) if self.joint_types[i] == _JOINT_PRISMATIC] - - @property - def revolute_values(self): - """list of float : Revolute joint values in radians.""" - return [v for i, v in enumerate(self.joint_values) if self.joint_types[i] == _JOINT_REVOLUTE] - - def scale(self, scale_factor): - """Scales the joint positions of the current configuration. - - Only scalable joints are scaled, i.e. planar and prismatic joints. - - Parameters - ---------- - scale_factor : float - Scale factor. - - Returns - ------- - None - """ - values_scaled = [] - - for value, joint_type in zip(self.joint_values, self.joint_types): - if joint_type in (_JOINT_PLANAR, _JOINT_PRISMATIC): - value *= scale_factor - values_scaled.append(value) - - self._joint_values = FixedLengthList(values_scaled) - - def scaled(self, scale_factor): - """Return a scaled copy of this configuration. - - Only scalable joints are scaled, i.e. planar and prismatic joints. - - Parameters - ---------- - scale_factor : float - Scale factor - - Returns - ------- - None - """ - config = self.copy() - config.scale(scale_factor) - return config - - def iter_differences(self, other): - """Generator over the differences to another `Configuration`'s joint_values. - - If the joint type is revolute or continuous, the smaller difference - (+/- 2*:math:`\\pi`) is calculated. - - Parameters - ---------- - other : :class:`Configuration` - The configuration to compare to. - - Yields - ------ - float - The next difference to the `Configuration`'s joint_values. - - Raises - ------ - ValueError - If the configurations are not comparable. - - Examples - -------- - >>> from compas.geometry import allclose - >>> c1 = Configuration.from_revolute_values([1, 0, 3]) - >>> c2 = Configuration.from_revolute_values([1, 2 * pi, 4]) - >>> allclose(c1.iter_differences(c2), [0.0, 0.0, -1.0]) - True - >>> c1 = Configuration.from_revolute_values([1, 0, 3]) - >>> c2 = Configuration.from_revolute_values([1, -2 * pi - 0.2, 4]) - >>> allclose(c1.iter_differences(c2), [0.0, 0.2, -1.0]) - True - """ - if self.joint_names and other.joint_names: - if set(self.joint_names) != set(other.joint_names): - raise ValueError("Configurations have different joint names.") - other_value_by_name = dict(zip(other.joint_names, other.joint_values)) - sorted_other_values = [other_value_by_name[name] for name in self.joint_names] - value_pairs = zip(self.joint_values, sorted_other_values) - else: - if len(self.joint_values) != len(other.joint_values): - raise ValueError("Can't compare configurations with different lengths of joint_values.") - value_pairs = zip(self.joint_values, other.joint_values) - - for i, (v1, v2) in enumerate(value_pairs): - diff = v1 - v2 - if self.joint_types[i] in [_JOINT_REVOLUTE, _JOINT_CONTINUOUS]: - d1 = diff % (2 * pi) - d1 = d1 if diff >= 0 else d1 - 2 * pi - d2 = d1 - 2 * pi if diff >= 0 else d1 + 2 * pi - diff = d1 if abs(d1) < abs(d2) else d2 - yield diff - - def max_difference(self, other): - """Returns the maximum difference to another `Configuration`'s joint values. - - Parameters - ---------- - other : :class:`Configuration` - The configuration to compare to. - - Returns - ------- - float - The maximum absolute difference. - - Examples - -------- - >>> c1 = Configuration.from_revolute_values([1, 0, 3]) - >>> c2 = Configuration.from_revolute_values([1, 2 * pi, 4]) - >>> c1.max_difference(c2) - 1.0 - """ - return max([abs(v) for v in self.iter_differences(other)]) - - def close_to(self, other, tol=1e-3): - """Returns ``True`` if the other `Configuration`'s joint_values are within a certain range. - - Parameters - ---------- - other : :class:`Configuration` - The configuration to compare to. - tol : float - The tolerance under which we consider 2 floats the same. Defaults to 1e-3. - - Returns - ------- - :obj:`bool` - ``True`` if the other `Configuration`'s joint_values are within a certain - tolerance, `False` otherwise. - - Examples - -------- - >>> c1 = Configuration.from_revolute_values([1, 0, 3]) - >>> c2 = Configuration.from_revolute_values([1, 2 * pi, 3]) - >>> c1.close_to(c2) - True - """ - for diff in self.iter_differences(other): - if abs(diff) > tol: - return False - return True - - @property - def has_joint_names(self): - """Returns ``True`` when there is a joint name for every value.""" - return len(self.joint_values) == len(self.joint_names) - - def check_joint_names(self): - """Raises an error if there is not a joint name for every value.""" - if not self.has_joint_names: - if not len(self.joint_names): - raise ValueError("Joint names are required for this operation.") - else: - raise ValueError( - "Joint names do not match the number of joint values. Joint names={}, Joint values={}".format( - len(self.joint_values), len(self.joint_names) - ) - ) - - @property - def joint_dict(self): - """A dictionary of joint values by joint name.""" - self.check_joint_names() - return dict(zip(self.joint_names, self.joint_values)) - - @property - def type_dict(self): - """A dictionary of joint types by joint name.""" - self.check_joint_names() - return dict(zip(self.joint_names, self.joint_types)) - - def merge(self, other): - """Merge the configuration with another configuration in place along joint names. - The other configuration takes precedence over this configuration in - case a joint value is present in both. - - Notes - ----- - Caution: ``joint_names`` may be rearranged. - - Parameters - ---------- - other : :class:`Configuration` - The configuration to be merged. - - Raises - ------ - :exc:`ValueError` - If the configuration or the ``other`` configuration does not specify - joint names for all joint values. - """ - _joint_dict = self.joint_dict - _joint_dict.update(other.joint_dict) - - _type_dict = self.type_dict - _type_dict.update(other.type_dict) - - self.joint_names = list(_joint_dict.keys()) - self.joint_values = [_joint_dict[name] for name in self.joint_names] - self.joint_types = [_type_dict[name] for name in self.joint_names] - - def merged(self, other): - """Get a new ``Configuration`` with this configuration merged with another configuration. - The other configuration takes precedence over this configuration in - case a joint value is present in both. - - Notes - ----- - Caution: ``joint_names`` may be rearranged. - - Parameters - ---------- - other : :class:`Configuration` - The configuration to be merged. - - Returns - ------- - :class:`Configuration` - A configuration with values for all included joints. - - Raises - ------ - :exc:`ValueError` - If the configuration or the ``other`` configuration does not specify - joint names for all joint values. - """ - _joint_dict = self.joint_dict - _joint_dict.update(other.joint_dict) - - _type_dict = self.type_dict - _type_dict.update(other.type_dict) - - joint_names = list(_joint_dict.keys()) - joint_values = [_joint_dict[name] for name in joint_names] - joint_types = [_type_dict[name] for name in joint_names] - - return Configuration(joint_values, joint_types, joint_names) diff --git a/src/compas/robots/model/__init__.py b/src/compas/robots/model/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/compas/robots/model/base.py b/src/compas/robots/model/base.py deleted file mode 100644 index 4381acd225e..00000000000 --- a/src/compas/robots/model/base.py +++ /dev/null @@ -1,118 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from compas.files import URDFElement -from compas.files import URDFGenericElement -from compas.geometry import Frame - - -def _parse_floats(values): - return [float(i) for i in values.split()] - - -def _generic_from_data_or_data(data): - try: - data = URDFGenericElement.from_data(data) - finally: - return data - - -def _attr_to_data(attr): - return {k: v.data if hasattr(v, "data") else v for k, v in attr.items()} - - -def _attr_from_data(data): - return {k: _generic_from_data_or_data(d) for k, d in data.items()} - - -class ProxyObject(object): - """Base proxy class to create object proxies. - - An object proxy wraps the proxied object and forwards all calls to it - unless a method/attribute/property is found first on the proxy itself. - - The magic part is ``__getattr__`` .""" - - def __init__(self, obj): - self._proxied_object = obj - - def __getattr__(self, attr): - return getattr(self._proxied_object, attr) - - def __str__(self): - return str(self._proxied_object) - - def __repr__(self): - return repr(self._proxied_object) - - @classmethod - def create_proxy(cls, obj): - """Creates a proxy wrapping around an object, only if it's not already proxied.""" - if obj and not isinstance(obj, cls): - return cls(obj) - return obj - - -class FrameProxy(ProxyObject): - """Proxy class that adds URDF functionality to an instance of :class:`Frame`. - - This class is internal and not intended to be referenced externally. - """ - - def get_urdf_element(self): - attributes = { - "xyz": "{} {} {}".format(self.point.x, self.point.y, self.point.z), - "rpy": "{} {} {}".format(*self.euler_angles()), - } - return URDFElement("origin", attributes) - - @classmethod - def from_urdf(cls, attributes, elements=None, text=None): - """Create origin instance from an URDF element. - - Parameters - ---------- - attributes : dict[str, Any] - Attributes of the URDF element. - elements: list[object], optional - Children elements of the URDF element. - text: str, optional - Text content of the URDF element. - - Returns - ------- - :class:`FrameProxy` - Frame proxy instance. - - Examples - -------- - >>> attributes = {'rpy': '0.0 1.57 0.0', 'xyz': '0.0 0.13 0.0'} - >>> f = FrameProxy.from_urdf(attributes, [], None) - >>> f - Frame(Point(0.000, 0.130, 0.000), Vector(0.001, 0.000, -1.000), Vector(0.000, 1.000, 0.000)) - - """ - xyz = _parse_floats(attributes.get("xyz", "0 0 0")) - rpy = _parse_floats(attributes.get("rpy", "0 0 0")) - return cls(Frame.from_euler_angles(rpy, static=True, axes="xyz", point=xyz)) - - def scale(self, factor): - """Scale the origin by a given factor. - - Parameters - ---------- - factor : float - Scale factor. - - Returns - ------- - None - - Examples - -------- - >>> o = FrameProxy(Frame([0, 0, 0], [1, 0, 0], [0, 1, 0])) - >>> o.scale(10) - - """ - self.point = self.point * factor diff --git a/src/compas/robots/model/geometry.py b/src/compas/robots/model/geometry.py deleted file mode 100644 index b5f9172a9c0..00000000000 --- a/src/compas/robots/model/geometry.py +++ /dev/null @@ -1,475 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import compas -import compas.colors -import compas.geometry - -from compas.data import Data -from compas.datastructures import Mesh -from compas.files import URDFElement -from compas.geometry import Frame - -from .base import ProxyObject -from .base import _attr_from_data -from .base import _attr_to_data -from .base import _parse_floats - - -class BoxProxy(ProxyObject): - """Proxy class that adds URDF functionality to an instance of :class:`~compas.geometry.Box`. - - This class is internal and not intended to be referenced externally. - """ - - def get_urdf_element(self): - attributes = {"size": "{} {} {}".format(*self.size)} - return URDFElement("box", attributes) - - @classmethod - def from_urdf(cls, attributes, elements=None, text=None): - size = _parse_floats(attributes["size"]) - return cls(compas.geometry.Box(Frame.worldXY(), *size)) - - @property - def meshes(self): - return [Mesh.from_shape(self)] - - @property - def size(self): - return [self.xsize, self.ysize, self.zsize] - - -class CylinderProxy(ProxyObject): - """Proxy class that adds URDF functionality to an instance of :class:`~compas.geometry.Cylinder`. - - This class is internal and not intended to be referenced externally. - """ - - def get_urdf_element(self): - attributes = {"radius": self.radius, "length": self.length} - return URDFElement("cylinder", attributes) - - @classmethod - def from_urdf(cls, attributes, elements=None, text=None): - radius = float(attributes["radius"]) - length = float(attributes["length"]) - frame = compas.geometry.Frame.worldXY() - return cls(compas.geometry.Cylinder(frame, radius=radius, height=length)) - - @property - def meshes(self): - return [Mesh.from_shape(self)] - - @property - def length(self): - return self.height - - -class SphereProxy(ProxyObject): - """Proxy class that adds URDF functionality to an instance of :class:`~compas.geometry.Sphere`. - - This class is internal and not intended to be referenced externally. - """ - - def get_urdf_element(self): - attributes = {"radius": self.radius} - return URDFElement("sphere", attributes) - - @classmethod - def from_urdf(cls, attributes, elements=None, text=None): - radius = float(attributes["radius"]) - return cls(compas.geometry.Sphere(compas.geometry.Frame.worldXY(), radius)) - - @property - def meshes(self): - return [Mesh.from_shape(self)] - - -class CapsuleProxy(ProxyObject): - """Proxy class that adds URDF functionality to an instance of :class:`~compas.geometry.Capsule`. - - This class is internal and not intended to be referenced externally. - """ - - def get_urdf_element(self): - attributes = {"radius": self.radius, "length": self.length} - return URDFElement("capsule", attributes) - - @classmethod - def from_urdf(cls, attributes, elements=None, text=None): - radius = float(attributes["radius"]) - length = float(attributes["length"]) - frame = compas.geometry.Frame.worldXY() - return cls(compas.geometry.Capsule(frame, radius=radius, height=length)) - - @property - def meshes(self): - return [Mesh.from_shape(self)] - - -class MeshDescriptor(Data): - """Description of a mesh. - - Parameters - ---------- - filename : str - The mesh' filename. - scale : str, optional - The scale factors of the mesh in the x-, y-, and z-direction. - **kwargs : dict[str, Any], optional - The keyword arguments (kwargs) collected in a dict. - These allow using non-standard attributes absent in the URDF specification. - - Attributes - ---------- - filename : str - The mesh' filename. - scale : [float, float, float] - The scale factors of the mesh in the x-, y-, and z-direction. - meshes : list[:class:`~compas.datastructures.Mesh`] - List of COMPAS geometric meshes. - - Examples - -------- - >>> m = MeshDescriptor('link.stl') - - """ - - def __init__(self, filename, scale="1.0 1.0 1.0", **kwargs): - super(MeshDescriptor, self).__init__() - self.filename = filename - self.scale = _parse_floats(scale) - self.meshes = [] - self.attr = kwargs or {} - - def get_urdf_element(self): - attributes = {"filename": self.filename} - # There is no need to record default values. Usually these - # coincide with some form of 0 and are filtered out with - # `attributes = dict(filter(lambda x: x[1], attributes.items()))`, - # but here we must be explicit. - if self.scale != [1.0, 1.0, 1.0]: - attributes["scale"] = "{} {} {}".format(*self.scale) - attributes.update(self.attr) - return URDFElement("mesh", attributes) - - @property - def data(self): - return { - "filename": self.filename, - "scale": self.scale, - "attr": _attr_to_data(self.attr), - "meshes": self.meshes, - } - - @data.setter - def data(self, data): - self.filename = data["filename"] - self.scale = data["scale"] - self.attr = _attr_from_data(data["attr"]) if "attr" in data else {} - self.meshes = data["meshes"] - - @classmethod - def from_data(cls, data): - md = cls("") - md.data = data - return md - - -class Color(Data): - """Color represented in RGBA. - - Parameters - ---------- - rgba : str - Color values as string. - - Attributes - ---------- - rgba : [float, float, float, float] - Color values as list of float - - Examples - -------- - >>> c = Color('1 0 0') - >>> c.rgba - [1.0, 0.0, 0.0] - - """ - - def __init__(self, rgba): - super(Color, self).__init__() - self.rgba = _parse_floats(rgba) - - def get_urdf_element(self): - attributes = {"rgba": "{} {} {} {}".format(*self.rgba)} - return URDFElement("color", attributes) - - @property - def data(self): - return { - "rgba": self.rgba, - } - - @data.setter - def data(self, data): - self.rgba = data["rgba"] - - @classmethod - def from_data(cls, data): - color = cls("1 1 1") - color.data = data - return color - - -class Texture(Data): - """Texture description. - - Parameters - ---------- - filename : str - The filename of the texture. - - Attributes - ---------- - filename : str - The filename of the texture. - - Examples - -------- - >>> t = Texture('wood.jpg') - - """ - - def __init__(self, filename): - super(Texture, self).__init__() - self.filename = filename - - def get_urdf_element(self): - attributes = {"filename": self.filename} - return URDFElement("texture", attributes) - - @property - def data(self): - return { - "filename": self.filename, - } - - @data.setter - def data(self, data): - self.filename = data["filename"] - - @classmethod - def from_data(cls, data): - return cls(**data) - - -class Material(Data): - """Material description. - - Parameters - ---------- - name : str - The name of the material. - color : :class:`~compas.robots.Color`, optional - The color of the material. - texture : :class:`~compas.robots.Texture`, optional - The filename of the texture. - - Examples - -------- - >>> c = Color('1 0 0') - >>> material = Material('wood', c) - - >>> material = Material('aqua') - >>> material.get_color() - (0.0, 1.0, 1.0, 1.0) - - """ - - def __init__(self, name=None, color=None, texture=None): - super(Material, self).__init__() - self.name = name - self.color = color - self.texture = texture - - def get_urdf_element(self): - attributes = {"name": self.name} - elements = [self.color, self.texture] - return URDFElement("material", attributes, elements) - - @property - def data(self): - return { - "name": self.name, - "color": self.color.data if self.color else None, - "texture": self.texture.data if self.texture else None, - } - - @data.setter - def data(self, data): - self.name = data["name"] - self.color = Color.from_data(data["color"]) if data["color"] else None - self.texture = Texture.from_data(data["texture"]) if data["texture"] else None - - def get_color(self): - """Get the RGBA color array of the material. - - Returns - ------- - [float, float, float, float] - List of 4 floats (``0.0-1.0``) indicating RGB colors and Alpha channel of the material. - - Examples - -------- - >>> material = Material('aqua') - >>> material.get_color() - (0.0, 1.0, 1.0, 1.0) - - """ - if self.name: - try: - color = compas.colors.Color.from_name(self.name) - return color.rgba - except ValueError: - pass - if self.color: - return self.color.rgba - return None - - -TYPE_CLASS_ENUM = { - "box": compas.geometry.Box, - "cylinder": compas.geometry.Cylinder, - "sphere": compas.geometry.Sphere, - "capsule": compas.geometry.Capsule, - "mesh": MeshDescriptor, -} - -# TYPE_CLASS_ENUM_BY_DATA = { -# ("frame", "xsize", "ysize", "zsize"): compas.geometry.Box, -# ("circle", "height"): compas.geometry.Cylinder, -# ("point", "radius"): compas.geometry.Sphere, -# ("line", "radius"): compas.geometry.Capsule, -# ("attr", "filename", "scale"): MeshDescriptor, -# ("attr", "filename", "meshes", "scale"): MeshDescriptor, -# } - - -# def _get_type_from_shape_data(data): -# # This is here only to support models serialized with older versions of COMPAS -# if "type" in data: -# return TYPE_CLASS_ENUM[data["type"]] - -# # The current scenario is that we need to figure out the object type based on the DATASCHEMA -# keys = tuple(sorted(data.keys())) -# return TYPE_CLASS_ENUM_BY_DATA[keys] - - -class Geometry(Data): - """Geometrical description of the shape of a link. - - Parameters - ---------- - box : :class:`~compas.geometry.Box`, optional - A box shape primitive. - cylinder : :class:`~compas.geometry.Cylinder`, optional - A cylinder shape primitive. - sphere : :class:`~compas.geometry.Sphere`, optional - A sphere shape primitive. - capsule : :class:`~compas.geometry.Capsule`, optional - A capsule shape primitive. - mesh : :class:`~compas.robots.MeshDescriptor`, optional - A descriptor of a mesh. - **kwargs : dict[str, Any], optional - The keyword arguments (kwargs) collected in a dict. - These allow using non-standard attributes absent in the URDF specification. - - Attributes - ---------- - shape : object - The shape of the geometry - attr : keyword arguments - Additional attributes - - Examples - -------- - >>> box = compas.geometry.Box(Frame.worldXY(), 1, 1, 1) - >>> geo = Geometry(box=box) - - """ - - def __init__(self, box=None, cylinder=None, sphere=None, capsule=None, mesh=None, **kwargs): - super(Geometry, self).__init__() - self.shape = box or cylinder or sphere or capsule or mesh - self.attr = kwargs - - @property - def shape(self): - return self._shape - - @shape.setter - def shape(self, value): - if value is None: - self._shape = None - return - - if isinstance(value, compas.geometry.Box): - self._shape = BoxProxy.create_proxy(value) - elif isinstance(value, compas.geometry.Cylinder): - self._shape = CylinderProxy.create_proxy(value) - elif isinstance(value, compas.geometry.Sphere): - self._shape = SphereProxy.create_proxy(value) - elif isinstance(value, compas.geometry.Capsule): - self._shape = CapsuleProxy.create_proxy(value) - else: - self._shape = value - - if "meshes" not in dir(self._shape): - raise TypeError("Shape implementation does not define a meshes accessor: {}".format(type(self._shape))) - - def get_urdf_element(self): - attributes = self.attr.copy() - elements = [self.shape] - return URDFElement("geometry", attributes, elements) - - @property - def data(self): - return { - "shape": self.shape, # type: ignore - "attr": _attr_to_data(self.attr), - } - - @data.setter - def data(self, data): - # class_ = _get_type_from_shape_data(data["shape"]) - self.shape = data["shape"] - self.attr = _attr_from_data(data["attr"]) - - @classmethod - def from_data(cls, data): - # class_ = _get_type_from_shape_data(data["shape"]) - geo = cls(box=data["shape"]) - geo.data = data - return geo - - @staticmethod - def _get_item_meshes(item): - meshes = item.geometry.shape.meshes - - if meshes: - # Coerce meshes into an iterable (a tuple if not natively iterable) - if not hasattr(meshes, "__iter__"): - meshes = (meshes,) - - return meshes - - -# Deprecated: this are aliases for backwards compatibility, but need to be removed on 2.x -Origin = Frame -Cylinder = CylinderProxy -Box = BoxProxy -Sphere = SphereProxy -Capsule = CapsuleProxy diff --git a/src/compas/robots/model/joint.py b/src/compas/robots/model/joint.py deleted file mode 100644 index 16d6f02fa6a..00000000000 --- a/src/compas/robots/model/joint.py +++ /dev/null @@ -1,779 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from compas.data import Data -from compas.files import URDFElement -from compas.files import URDFParser -from compas.geometry import Frame -from compas.geometry import Rotation -from compas.geometry import Transformation -from compas.geometry import Translation -from compas.geometry import Vector -from compas.geometry import transform_vectors - -from .base import FrameProxy -from .base import _attr_from_data -from .base import _attr_to_data -from .base import _parse_floats - - -class ParentLink(Data): - """Describes a parent relation between a joint its parent link.""" - - def __init__(self, link): - super(ParentLink, self).__init__() - self.link = link - - def __str__(self): - return str(self.link) - - def get_urdf_element(self): - return URDFElement("parent", {"link": self.link}) - - @property - def data(self): - return { - "link": self.link, - } - - @data.setter - def data(self, data): - self.link = data["link"] - - @classmethod - def from_data(cls, data): - return cls(data["link"]) - - -class ChildLink(Data): - """Describes a child relation between a joint and its child link.""" - - def __init__(self, link): - super(ChildLink, self).__init__() - self.link = link - - def __str__(self): - return str(self.link) - - def get_urdf_element(self): - return URDFElement("child", {"link": self.link}) - - @property - def data(self): - return { - "link": self.link, - } - - @data.setter - def data(self, data): - self.link = data["link"] - - @classmethod - def from_data(cls, data): - return cls(data["link"]) - - -class Calibration(Data): - """Reference positions of the joint, used to calibrate the absolute position.""" - - def __init__(self, rising=0.0, falling=0.0, reference_position=0.0): - super(Calibration, self).__init__() - self.rising = float(rising) - self.falling = float(falling) - self.reference_position = float(reference_position) - - def get_urdf_element(self): - attributes = { - "rising": self.rising, - "falling": self.falling, - "reference_position": self.reference_position, - } - attributes = dict(filter(lambda x: x[1], attributes.items())) - return URDFElement("calibration", attributes) - - @property - def data(self): - return { - "rising": self.rising, - "falling": self.falling, - "reference_position": self.reference_position, - } - - @data.setter - def data(self, data): - self.rising = data["rising"] - self.falling = data["falling"] - self.reference_position = data["reference_position"] - - -class Dynamics(Data): - """Physical properties of the joint used for simulation of dynamics.""" - - def __init__(self, damping=0.0, friction=0.0, **kwargs): - super(Dynamics, self).__init__() - self.damping = float(damping) - self.friction = float(friction) - self.attr = kwargs - - def get_urdf_element(self): - attributes = { - "damping": self.damping, - "friction": self.friction, - } - attributes.update(self.attr) - return URDFElement("dynamics", attributes) - - @property - def data(self): - return { - "damping": self.damping, - "friction": self.friction, - "attr": _attr_to_data(self.attr), - } - - @data.setter - def data(self, data): - self.damping = data["damping"] - self.friction = data["friction"] - self.attr = _attr_from_data(data["attr"]) - - -class Limit(Data): - """Joint limit properties. - - Attributes - ---------- - effort : float - Maximum joint effort. - velocity : float - Maximum joint velocity. - lower : float - Lower joint limit (radians for revolute joints, meter for prismatic joints). - upper : float - Upper joint limit (radians for revolute joints, meter for prismatic joints). - - """ - - def __init__(self, effort=0.0, velocity=0.0, lower=0.0, upper=0.0, **kwargs): - super(Limit, self).__init__() - self.effort = float(effort) - self.velocity = float(velocity) - self.lower = float(lower) - self.upper = float(upper) - self.attr = kwargs - - def get_urdf_element(self): - attributes = { - "lower": self.lower, - "upper": self.upper, - } - attributes = dict(filter(lambda x: x[1], attributes.items())) - attributes["effort"] = self.effort - attributes["velocity"] = self.velocity - attributes.update(self.attr) - return URDFElement("limit", attributes) - - @property - def data(self): - return { - "effort": self.effort, - "velocity": self.velocity, - "lower": self.lower, - "upper": self.upper, - "attr": _attr_to_data(self.attr), - } - - @data.setter - def data(self, data): - self.effort = data["effort"] - self.velocity = data["velocity"] - self.lower = data["lower"] - self.upper = data["upper"] - self.attr = _attr_from_data(data["attr"]) - - def scale(self, factor): - """Scale the upper and lower limits by a given factor. - - Parameters - ---------- - factor : float - Scale factor. - - Returns - ------- - None - - """ - self.lower *= factor - self.upper *= factor - - -class Mimic(Data): - """Description of joint mimic.""" - - def __init__(self, joint, multiplier=1.0, offset=0.0): - super(Mimic, self).__init__() - self.joint = joint # == joint name - self.multiplier = float(multiplier) - self.offset = float(offset) - - def get_urdf_element(self): - attributes = {"joint": self.joint} - if self.multiplier != 1.0: - attributes["multiplier"] = self.multiplier - if self.offset != 0.0: - attributes["offset"] = self.offset - return URDFElement("mimic", attributes) - - @property - def data(self): - return { - "joint": self.joint, - "multiplier": self.multiplier, - "offset": self.offset, - } - - @data.setter - def data(self, data): - self.joint = data["joint"] - self.multiplier = data["multiplier"] - self.offset = data["offset"] - - @classmethod - def from_data(cls, data): - mimic = cls(data["joint"]) - mimic.data = data - return mimic - - def calculate_position(self, mimicked_joint_position): - return self.multiplier * mimicked_joint_position + self.offset - - -class SafetyController(Data): - """Safety controller properties.""" - - def __init__(self, k_velocity, k_position=0.0, soft_lower_limit=0.0, soft_upper_limit=0.0): - super(SafetyController, self).__init__() - self.k_velocity = float(k_velocity) - self.k_position = float(k_position) - self.soft_lower_limit = float(soft_lower_limit) - self.soft_upper_limit = float(soft_upper_limit) - - def get_urdf_element(self): - attributes = { - "k_position": self.k_position, - "soft_lower_limit": self.soft_lower_limit, - "soft_upper_limit": self.soft_upper_limit, - } - attributes = dict(filter(lambda x: x[1], attributes.items())) - attributes["k_velocity"] = self.k_velocity - return URDFElement("safety_controller", attributes) - - @property - def data(self): - return { - "k_velocity": self.k_velocity, - "k_position": self.k_position, - "soft_lower_limit": self.soft_lower_limit, - "soft_upper_limit": self.soft_upper_limit, - } - - @data.setter - def data(self, data): - self.k_velocity = data["k_velocity"] - self.k_position = data["k_position"] - self.soft_lower_limit = data["soft_lower_limit"] - self.soft_upper_limit = data["soft_upper_limit"] - - @classmethod - def from_data(cls, data): - sc = cls(data["k_velocity"]) - sc.data = data - return sc - - -class Axis(Data): - """Representation of an axis or vector. - - Attributes - ---------- - x : float - X coordinate. - y: float - Y coordinate. - z : float - Z coordinate. - attr : dict - Additional axis attributes. - - """ - - def __init__(self, xyz="1 0 0", **kwargs): - # We are not using Vector here because we - # cannot attach _urdf_source to it due to __slots__ - super(Axis, self).__init__() - xyz = _parse_floats(xyz) - xyz = Vector(*xyz) - if xyz.length != 0: - xyz.unitize() - self.x = xyz[0] - self.y = xyz[1] - self.z = xyz[2] - self.attr = kwargs - - def get_urdf_element(self): - attributes = {"xyz": "{} {} {}".format(self.x, self.y, self.z)} - attributes.update(self.attr) - return URDFElement("axis", attributes) - - @property - def data(self): - return { - "x": self.x, - "y": self.y, - "z": self.z, - "attr": _attr_to_data(self.attr), - } - - @data.setter - def data(self, data): - self.x = data["x"] - self.y = data["y"] - self.z = data["z"] - self.attr = _attr_from_data(data["attr"]) - - def copy(self): - """Create a copy of the axis instance.""" - cls = type(self) - return cls("%f %f %f" % (self.x, self.y, self.z)) - - def transform(self, transformation): - """Transform the axis in place. - - Parameters - ---------- - transformation : :class:`~compas.geometry.Transformation` - The transformation used to transform the axis. - - """ - xyz = transform_vectors([[self.x, self.y, self.z]], transformation.matrix) - self.x = xyz[0][0] - self.y = xyz[0][1] - self.z = xyz[0][2] - - def transformed(self, transformation): - """Return a transformed copy of the axis. - - Parameters - ---------- - transformation : :class:`~compas.geometry.Transformation` - The transformation used to transform the axis. - - Returns - ------- - :class:`Axis` - The transformed axis. - - """ - xyz = transform_vectors([[self.x, self.y, self.z]], transformation.matrix) - return Vector(xyz[0][0], xyz[0][1], xyz[0][2]) - - @property - def vector(self): - """Vector of the axis.""" - return Vector(self.x, self.y, self.z) - - def __str__(self): - return "[%.3f, %.3f, %.3f]" % (self.x, self.y, self.z) - - -class Joint(Data): - """Representation of the kinematics and dynamics of a joint and its safety limits. - - Attributes - ---------- - name : str - Unique name for the joint. - type : str | int - Joint type either as a string or an index number. See class attributes for named constants and supported types. - origin : :class:`Frame` - Frame defining the transformation from the parent link to the child link frame. - parent : :class:`ParentLink` | str - Parent link instance or parent link name. - child : :class:`ChildLink` | str - Child link instance or name of child link. - axis : :class:`Axis` - Joint axis specified in the joint frame. Represents the axis of - rotation for revolute joints, the axis of translation for prismatic - joints, and the surface normal for planar joints. The axis is - specified in the joint frame of reference. - calibration : :class:`Calibration` - Reference positions of the joint, used to calibrate the absolute position of the joint. - dynamics : :class:`Dynamics` - Physical properties of the joint. These values are used to - specify modeling properties of the joint, particularly useful for - simulation. - limit : :class:`Limit` - Joint limit properties. - safety_controller : :class:`SafetyController` - Safety controller properties. - mimic : :class:`Mimic` - Used to specify that the defined joint mimics another existing joint. - attr : dict - Non-standard attributes. - child_link : :class:`Link` - Joint's child link - position : float - The current position of the joint. This depends on the - joint type, i.e. for revolute joints, it will be the rotation angle - in radians, for prismatic joints the translation in meters. - - Class Attributes - ---------------- - REVOLUTE : int - Revolute joint type. - CONTINUOUS : int - Continuous joint type. - PRISMATIC : int - Prismatic joint type. - FIXED : int - Fixed joint type. - FLOATING : int - Floating joint type. - PLANAR : int - Planar joint type. - SUPPORTED_TYPES : list[str] - String representations of the supported joint types. - """ - - REVOLUTE = 0 - CONTINUOUS = 1 - PRISMATIC = 2 - FIXED = 3 - FLOATING = 4 - PLANAR = 5 - - SUPPORTED_TYPES = ( - "revolute", - "continuous", - "prismatic", - "fixed", - "floating", - "planar", - ) - - def __init__( - self, - name, - type, - parent, - child, - origin=None, - axis=None, - calibration=None, - dynamics=None, - limit=None, - safety_controller=None, - mimic=None, - **kwargs - ): - type_idx = type - - if isinstance(type_idx, str) and type_idx in Joint.SUPPORTED_TYPES: - type_idx = Joint.SUPPORTED_TYPES.index(type_idx) - - if type_idx not in range(len(Joint.SUPPORTED_TYPES)): - raise ValueError("Unsupported joint type: %s" % type) - - super(Joint, self).__init__() - self.name = name - self.type = type_idx - self.parent = parent if isinstance(parent, ParentLink) else ParentLink(parent) - self.child = child if isinstance(child, ChildLink) else ChildLink(child) - self.origin = origin or Frame.from_euler_angles([0.0, 0.0, 0.0]) - self.axis = axis or Axis() - self.calibration = calibration - self.dynamics = dynamics - self.limit = limit - self.safety_controller = safety_controller - self.mimic = mimic - self.attr = kwargs - self.child_link = None - self.position = 0 - # The following are world-relative frames representing the origin and the axis, which change with - # the joint state, while `origin` and `axis` above are parent-relative and static. - self.current_origin = self.origin.copy() - self.current_axis = self.axis.copy() - - @property - def origin(self): - return self._origin - - @origin.setter - def origin(self, value): - self._origin = FrameProxy.create_proxy(value) - - @property - def current_origin(self): - return self._current_origin - - @current_origin.setter - def current_origin(self, value): - self._current_origin = FrameProxy.create_proxy(value) - - def get_urdf_element(self): - attributes = {"name": self.name, "type": self.SUPPORTED_TYPES[self.type]} - attributes.update(self.attr) - elements = [ - self.parent, - self.child, - self.axis, - self.calibration, - self.dynamics, - self.limit, - self.safety_controller, - self.mimic, - self.origin, - ] - return URDFElement("joint", attributes, elements) - - @property - def data(self): - return { - "name": self.name, - "type": self.SUPPORTED_TYPES[self.type], - "parent": self.parent.data, - "child": self.child.data, - "origin": self.origin.data if self.origin else None, - "axis": self.axis.data if self.axis else None, - "calibration": self.calibration.data if self.calibration else None, - "dynamics": self.dynamics.data if self.dynamics else None, - "limit": self.limit.data if self.limit else None, - "safety_controller": self.safety_controller.data if self.safety_controller else None, - "mimic": self.mimic.data if self.mimic else None, - "attr": _attr_to_data(self.attr), - "position": self.position, - } - - @data.setter - def data(self, data): - self.name = data["name"] - self.type = Joint.SUPPORTED_TYPES.index(data["type"]) - self.parent = ParentLink.from_data(data["parent"]) - self.child = ChildLink.from_data(data["child"]) - self.origin = Frame.from_data(data["origin"]) if data["origin"] else None - self.axis = Axis.from_data(data["axis"]) if data["axis"] else None - self.calibration = Calibration.from_data(data["calibration"]) if data["calibration"] else None - self.dynamics = Dynamics.from_data(data["dynamics"]) if data["dynamics"] else None - self.limit = Limit.from_data(data["limit"]) if data["limit"] else None - self.safety_controller = ( - SafetyController.from_data(data["safety_controller"]) if data["safety_controller"] else None - ) - self.mimic = Mimic.from_data(data["mimic"]) if data["mimic"] else None - self.attr = _attr_from_data(data["attr"]) - self.position = data["position"] - - @classmethod - def from_data(cls, data): - joint = cls( - data["name"], - data["type"], - ParentLink.from_data(data["parent"]), - ChildLink.from_data(data["child"]), - ) - joint.data = data - return joint - - @property - def current_transformation(self): - """Current transformation of the joint.""" - return Transformation.from_frame(self.current_origin) - - def transform(self, transformation): - """Transform the joint in place. - - Parameters - ---------- - transformation : :class:`~compas.geometry.Transformation` - The transformation used to transform the joint. - - Returns - ------- - None - - """ - self.current_origin.transform(transformation) - self.current_axis.transform(transformation) - - def _create(self, transformation): - """Internal method to initialize the transformation tree. - - Parameters - ---------- - transformation : :class:`~compas.geometry.Transformation` - The transformation used to transform the joint. - - Returns - ------- - None - - """ - self.current_origin = self.origin.transformed(transformation) - self.current_axis.transform(self.current_transformation) - - def calculate_revolute_transformation(self, position): - """Returns a transformation of a revolute joint. - - A revolute joint rotates about the axis and has a limited range - specified by the upper and lower limits. - - Parameters - ---------- - position : float - Angle in radians. - - Returns - ------- - :class:`Rotation` - Transformation of type rotation for the revolute joint. - - """ - if not self.limit: - raise ValueError("Revolute joints are required to define a limit") - - position = max(min(position, self.limit.upper), self.limit.lower) - return self.calculate_continuous_transformation(position) - - def calculate_continuous_transformation(self, position): - """Returns a transformation of a continuous joint. - - A continuous joint rotates about the axis and has no upper and lower - limits. - - Parameters - ---------- - position : float - Angle in radians - - Returns - ------- - :class:`Rotation` - Transformation of type rotation for the continuous joint. - - """ - return Rotation.from_axis_and_angle(self.current_axis.vector, position, self.current_origin.point) - - def calculate_prismatic_transformation(self, position): - """Returns a transformation of a prismatic joint. - - A prismatic joint slides along the axis and has a limited range - specified by the upper and lower limits. - - Parameters - ---------- - position : float - Translation movement in meters. - - Returns - ------- - :class:`Translation` - Transformation of type translation for the prismatic joint. - - """ - if not self.limit: - raise ValueError("Prismatic joints are required to define a limit") - - position = max(min(position, self.limit.upper), self.limit.lower) - return Translation.from_vector(self.current_axis.vector * position) - - # does this ever happen? - def calculate_fixed_transformation(self, position): - """Returns an identity transformation. - - The fixed joint is is not really a joint because it cannot move. All - degrees of freedom are locked. - - Returns - ------- - :class:`Translation` - Identity transformation. - - """ - return Transformation() - - def calculate_floating_transformation(self, position): - """Returns a transformation of a floating joint. - - A floating joint allows motion for all 6 degrees of freedom. - """ - raise NotImplementedError - - def calculate_planar_transformation(self, position): - """Returns a transformation of a planar joint. - - A planar joint allows motion in a plane perpendicular to the axis. - """ - raise NotImplementedError - - def calculate_transformation(self, position): - """Returns the transformation of the joint. - - This function calls different calculate_*_transformation depends on self.type - - Parameters - ---------- - position : float - Position in radians or meters depending on the joint type. - """ - - # Set the transformation function according to the type - if not hasattr(self, "_calculate_transformation"): - switcher = { - Joint.REVOLUTE: self.calculate_revolute_transformation, - Joint.CONTINUOUS: self.calculate_continuous_transformation, - Joint.PRISMATIC: self.calculate_prismatic_transformation, - Joint.FIXED: self.calculate_fixed_transformation, - Joint.FLOATING: self.calculate_floating_transformation, - Joint.PLANAR: self.calculate_planar_transformation, - } - self._calculate_transformation = switcher.get(self.type) - - return self._calculate_transformation(position) - - def is_configurable(self): - """Returns ``True`` if the joint can be configured, otherwise ``False``.""" - return self.type != Joint.FIXED and self.mimic is None - - def is_scalable(self): - """Returns ``True`` if the joint can be scaled, otherwise ``False``.""" - return self.type in [Joint.PLANAR, Joint.PRISMATIC] - - def scale(self, factor): - """Scale the joint origin and limit (only if scalable) by a given factor. - - Parameters - ---------- - factor : float - Scale factor. - - Returns - ------- - None - """ - self.current_origin.scale(factor) - if self.is_scalable(): - self.limit.scale(factor) - - -URDFParser.install_parser(Joint, "robot/joint") -URDFParser.install_parser(ParentLink, "robot/joint/parent") -URDFParser.install_parser(ChildLink, "robot/joint/child") -URDFParser.install_parser(Calibration, "robot/joint/calibration") -URDFParser.install_parser(Dynamics, "robot/joint/dynamics") -URDFParser.install_parser(Limit, "robot/joint/limit") -URDFParser.install_parser(Axis, "robot/joint/axis") -URDFParser.install_parser(Mimic, "robot/joint/mimic") -URDFParser.install_parser(SafetyController, "robot/joint/safety_controller") - -URDFParser.install_parser(Frame, "robot/joint/origin", proxy_type=FrameProxy) diff --git a/src/compas/robots/model/link.py b/src/compas/robots/model/link.py deleted file mode 100644 index b9e41c8f5eb..00000000000 --- a/src/compas/robots/model/link.py +++ /dev/null @@ -1,512 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from compas.data import Data -from compas.files import URDFElement -from compas.files import URDFParser -from compas.geometry import Box -from compas.geometry import Capsule -from compas.geometry import Cylinder -from compas.geometry import Frame -from compas.geometry import Sphere -from compas.geometry import Transformation - -from .base import FrameProxy -from .base import _attr_from_data -from .base import _attr_to_data - -from .geometry import BoxProxy -from .geometry import CapsuleProxy -from .geometry import Color -from .geometry import CylinderProxy -from .geometry import Geometry -from .geometry import Material -from .geometry import MeshDescriptor -from .geometry import SphereProxy -from .geometry import Texture - - -class Mass(Data): - """Represents a value of mass usually related to a link.""" - - def __init__(self, value): - super(Mass, self).__init__() - self.value = float(value) - - def __str__(self): - return str(self.value) - - def get_urdf_element(self): - attributes = {"value": self.value} - return URDFElement("mass", attributes) - - @property - def data(self): - return {"value": self.value} - - @data.setter - def data(self, data): - self.value = data["value"] - - @classmethod - def from_data(cls, data): - return cls(**data) - - -class Inertia(Data): - """Rotational inertia matrix (3x3) represented in the inertia frame. - - Since the rotational inertia matrix is symmetric, only 6 above-diagonal - elements of this matrix are specified here, using the attributes - ``ixx``, ``ixy``, ``ixz``, ``iyy``, ``iyz``, ``izz``. - - """ - - def __init__(self, ixx=0.0, ixy=0.0, ixz=0.0, iyy=0.0, iyz=0.0, izz=0.0): - super(Inertia, self).__init__() - self.ixx = float(ixx) - self.ixy = float(ixy) - self.ixz = float(ixz) - self.iyy = float(iyy) - self.iyz = float(iyz) - self.izz = float(izz) - - def get_urdf_element(self): - attributes = { - "ixx": self.ixx, - "ixy": self.ixy, - "ixz": self.ixz, - "iyy": self.iyy, - "iyz": self.iyz, - "izz": self.izz, - } - return URDFElement("inertia", attributes) - - @property - def data(self): - return { - "ixx": self.ixx, - "ixy": self.ixy, - "ixz": self.ixz, - "iyy": self.iyy, - "iyz": self.iyz, - "izz": self.izz, - } - - @data.setter - def data(self, data): - self.ixx = data.get("ixx", 0.0) - self.ixy = data.get("ixy", 0.0) - self.ixz = data.get("ixz", 0.0) - self.iyy = data.get("iyy", 0.0) - self.iyz = data.get("iyz", 0.0) - self.izz = data.get("izz", 0.0) - - @classmethod - def from_data(cls, data): - return cls(**data) - - -class Inertial(Data): - """Inertial properties of a link. - - Attributes - ---------- - origin - This is the pose of the inertial reference frame, - relative to the link reference frame. - mass - Mass of the link. - inertia - 3x3 rotational inertia matrix, represented in the inertia frame. - - """ - - def __init__(self, origin=None, mass=None, inertia=None): - super(Inertial, self).__init__() - self.origin = origin - self.mass = mass - self.inertia = inertia - - @property - def origin(self): - return self._origin - - @origin.setter - def origin(self, value): - self._origin = FrameProxy.create_proxy(value) - - def get_urdf_element(self): - elements = [self.origin, self.mass, self.inertia] - return URDFElement("inertial", elements=elements) - - @property - def data(self): - return { - "origin": self.origin.data if self.origin else None, - "mass": self.mass.data if self.mass else None, - "inertia": self.inertia.data if self.inertia else None, - } - - @data.setter - def data(self, data): - self.origin = Frame.from_data(data["origin"]) if data["origin"] else None - self.mass = Mass.from_data(data["mass"]) if data["mass"] else None - self.inertia = Inertia.from_data(data["inertia"]) if data["inertia"] else None - - -class LinkItem(object): - def __init__(self): - self.init_transformation = None # to store the init transformation - self.current_transformation = None # to store the current transformation - self.native_geometry = None # to store the link's CAD native geometry - - -class Visual(LinkItem, Data): - """Visual description of a link. - - Attributes - ---------- - geometry - Shape of the visual element. - origin - Reference frame of the visual element with respect - to the reference frame of the link. - name - Name of the visual element. - material - Material of the visual element. - attr - Non-standard attributes. - - """ - - def __init__(self, geometry, origin=None, name=None, material=None, **kwargs): - super(Visual, self).__init__() - self.geometry = geometry - self.origin = origin - self.name = name - self.material = material - self.attr = kwargs - - @property - def origin(self): - return self._origin - - @origin.setter - def origin(self, value): - self._origin = FrameProxy.create_proxy(value) - - def get_urdf_element(self): - attributes = {} - if self.name is not None: - attributes["name"] = self.name - attributes.update(self.attr) - elements = [self.origin, self.geometry, self.material] - return URDFElement("visual", attributes, elements) - - # Overriding the default name property, because sometimes the name really is `None`. - @property - def name(self): - return self._name - - @name.setter - def name(self, name): - self._name = name - - @property - def data(self): - return { - "geometry": self.geometry.data, - "origin": self.origin.data if self.origin else None, - "name": self.name, - "material": self.material.data if self.material else None, - "attr": _attr_to_data(self.attr), - "init_transformation": self.init_transformation.data if self.init_transformation else None, - "current_transformation": self.current_transformation.data if self.current_transformation else None, - } - - @data.setter - def data(self, data): - self.geometry = Geometry.from_data(data["geometry"]) - self.origin = Frame.from_data(data["origin"]) if data["origin"] else None - self.name = data["name"] - self.material = Material.from_data(data["material"]) if data["material"] else None - self.attr = _attr_from_data(data["attr"]) - self.init_transformation = ( - Transformation.from_data(data["init_transformation"]) if data["init_transformation"] else None - ) - self.current_transformation = ( - Transformation.from_data(data["current_transformation"]) if data["current_transformation"] else None - ) - - @classmethod - def from_data(cls, data): - visual = cls(Geometry.from_data(data["geometry"])) - visual.data = data - return visual - - def get_color(self): - """Get the RGBA color array assigned to the link. - - Only if the link has a material assigned. - - Returns - ------- - list[float] - List of 4 floats (``0.0-1.0``) indicating RGB colors and Alpha channel. - - """ - if self.material: - return self.material.get_color() - else: - return None - - @classmethod - def from_primitive(cls, primitive, **kwargs): - """Create visual link from a primitive shape. - - Parameters - ---------- - primitive : :class:`compas.geometry.Shape` - A primitive shape. - **kwargs : dict[str, Any], optional - The keyword arguments (kwargs) collected in a dict. - These allow using non-standard attributes absent in the URDF specification. - - Returns - ------- - :class:`~compas.datastructures.Mesh` - A visual description object. - """ - geometry = Geometry() - geometry.shape = primitive - return cls(geometry, **kwargs) - - -class Collision(LinkItem, Data): - """Collidable description of a link. - - Attributes - ---------- - geometry - Shape of the collidable element. - origin - Reference frame of the collidable element with respect - to the reference frame of the link. - name - Name of the collidable element. - attr - Non-standard attributes. - - """ - - def __init__(self, geometry, origin=None, name=None, **kwargs): - super(Collision, self).__init__() - self.geometry = geometry - self.origin = origin - self.name = name - self.attr = kwargs - - @property - def origin(self): - return self._origin - - @origin.setter - def origin(self, value): - self._origin = FrameProxy.create_proxy(value) - - def get_urdf_element(self): - attributes = {} - if self.name is not None: - attributes["name"] = self.name - attributes.update(self.attr) - elements = [self.origin, self.geometry] - return URDFElement("collision", attributes, elements) - - # Overriding the default name property, because sometimes the name really is `None`. - @property - def name(self): - return self._name - - @name.setter - def name(self, name): - self._name = name - - @property - def data(self): - return { - "geometry": self.geometry.data, - "origin": self.origin.data if self.origin else None, - "name": self.name, - "attr": _attr_to_data(self.attr), - "init_transformation": self.init_transformation.data if self.init_transformation else None, - "current_transformation": self.current_transformation.data if self.current_transformation else None, - } - - @data.setter - def data(self, data): - self.geometry = Geometry.from_data(data["geometry"]) - self.origin = Frame.from_data(data["origin"]) if data["origin"] else None - self.name = data["name"] - self.attr = _attr_from_data(data["attr"]) - self.init_transformation = ( - Transformation.from_data(data["init_transformation"]) if data["init_transformation"] else None - ) - self.current_transformation = ( - Transformation.from_data(data["current_transformation"]) if data["current_transformation"] else None - ) - - @classmethod - def from_data(cls, data): - collision = cls(Geometry.from_data(data["geometry"])) - collision.data = data - return collision - - @classmethod - def from_primitive(cls, primitive, **kwargs): - """Create collision link from a primitive shape. - - Parameters - ---------- - primitive : :class:`compas.geometry.Shape` - A primitive shape. - **kwargs : dict[str, Any], optional - The keyword arguments (kwargs) collected in a dict. - These allow using non-standard attributes absent in the URDF specification. - - Returns - ------- - :class:`~compas.datastructures.Mesh` - A collision description object. - """ - geometry = Geometry() - geometry.shape = primitive - return cls(geometry, **kwargs) - - -class Link(Data): - """Link represented as a rigid body with an inertia, visual, and collision features. - - Attributes - ---------- - name - Name of the link itself. - type - Link type. Undocumented in URDF, but used by PR2. - visual - Visual properties of the link. - collision - Collision properties of the link. This can be different - from the visual properties of a link. - inertial - Inertial properties of the link. - attr - Non-standard attributes. - joints - A list of joints that are the link's children - parent_joint - The reference to a parent joint if it exists - - """ - - def __init__(self, name, type=None, visual=(), collision=(), inertial=None, **kwargs): - super(Link, self).__init__() - self.name = name - self.type = type - self.visual = list(visual or []) - self.collision = list(collision or []) - self.inertial = inertial - self.attr = kwargs - self.joints = [] - self.parent_joint = None - - def get_urdf_element(self): - attributes = {"name": self.name} - if self.type is not None: - attributes["type"] = self.type - attributes.update(self.attr) - elements = self.visual + self.collision + [self.inertial] - return URDFElement("link", attributes, elements) - - @property - def data(self): - return { - "name": self.name, - "type": self.type, - "visual": [visual.data for visual in self.visual], - "collision": [collision.data for collision in self.collision], - "inertial": self.inertial.data if self.inertial else None, - "attr": _attr_to_data(self.attr), - "joints": [joint.data for joint in self.joints], - } - - @data.setter - def data(self, data): - from .joint import Joint - - self.name = data["name"] - self.type = data["type"] - self.visual = [Visual.from_data(d) for d in data["visual"]] - self.collision = [Collision.from_data(d) for d in data["collision"]] - self.inertial = Inertial.from_data(data["inertial"]) if data["inertial"] else None - self.attr = _attr_from_data(data["attr"]) - self.joints = [Joint.from_data(d) for d in data["joints"]] - - @classmethod - def from_data(cls, data): - link = cls(data["name"]) - link.data = data - return link - - -URDFParser.install_parser(Link, "robot/link") -URDFParser.install_parser(Inertial, "robot/link/inertial") -URDFParser.install_parser(Mass, "robot/link/inertial/mass") -URDFParser.install_parser(Inertia, "robot/link/inertial/inertia") - -URDFParser.install_parser(Visual, "robot/link/visual") -URDFParser.install_parser(Collision, "robot/link/collision") - -URDFParser.install_parser( - Frame, - "robot/link/inertial/origin", - "robot/link/visual/origin", - "robot/link/collision/origin", - proxy_type=FrameProxy, -) -URDFParser.install_parser(Geometry, "robot/link/visual/geometry", "robot/link/collision/geometry") -URDFParser.install_parser( - MeshDescriptor, - "robot/link/visual/geometry/mesh", - "robot/link/collision/geometry/mesh", -) -URDFParser.install_parser( - Box, - "robot/link/visual/geometry/box", - "robot/link/collision/geometry/box", - proxy_type=BoxProxy, -) -URDFParser.install_parser( - Cylinder, - "robot/link/visual/geometry/cylinder", - "robot/link/collision/geometry/cylinder", - proxy_type=CylinderProxy, -) -URDFParser.install_parser( - Sphere, - "robot/link/visual/geometry/sphere", - "robot/link/collision/geometry/sphere", - proxy_type=SphereProxy, -) -URDFParser.install_parser( - Capsule, - "robot/link/visual/geometry/capsule", - "robot/link/collision/geometry/capsule", - proxy_type=CapsuleProxy, -) - -URDFParser.install_parser(Material, "robot/link/visual/material") -URDFParser.install_parser(Color, "robot/link/visual/material/color") -URDFParser.install_parser(Texture, "robot/link/visual/material/texture") diff --git a/src/compas/robots/model/robot.py b/src/compas/robots/model/robot.py deleted file mode 100644 index a7480fb618d..00000000000 --- a/src/compas/robots/model/robot.py +++ /dev/null @@ -1,1188 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import itertools -import random - -import compas -from compas.data import Data -from compas.datastructures import Mesh -from compas.files import URDF -from compas.files import URDFElement -from compas.files import URDFParser -from compas.geometry import Frame -from compas.geometry import Transformation -from compas.topology import shortest_path - -from compas.robots import Configuration -from compas.robots import DefaultMeshLoader -from compas.robots import LocalPackageMeshLoader - -from .base import _attr_from_data -from .base import _attr_to_data -from .geometry import Color -from .geometry import Geometry -from .geometry import Material -from .geometry import MeshDescriptor -from .geometry import Texture -from .joint import Axis -from .joint import Joint -from .joint import Limit -from .link import Collision -from .link import Link -from .link import Visual - - -class RobotModel(Data): - """RobotModel is the root element of the model. - - Instances of this class represent an entire robot as defined in an URDF - structure. - - In line with URDF limitations, only tree structures can be represented by - this model, ruling out all parallel robots. - - Attributes - ---------- - name : str - Unique name of the robot. - joints : list[:class:`~compas.robots.Joint`] - List of joint elements. - links : list[:class:`~compas.robots.Link`] - List of links of the robot. - materials : list[:class:`~compas.robots.Material`] - List of global materials. - root : :class:`~compas.robots.Link` - Root link of the model. - attr : dict - Non-standard attributes. - - """ - - def __init__(self, name=None, joints=(), links=(), materials=(), **kwargs): - super(RobotModel, self).__init__() - self.name = name or "Robot" - self.joints = list(joints or []) - self.links = list(links or []) - self.materials = list(materials or []) - self.attr = kwargs - self.root = None - self._rebuild_tree() - self._create(self.root, Transformation()) - self._scale_factor = 1.0 - - def get_urdf_element(self): - attributes = {"name": self.name} - attributes.update(self.attr) - elements = self.links + self.joints + self.materials - return URDFElement("robot", attributes, elements) - - @property - def data(self): - """Returns the data dictionary that represents the :class:`RobotModel`. - - Returns - ------- - dict - The RobotModel's data. - - """ - return self._get_data() - - def _get_data(self): - return { - "name": self.name, - "joints": [joint.data for joint in self.joints], - "links": [link.data for link in self.links], - "materials": [material.data for material in self.materials], - "attr": _attr_to_data(self.attr), - "_scale_factor": self._scale_factor, - } - - @data.setter - def data(self, data): - self._set_data(data) - - def _set_data(self, data): - self.name = data.get("name", "") - self.joints = [Joint.from_data(d) for d in data.get("joints", [])] - self.links = [Link.from_data(d) for d in data.get("links", [])] - self.materials = [Material.from_data(d) for d in data.get("materials", [])] - self.attr = _attr_from_data(data.get("attr", {})) - self._scale_factor = data.get("_scale_factor", 1.0) - - self._rebuild_tree() - - def _rebuild_tree(self): - """Store tree structure from link and joint lists.""" - self._adjacency = dict() - self._links = dict() - self._joints = dict() - - for link in self.links: - link.joints = self.find_children_joints(link) - link.parent_joint = self.find_parent_joint(link) - - self._links[link.name] = link - self._adjacency[link.name] = [joint.name for joint in link.joints] - - if not link.parent_joint: - self.root = link - - for joint in self.joints: - child_name = joint.child.link - joint.child_link = self.get_link_by_name(child_name) - - self._joints[joint.name] = joint - self._adjacency[joint.name] = [child_name] - - @classmethod - def from_urdf_file(cls, file): - """Construct a robot model from a URDF file model description. - - Parameters - ---------- - file : str | file - File path or file-like object. - - Returns - ------- - :class:`~compas.robots.RobotModel` - A robot model instance. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> print(robot) - Robot name=ur5, Links=11, Joints=10 (6 configurable) - - """ - urdf = URDF.from_file(file) - return urdf.robot - - def to_urdf_file(self, file, prettify=False): - """Construct a URDF file model description from a robot model. - - Parameters - ---------- - file : str | file - File path or file-like object. - - Returns - ------- - None - - """ - urdf = URDF.from_robot(self) - urdf.to_file(file, prettify) - - @classmethod - def from_urdf_string(cls, text): - """Construct a robot model from a URDF description as string. - - Parameters - ---------- - text : str - String containing the XML URDF model. - - Returns - ------- - :class:`~compas.robots.RobotModel` - A robot model instance. - - Examples - -------- - >>> urdf_string = '' - >>> robot = RobotModel.from_urdf_string(urdf_string) - - """ - urdf = URDF.from_string(text) - return urdf.robot - - @classmethod - def ur5(cls, load_geometry=False): - """ "Construct a UR5 robot model. - - Parameters - ---------- - load_geometry : bool, optional - Indicate whether to load the geometry of the robot or not. - - Returns - ------- - :class:`~compas.robots.RobotModel` - A robot model instance. - - """ - model = cls.from_urdf_file(compas.get("ur_description/urdf/ur5.urdf")) - if load_geometry: - loader = LocalPackageMeshLoader(compas.DATA, "ur_description") - model.load_geometry(loader) - return model - - def to_urdf_string(self, prettify=False): - """Construct a URDF string model description from a robot model. - - Parameters - ---------- - prettify : bool, optional - If True, the string will be pretty-printed. - - Returns - ------- - str - URDF string. - - """ - urdf = URDF.from_robot(self) - return urdf.to_string(prettify=prettify) - - def find_children_joints(self, link): - """Returns a list of all children joints of the link.""" - joints = [] - for joint in self.joints: - if str(joint.parent) == link.name: - joints.append(joint) - return joints - - def find_parent_joint(self, link): - """Returns the parent joint of the link or None if not found. - - Parameters - ---------- - link : :class:`~compas.robots.Link` - The link of which we want to know the parent joint. - - Returns - ------- - :class:`~compas.robots.Joint` - The parent joint of the link. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> j = robot.find_parent_joint(Link('shoulder_link')) - >>> j.name - 'shoulder_pan_joint' - - """ - for joint in self.joints: - if str(joint.child) == link.name: - return joint - return None - - def get_link_by_name(self, name): - """Get a link in a robot model matching by its name. - - Parameters - ---------- - name : str - Link name. - - Returns - ------- - :class:`~compas.robots.Link` - A link instance. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> l = robot.get_link_by_name('world') - >>> l.name - 'world' - - """ - return self._links.get(name, None) - - def get_joint_by_name(self, name): - """Get a joint in a robot model matching by its name. - - Parameters - ---------- - name : str - Joint name. - - Returns - ------- - :class:`Joint` - A joint instance. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> j = robot.get_joint_by_name('shoulder_lift_joint') - >>> j.name - 'shoulder_lift_joint' - - """ - return self._joints.get(name, None) - - def iter_links(self): - """Iterator over the links that starts with the root link. - - Returns - ------- - Iterator of all links starting at root. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> [l.name for l in robot.iter_links()] - ['world', 'base_link', 'shoulder_link', 'upper_arm_link', 'forearm_link', 'wrist_1_link', 'wrist_2_link', 'wrist_3_link', 'ee_link', 'tool0', 'base'] - - """ - - def func(cjoints, links): - for j in cjoints: - link = j.child_link - links.append(link) - links += func(link.joints, []) - return links - - return iter(func(self.root.joints, [self.root])) - - def iter_joints(self): - """Iterator over the joints that starts with the root link's children joints. - - Returns - ------- - Iterator of all joints starting at root. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> [j.name for j in robot.iter_joints()] - ['world_joint', 'shoulder_pan_joint', 'base_link-base_fixed_joint', 'shoulder_lift_joint', \ - 'elbow_joint', 'wrist_1_joint', 'wrist_2_joint', 'wrist_3_joint', 'ee_fixed_joint', 'wrist_3_link-tool0_fixed_joint'] - - """ - - def func(clink, joints): - cjoints = clink.joints - joints += cjoints - for j in cjoints: - joints += func(j.child_link, []) - return joints - - return iter(func(self.root, [])) - - def iter_link_chain(self, link_start_name=None, link_end_name=None): - """Iterator over the chain of links between a pair of start and end links. - - Parameters - ---------- - link_start_name : str, optional - Name of the starting link of the chain. Defaults to the root link name. - link_end_name : str, optional - Name of the final link of the chain. Defaults to the last link's name. - - Returns - ------- - Iterator of the chain of links. - - Notes - ----- - This method differs from :meth:`iter_links` in that it returns the chain respecting - the tree structure, hence if one link branches into two or more joints, only the - branch matching the end link will be returned. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> [l.name for l in robot.iter_link_chain('world', 'forearm_link')] - ['world', 'base_link', 'shoulder_link', 'upper_arm_link', 'forearm_link'] - - """ - chain = self.iter_chain(link_start_name, link_end_name) - for link in map(self.get_link_by_name, chain): - # If None, it's not a link - if link: - yield link - - def iter_joint_chain(self, link_start_name=None, link_end_name=None): - """Iterator over the chain of joints between a pair of start and end links. - - Parameters - ---------- - link_start_name : str, optional - Name of the starting link of the chain. Defaults to the root link name. - link_end_name : str, optional - Name of the final link of the chain. Defaults to the last link's name. - - Returns - ------- - Iterator of the chain of joints. - - Notes - ----- - This method differs from :meth:`iter_joints` in that it returns the - chain respecting the tree structure, hence if one link branches into - two or more joints, only the branch matching the end link will be - returned. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> [j.name for j in robot.iter_joint_chain('world', 'forearm_link')] - ['world_joint', 'shoulder_pan_joint', 'shoulder_lift_joint', 'elbow_joint'] - - """ - chain = self.iter_chain(link_start_name, link_end_name) - for joint in map(self.get_joint_by_name, chain): - # If None, it's not a joint - if joint: - yield joint - - def iter_chain(self, start=None, end=None): - """Iterator over the chain of all elements between a pair of start and end elements. - - Parameters - ---------- - start : str, optional - Name of the starting element of the chain. Defaults to the root link name. - end : str, optional - Name of the final element of the chain. Defaults to the name of the last element. - - Returns - ------- - Iterator of the chain of links and joints (names). - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> list(robot.iter_chain('world', 'forearm_link')) - ['world', 'world_joint', 'base_link', 'shoulder_pan_joint', 'shoulder_link', 'shoulder_lift_joint', 'upper_arm_link', 'elbow_joint', 'forearm_link'] - - """ - if not start: - if not self.root: - raise Exception("No root link found") - start = self.root.name - - if not end: - # Normally URDF will contain the end links at the end - # so we break out faster by reversing the list - for link in reversed(self.links): - if not link.joints: - end = link.name - break - - shortest_chain = shortest_path(self._adjacency, start, end) - - if not shortest_chain: - raise Exception("No chain found between the specified element") - - for name in shortest_chain: - yield name - - def get_configurable_joints(self): - """Returns the configurable joints. - - Configurable joints are those that are not FIXED or MIMIC. - - Returns - ------- - list[:class:`~compas.robots.Joint`] - List of configurable joints. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> joints = robot.get_configurable_joints() - >>> [j.name for j in joints] - ['shoulder_pan_joint', 'shoulder_lift_joint', 'elbow_joint', 'wrist_1_joint', 'wrist_2_joint', 'wrist_3_joint'] - - """ - joints = self.iter_joints() - return [joint for joint in joints if joint.is_configurable()] - - def get_joint_types(self): - """Returns the joint types of the configurable joints. - - Returns - ------- - list[int] - List of joint types in the robot model. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> robot.get_joint_types() == [Joint.REVOLUTE] * 6 - True - - """ - joints = self.get_configurable_joints() - return [joint.type for joint in joints] - - def get_joint_types_by_names(self, names): - """Get a list of joint types given a list of joint names. - - Parameters - ---------- - names : list[str] - The names of the joints. - - Returns - ------- - list[:attr:`compas.robots.Joint.SUPPORTED_TYPES`] - List of joint types. - - """ - return [self.get_joint_by_name(n).type for n in names] - - def get_configurable_joint_names(self): - """Returns the configurable joint names. - - Returns - ------- - list[str] - List of configurable joint names. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> robot.get_configurable_joint_names() - ['shoulder_pan_joint', 'shoulder_lift_joint', 'elbow_joint', 'wrist_1_joint', 'wrist_2_joint', 'wrist_3_joint'] - - """ - joints = self.get_configurable_joints() - return [j.name for j in joints] - - def get_end_effector_link(self): - """Returns the end effector link. - - Returns - ------- - :class:`~compas.robots.Link` - Instance of the end effector link. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> link = robot.get_end_effector_link() - >>> link.name - 'ee_link' - - """ - joints = self.get_configurable_joints() - clink = joints[-1].child_link - for j in clink.joints: - if j.type == Joint.FIXED: - return j.child_link - return clink - - def get_end_effector_link_name(self): - """Returns the name of the end effector link. - - Returns - ------- - str - Name of the end effector link - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> robot.get_end_effector_link_name() - 'ee_link' - - """ - link = self.get_end_effector_link() - return link.name - - def get_base_link_name(self): - """Returns the name of the base link. - - Returns - ------- - str - Name of the base link - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> robot.get_base_link_name() - 'base_link' - - """ - joints = self.get_configurable_joints() - return joints[0].parent.link - - def zero_configuration(self): - """Get the zero joint configuration. - - If zero is out of joint limits ``(upper, lower)`` then - ``(upper + lower) / 2`` is used as joint value. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> robot.zero_configuration() - Configuration((0.000, 0.000, 0.000, 0.000, 0.000, 0.000), (0, 0, 0, 0, 0, 0), \ - ('shoulder_pan_joint', 'shoulder_lift_joint', 'elbow_joint', 'wrist_1_joint', 'wrist_2_joint', 'wrist_3_joint')) - - """ - values = [] - joint_names = [] - joint_types = [] - for joint in self.get_configurable_joints(): - if joint.limit and not (0 <= joint.limit.upper and 0 >= joint.limit.lower): - values.append((joint.limit.upper + joint.limit.lower) / 2.0) - else: - values.append(0) - joint_names.append(joint.name) - joint_types.append(joint.type) - return Configuration(values, joint_types, joint_names) - - def random_configuration(self): - """Get a random configuration. - - Returns - ------- - :class:`~compas.robots.Configuration` - Instance of a configuration with randomized joint values. - - Notes - ----- - No collision checking is involved, the configuration may be invalid. - - """ - configurable_joints = self.get_configurable_joints() - values = [] - for joint in configurable_joints: - if joint.limit: - values.append(joint.limit.lower + (joint.limit.upper - joint.limit.lower) * random.random()) - else: - values.append(0) - joint_names = self.get_configurable_joint_names() - joint_types = self.get_joint_types_by_names(joint_names) - return Configuration(values, joint_types, joint_names) - - def load_geometry(self, *resource_loaders, **kwargs): - """Load external geometry resources, such as meshes. - - Parameters - ---------- - resource_loaders : :class:`~compas.robots.AbstractMeshLoader` - List of objects that implement the - resource loading interface (:class:`~compas.robots.AbstractMeshLoader`) - and can retrieve external geometry. - force : bool - True if it should force reloading even if the geometry - has been loaded already, otherwise False. - - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> robot.load_geometry(LocalPackageMeshLoader(compas.DATA, 'ur_description')) - >>> print(robot) - Robot name=ur5, Links=11, Joints=10 (6 configurable) - - """ - force = kwargs.get("force", False) - - loaders = list(resource_loaders) - loaders.insert(0, DefaultMeshLoader()) - - for link in self.links: - for element in itertools.chain(link.collision, link.visual): - shape = element.geometry.shape - needs_reload = force or not shape.meshes - if "filename" in dir(shape) and needs_reload: - for loader in loaders: - if loader.can_load_mesh(shape.filename): - # NOTE: this part is annoying, but we keep it for backwards compatibility's sake. - # Externally defined loaders (eg. COMPAS_FAB File Server loader) - # might not be updated yet on the user's system, so we fallback - # to the deprecated load_mesh method in that case. - # However, to add to the confusion, some loaders were actually returning - # meshes regardless of the misnaming. We handle that in _get_item_meshes - # so, we don't force load_mesh into a list, otherwise it will turn into a - # list of lists in those cases. - # All of this ugly fallback should be removed in 2.0 - if hasattr(loader, "load_meshes"): - shape.meshes = loader.load_meshes(shape.filename) - else: - shape.meshes = loader.load_mesh(shape.filename) - break - - if not shape.meshes: - raise Exception("Unable to load meshes for {}".format(shape.filename)) - - def ensure_geometry(self): - """Check if geometry has been loaded. - - Raises - ------ - :exc:`Exception` - If geometry has not been loaded. - - """ - for link in self.links: - for element in itertools.chain(link.collision, link.visual): - shape = element.geometry.shape - if not shape.meshes: - raise Exception("This method is only callable once the geometry has been loaded.") - - @property - def frames(self): - """Returns the frames of links that have a visual node. - - Returns - ------- - list[:class:`~compas.geometry.Frame`] - Reference frames of all links with a visual representation. - - """ - frames = [] - for link in self.iter_links(): - if len(link.visual) and link.parent_joint: - frames.append(link.parent_joint.current_origin.copy()) - return frames - - @property - def axes(self): - """Returns the joints' axes. - - Returns - ------- - list[:class:`Vector`] - Axis vectors of all joints. - - """ - axes = [] - for joint in self.iter_joints(): - axes.append(joint.current_axis.vector) - return axes - - def __str__(self): - """Generate a readable representation of the robot.""" - return "Robot name={}, Links={}, Joints={} ({} configurable)".format( - self.name, - len(self.links), - len(self.joints), - len(self.get_configurable_joints()), - ) - - def _create(self, link, parent_transformation): - """Private function called during initialization to transform origins and axes. - - Parameters - ---------- - link : :class:`~compas.robots.Link` - Link instance to create. - parent_transformation : :class:`Transformation` - Parent transformation to apply to the link when creating the structure. - - """ - if link is None: # some urdfs would fail here otherwise - return - - for item in itertools.chain(link.visual, link.collision): - if item.origin: - # transform visual or collision geometry with the transformation specified in origin - transformation = Transformation.from_frame(item.origin) - item.init_transformation = parent_transformation * transformation - else: - item.init_transformation = parent_transformation - - for child_joint in link.joints: - child_joint._create(parent_transformation) - # Recursively call creation - self._create(child_joint.child_link, child_joint.current_transformation) - - def scale(self, factor, link=None): - """Scales the robot by factor (absolute). - - Parameters - ---------- - factor : float - The factor to scale the robot with. - link : :class:`~compas.robots.Link`, optional - Starting link from which to start scaling. - Defaults to root. - - Returns - ------- - None - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> robot.scale(100) - - """ - if not link or link == self.root: - link = self.root - relative_factor = factor / self._scale_factor # relative scaling factor - else: - relative_factor = factor - - for child_joint in link.joints: - child_joint.scale(relative_factor) - # Recursive call - self.scale(relative_factor, child_joint.child_link) - - self._scale_factor = factor - - def compute_transformations(self, joint_state, link=None, parent_transformation=None): - """Recursive function to calculate the transformations of each joint. - - Parameters - ---------- - joint_state : :class:`~compas.robots.Configuration` | dict[str, float] - A configuration instance or a dictionary with joint names and joint values in radians and - meters (depending on the joint type). - link : :class:`~compas.robots.Link`, optional - Link instance to calculate the child joint's transformation. - parent_transformation : :class:`Transformation`, optional - The transfomation of the parent joint. - Defaults to the identity matrix. - - Returns - ------- - dict[str, :class:`Transformation`] - A dictionary with the joint names as keys and values are the joint's respective transformation. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> config = robot.random_configuration() - >>> transformations = robot.compute_transformations(config) - - """ - if link is None: - link = self.root - if parent_transformation is None: - parent_transformation = Transformation() - - transformations = {} - - for child_joint in link.joints: - if ( - child_joint.name in joint_state.keys() - ): # if passive/mimicking joint is in the joint_state, the transformation will be calculated according to this value - position = joint_state[child_joint.name] - transformation = parent_transformation * child_joint.calculate_transformation(position) - elif child_joint.mimic and child_joint.mimic.joint in joint_state.keys(): - mimicked_joint_position = joint_state[child_joint.mimic.joint] - position = child_joint.mimic.calculate_position(mimicked_joint_position) - transformation = parent_transformation * child_joint.calculate_transformation(position) - else: - transformation = parent_transformation - transformations.update({child_joint.name: transformation}) - # call function on child - transformations.update(self.compute_transformations(joint_state, child_joint.child_link, transformation)) - - return transformations - - def transformed_frames(self, joint_state): - """Returns the transformed frames based on the joint_state. - - Parameters - ---------- - joint_state : :class:`~compas.robots.Configuration` | dict[str, float] - A configuration instance or a dictionary with joint names and joint values in radians and - meters (depending on the joint type). - - Returns - ------- - list[:class:`Frame`] - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> config = robot.zero_configuration() - >>> config['shoulder_pan_joint'] = 1.2 - >>> config['wrist_2_joint'] = 0.5 - >>> ft = robot.transformed_frames(config) - >>> ft[1] - Frame(Point(0.000, 0.000, 0.089), Vector(0.362, 0.932, 0.000), Vector(-0.932, 0.362, 0.000)) - - """ - transformations = self.compute_transformations(joint_state) - return [j.current_origin.transformed(transformations[j.name]) for j in self.iter_joints()] - - def transformed_axes(self, joint_state): - """Returns the transformed axes based on the joint_state. - - Parameters - ---------- - joint_state : :class:`~compas.robots.Configuration` | dict[str, float] - A configuration instance or a dictionary with joint names and joint values in radians and - meters (depending on the joint type). - - Returns - ------- - list[:class:`Vector`] - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> config = robot.zero_configuration() - >>> config['shoulder_pan_joint'] = 1.2 - >>> config['wrist_2_joint'] = 0.5 - >>> at = robot.transformed_axes(config) - >>> at[3] - Vector(-0.932, 0.362, 0.000) - - """ - transformations = self.compute_transformations(joint_state) - return [ - j.current_axis.transformed(transformations[j.name]) - for j in self.iter_joints() - if j.current_axis.vector.length - ] - - def forward_kinematics(self, joint_state, link_name=None): - """Calculate the robot's forward kinematic. - - Parameters - ---------- - joint_state : :class:`~compas.robots.Configuration` | dict[str, float] - A configuration instance or a dictionary with joint names and joint values in radians and - meters (depending on the joint type). - link_name : str, optional - The name of the link we want to calculate the forward kinematics for. - Defaults to the end-effector link name. - - Returns - ------- - :class:`Frame` - The frame at the end-effector link in the world coordinate system. - - Examples - -------- - >>> robot = RobotModel.ur5() - >>> config = robot.zero_configuration() - >>> robot.forward_kinematics(config) - Frame(Point(0.817, 0.191, -0.005), Vector(-0.000, 1.000, 0.000), Vector(1.000, 0.000, 0.000)) - - """ - if link_name is None: - ee_link = self.get_end_effector_link() - else: - ee_link = self.get_link_by_name(link_name) - joint = ee_link.parent_joint - if joint: - transformations = self.compute_transformations(joint_state) - return joint.current_origin.transformed(transformations[joint.name]) - else: - return Frame.worldXY() # if we ask forward from base link - - @staticmethod - def _consolidate_meshes(meshes, key, **kwargs): - meshes = meshes or [] - mesh = kwargs.get(key) - if mesh: - meshes.append(mesh) - del kwargs[key] - return meshes, kwargs - - def _check_link_name(self, name): - all_link_names = [l.name for l in self.links] # noqa: E741 - if name in all_link_names: - raise ValueError("Link name '%s' already used in chain." % name) - - def add_link(self, name, visual_meshes=None, visual_color=None, collision_meshes=None, **kwargs): - """Adds a link to the robot model. - - Provides an easy way to programmatically add a link to the robot model. - - Parameters - ---------- - name : str - The name of the link - visual_meshes : list[:class:`~compas.datastructures.Mesh` | :class:`~compas.geometry.Shape`], optional - The link's visual mesh. - visual_color : [float, float, float], optional - The rgb color of the mesh. - Defaults to (0.8, 0.8, 0.8) - collision_meshes : list[:class:`~compas.datastructures.Mesh`], optional - The link's collision mesh. - **kwargs : dict[str, Any], optional - The keyword arguments (kwargs) collected in a dict. - These allow using non-standard attributes absent in the URDF specification. - - Returns - ------- - :class:`Link` - The created `Link` - - Raises - ------ - ValueError - If the link name is already used in the chain. - - Examples - -------- - >>> from compas.datastructures import Mesh - >>> from compas.geometry import Sphere - >>> sphere = Sphere((0, 0, 0), 1) - >>> mesh = Mesh.from_shape(sphere) - >>> robot = RobotModel('robot') - >>> link = robot.add_link('link0', visual_mesh=mesh) - - """ - self._check_link_name(name) - visual_meshes, kwargs = self._consolidate_meshes(visual_meshes, "visual_mesh", **kwargs) - collision_meshes, kwargs = self._consolidate_meshes(collision_meshes, "collision_mesh", **kwargs) - if not visual_color: - visual_color = (0.8, 0.8, 0.8) - - visuals = [] - collisions = [] - - for visual in visual_meshes: - if isinstance(visual, Mesh): - v = Visual(Geometry(MeshDescriptor(""))) - v.geometry.shape.meshes = [visual] - else: - v = Visual.from_primitive(visual) - v.material = Material(color=Color("%f %f %f 1" % visual_color)) - visuals.append(v) - - for collision in collision_meshes: # use visual_mesh as collision_mesh if none passed? - if isinstance(collision, Mesh): - c = Collision(Geometry(MeshDescriptor(""))) - c.geometry.shape.meshes = [collision] - else: - c = Collision.from_primitive(collision) - collisions.append(c) - - link = Link(name, visual=visuals, collision=collisions, **kwargs) - self.links.append(link) - # Must build the tree structure, if adding the first link to an empty robot - if len(self.links) == 1: - self._rebuild_tree() - self._create(self.root, Transformation()) - return link - - def remove_link(self, name): - """Removes a link to the robot model. - - Provides an easy way to programmatically remove a link from the robot model. - - Parameters - ---------- - name : str - The name of the link - - """ - self.links = [link for link in self.links if link.name != name] - - def add_joint(self, name, type, parent_link, child_link, origin=None, axis=None, limit=None, **kwargs): - """Adds a joint to the robot model. - - Provides an easy way to programmatically add a joint to the robot model. - - Parameters - ---------- - name : str - The name of the joint - type : int - The joint type, e.g. Joint.REVOLUTE - parent_link : :class:`Link` - The joint's parent link. - child_link : :class:`Link` - The joint's child link. - origin : :class:`~compas.geometry.Frame` - The joint's origin frame. - axis : :class:`~compas.geometry.Vector` - The joint's axis. - limit : list of 2 float - The lower and upper limits of the joint (used for joint types Joint.REVOLUTE or Joint.PRISMATIC) - **kwargs : dict[str, Any], optional - The keyword arguments (kwargs) collected in a dict. - These allow using non-standard attributes absent in the URDF specification. - - Returns - ------- - :class:`Joint` - The created `Joint` - - Raises - ------ - ValueError - If the joint name is already used in the chain. - - Examples - -------- - >>> from compas.geometry import Frame - >>> robot = RobotModel('robot') - >>> parent_link = robot.add_link('link0') - >>> child_link = robot.add_link('link1') - >>> origin = Frame.worldXY() - >>> axis = (1, 0, 0) - >>> j = robot.add_joint("joint1", Joint.CONTINUOUS, parent_link, child_link, origin, axis) - - """ - all_joint_names = [j.name for j in self.joints] - if name in all_joint_names: - raise ValueError("Joint name '%s' already used in chain." % name) - - if origin: - origin = Frame(origin.point, origin.xaxis, origin.yaxis) - if axis: - axis = Axis("{} {} {}".format(*list(axis))) - if limit: - lower, upper = limit - limit = Limit(lower=lower, upper=upper) - - type_str = Joint.SUPPORTED_TYPES[type] - - joint = Joint( - name, type_str, parent_link.name, child_link.name, origin=origin, axis=axis, limit=limit, **kwargs - ) - - self.joints.append(joint) - - # Using only part of self._rebuild_tree() - parent_link.joints.append(joint) - child_link.parent_joint = joint - - self._links[parent_link.name] = parent_link - self._adjacency[parent_link.name] = [joint.name for joint in parent_link.joints] - self._links[child_link.name] = child_link - - if not parent_link.parent_joint: - self.root = parent_link - - joint.child_link = child_link - self._joints[joint.name] = joint - self._adjacency[joint.name] = [child_link.name] - - self._create(self.root, Transformation()) - - return joint - - def remove_joint(self, name): - """Removes a joint to the robot model. - - Provides an easy way to programmatically remove a joint from the robot model. - - Parameters - ---------- - name : str - The name of the joint - - Returns - ------- - None - - """ - joint = self.get_joint_by_name(name) - self.joints = [j for j in self.joints if j.name != name] - parent_link = self.get_link_by_name(joint.parent.link) - parent_link.joints = [j for j in parent_link.joints if j.name != name] - self._adjacency[parent_link.name] = [j.name for j in parent_link.joints] - del self._links[joint.child.link] - del self._joints[name] - del self._adjacency[name] - - -URDFParser.install_parser(RobotModel, "robot") -URDFParser.install_parser(Material, "robot/material") -URDFParser.install_parser(Color, "robot/material/color") -URDFParser.install_parser(Texture, "robot/material/texture") diff --git a/src/compas/robots/model/tool.py b/src/compas/robots/model/tool.py deleted file mode 100644 index 47969ea89d0..00000000000 --- a/src/compas/robots/model/tool.py +++ /dev/null @@ -1,180 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from compas.geometry import Frame -from compas.geometry import Transformation - -from .robot import RobotModel - - -class ToolModel(RobotModel): - """Represents a tool to be attached to the robot's flange. - - Attributes - ---------- - visual : :class:`~compas.datastructures.Mesh` - The visual mesh of the tool. - frame : :class:`~compas.geometry.Frame` - The frame of the tool in tool0 frame. - collision : :class:`~compas.datastructures.Mesh` - The collision mesh representation of the tool. - name : str - The name of the `ToolModel`. Defaults to 'attached_tool'. - link_name : str - The name of the `Link` to which the tool is attached. Defaults to ``None``. - - Examples - -------- - >>> import compas - >>> from compas.datastructures import Mesh - >>> from compas.geometry import Frame - >>> mesh = Mesh.from_stl(compas.get('cone.stl')) - >>> frame = Frame([0.14, 0, 0], [0, 1, 0], [0, 0, 1]) - >>> tool = ToolModel(mesh, frame) - - """ - - def __init__( - self, - visual, - frame_in_tool0_frame, - collision=None, - name="attached_tool", - link_name=None, - ): - collision = collision or visual - super(ToolModel, self).__init__(name) - self.add_link("attached_tool_link", visual_mesh=visual, collision_mesh=collision) - - self._rebuild_tree() - self._create(self.root, Transformation()) - - self.frame = frame_in_tool0_frame - self.link_name = link_name - - @classmethod - def from_robot_model(cls, robot, frame_in_tool0_frame, link_name=None): - """Creates a ``ToolModel`` from a :class:`~compas.robots.RobotModel` instance. - - Parameters - ---------- - robot : :class:`~compas.robots.RobotModel` - frame_in_tool0_frame : str - The frame of the tool in tool0 frame. - link_name : str - The name of the `Link` to which the tool is attached. - Defaults to ``None``. - - """ - data = robot.data - data["frame"] = frame_in_tool0_frame.data - data["link_name"] = link_name - return cls.from_data(data) - - @property - def data(self): - """Returns the data dictionary that represents the tool. - - Returns - ------- - dict - The tool data. - - """ - return self._get_data() - - def _get_data(self): - data = super(ToolModel, self)._get_data() - data["frame"] = self.frame.data - data["link_name"] = self.link_name - return data - - @data.setter - def data(self, data): - self._set_data(data) - - def _set_data(self, data): - super(ToolModel, self)._set_data(data) - self.frame = Frame.from_data(data["frame"]) - self.name = self.name or "attached_tool" - self.link_name = data["link_name"] if "link_name" in data else None - - @classmethod - def from_data(cls, data): - """Construct a `ToolModel` from its data representation. - - To be used in conjunction with the :meth:`to_data` method. - - Parameters - ---------- - data : dict - The data dictionary. - - Returns - ------- - :class:`ToolModel` - The constructed `ToolModel`. - - """ - tool = cls(None, None) - tool.data = data - return tool - - def from_tcf_to_t0cf(self, frames_tcf): - """Converts a list of frames at the robot's tool tip (tcf frame) to frames at the robot's flange (tool0 frame). - - Parameters - ---------- - frames_tcf : list[:class:`~compas.geometry.Frame`] - Frames (in WCF) at the robot's tool tip (tcf). - - Returns - ------- - list[:class:`~compas.geometry.Frame`] - Frames (in WCF) at the robot's flange (tool0). - - Examples - -------- - >>> import compas - >>> from compas.datastructures import Mesh - >>> from compas.geometry import Frame - >>> mesh = Mesh.from_stl(compas.get('cone.stl')) - >>> frame = Frame([0.14, 0, 0], [0, 1, 0], [0, 0, 1]) - >>> tool = ToolModel(mesh, frame) - >>> frames_tcf = [Frame((-0.309, -0.046, -0.266), (0.276, 0.926, -0.256), (0.879, -0.136, 0.456))] - >>> tool.from_tcf_to_t0cf(frames_tcf) - [Frame(Point(-0.363, 0.003, -0.147), Vector(0.388, -0.351, -0.852), Vector(0.276, 0.926, -0.256))] - - """ - Te = Transformation.from_frame_to_frame(self.frame, Frame.worldXY()) - return [Frame.from_transformation(Transformation.from_frame(f) * Te) for f in frames_tcf] - - def from_t0cf_to_tcf(self, frames_t0cf): - """Converts frames at the robot's flange (tool0 frame) to frames at the robot's tool tip (tcf frame). - - Parameters - ---------- - frames_t0cf : list[:class:`~compas.geometry.Frame`] - Frames (in WCF) at the robot's flange (tool0). - - Returns - ------- - list[:class:`~compas.geometry.Frame`] - Frames (in WCF) at the robot's tool tip (tcf). - - Examples - -------- - >>> import compas - >>> from compas.datastructures import Mesh - >>> from compas.geometry import Frame - >>> mesh = Mesh.from_stl(compas.get('cone.stl')) - >>> frame = Frame([0.14, 0, 0], [0, 1, 0], [0, 0, 1]) - >>> tool = ToolModel(mesh, frame) - >>> frames_t0cf = [Frame((-0.363, 0.003, -0.147), (0.388, -0.351, -0.852), (0.276, 0.926, -0.256))] - >>> tool.from_t0cf_to_tcf(frames_t0cf) - [Frame(Point(-0.309, -0.046, -0.266), Vector(0.276, 0.926, -0.256), Vector(0.879, -0.136, 0.456))] - - """ - Te = Transformation.from_frame_to_frame(Frame.worldXY(), self.frame) - return [Frame.from_transformation(Transformation.from_frame(f) * Te) for f in frames_t0cf] diff --git a/src/compas/robots/resources/__init__.py b/src/compas/robots/resources/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/compas/robots/resources/basic.py b/src/compas/robots/resources/basic.py deleted file mode 100644 index 24da80cb33b..00000000000 --- a/src/compas/robots/resources/basic.py +++ /dev/null @@ -1,307 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os - -try: - from urllib.parse import urlparse -except ImportError: - from urlparse import urlparse - -from compas.datastructures import Mesh - -SUPPORTED_FORMATS = ("obj", "stl", "ply") - - -class AbstractMeshLoader(object): - """Basic contract/interface for all mesh loaders.""" - - def can_load_mesh(self, url): - """Determine whether this loader can load a given Mesh URL. - - Parameters - ---------- - url : str - Mesh URL. - - Returns - ------- - bool - ``True`` if it can handle it, otherwise ``False``. - """ - return NotImplementedError - - def load_mesh(self, url): - """Load the mesh from the given URL. - - .. deprecated:: 1.13.3 - Use :meth:`load_meshes` instead. - - Parameters - ---------- - url : str - Mesh URL - - Returns - ------- - :class:`~compas.datastructures.Mesh` - Instance of a mesh. - """ - return NotImplementedError - - def load_meshes(self, url): - """Load meshes from the given URL. - - A single mesh file can contain multiple meshes depending on the format. - - Parameters - ---------- - url : str - Mesh URL - - Returns - ------- - list[:class:`~compas.datastructures.Mesh`] - List of meshes. - """ - return NotImplementedError - - -class DefaultMeshLoader(AbstractMeshLoader): - """Handles basic mesh loader tasks, mostly from local files. - - Attributes - ---------- - kwargs (optional): dict - Additional keyword arguments. - """ - - def __init__(self, **kwargs): - super(DefaultMeshLoader, self).__init__() - self.attr = kwargs or dict() - - def can_load_mesh(self, url): - """Determine whether this loader can load a given mesh URL. - - Parameters - ---------- - url : str - Mesh URL. - - Returns - ------- - bool - ``True`` if the URL points to a local and valid file. - Otherwise ``False``. - """ - - url = self._get_mesh_url(url) - scheme = urlparse(url).scheme - - # Local files have either: - # - no scheme - # - a one-letter scheme in Windows - # - file scheme - is_local_file = len(scheme) in (0, 1) or scheme == "file" - - if is_local_file: - if os.path.isfile(url): - return True - - # Only OBJ loader supports remote files atm - is_obj = _get_file_format(url) == "obj" - return scheme in ("http", "https") and is_obj - - def load_mesh(self, url): - """Loads a mesh from local storage. - - .. deprecated:: 1.13.3 - Use :meth:`load_meshes` instead. - - Parameters - ---------- - url : str - Mesh location - - Returns - ------- - :class:`~compas.datastructures.Mesh` - Instance of a mesh. - """ - return self.load_meshes(url)[0] - - def load_meshes(self, url): - """Load meshes from the given URL. - - A single mesh file can contain multiple meshes depending on the format. - - Parameters - ---------- - url : str - Mesh URL - - Returns - ------- - list[:class:`~compas.datastructures.Mesh`] - List of meshes. - """ - url = self._get_mesh_url(url) - return _mesh_import(url, url) - - def _get_mesh_url(self, url): - """Concatenates basepath directory to URL only if defined in the keyword arguments. - It also strips out the scheme 'file:///' from the URL if present. - - Parameters - ---------- - url : str - Mesh location. - - Returns - ------- - url: str - Extended mesh url location if basepath in kwargs. - Else, it returns url. - """ - if url.startswith("file:///"): - url = url[8:] - - basepath = self.attr.get("basepath") - if basepath: - return os.path.join(basepath, url) - return url - - -def _get_file_format(url): - # This could be much more elaborate - # with an actual header check - # and/or remote content-type check - file_extension = url.split(".")[-1].lower() - return file_extension - - -class LocalPackageMeshLoader(AbstractMeshLoader): - """Loads suport package resources stored locally. - - Attributes - ---------- - path : str - Path where the package is stored locally. - support_package : str - Name of the support package containing URDF, Meshes - and additional assets, e.g. 'abb_irb4400_support' - """ - - def __init__(self, path, support_package): - super(LocalPackageMeshLoader, self).__init__() - self.path = path - self.support_package = support_package - self.schema_prefix = "package://" + self.support_package + "/" - - def build_path(self, *path_parts): - """Returns the building path. - - Parameters - ---------- - *path_parts: str - The additional foldernames that construct the path. - - Returns - ------- - str - """ - return os.path.join(self.path, self.support_package, *path_parts) - - def load_urdf(self, file): - """Load a URDF file from local storage. - - Parameters - ---------- - file : str - File name. Following convention, the file should reside - inside a ``urdf`` folder. - """ - - path = self.build_path("urdf", file) - return open(path, "r") - - def can_load_mesh(self, url): - """Determine whether this loader can load a given mesh URL. - - Parameters - ---------- - url : str - Mesh URL. - - Returns - ------- - bool - ``True`` if the URL uses the ``package://` scheme and the package name - matches the specified in the constructor and the file exists locally, - otherwise ``False``. - """ - if not url.startswith(self.schema_prefix): - return False - - local_file = self._get_local_path(url) - return os.path.isfile(local_file) - - def load_mesh(self, url): - """Loads a mesh from local storage. - - .. deprecated:: 1.13.3 - Use :meth:`load_meshes` instead. - - Parameters - ---------- - url : str - Mesh location - - Returns - ------- - :class:`~compas.datastructures.Mesh` - Instance of a mesh. - """ - return self.load_meshes(url)[0] - - def load_meshes(self, url): - """Load meshes from the given URL. - - A single mesh file can contain multiple meshes depending on the format. - - Parameters - ---------- - url : str - Mesh URL - - Returns - ------- - list[:class:`~compas.datastructures.Mesh`] - List of meshes. - """ - local_file = self._get_local_path(url) - return _mesh_import(url, local_file) - - def _get_local_path(self, url): - _prefix, path = url.split(self.schema_prefix) - return self.build_path(*path.split("/")) - - -def _mesh_import(name, file): - """Internal function to load meshes using the correct loader. - - Name and file might be the same but not always, e.g. temp files.""" - file_extension = _get_file_format(name) - - if file_extension not in SUPPORTED_FORMATS: - raise NotImplementedError("Mesh type not supported: {}".format(file_extension)) - - if file_extension == "obj": - return [Mesh.from_obj(file)] - elif file_extension == "stl": - return [Mesh.from_stl(file)] - elif file_extension == "ply": - return [Mesh.from_ply(file)] - - raise Exception diff --git a/src/compas/robots/resources/github.py b/src/compas/robots/resources/github.py deleted file mode 100644 index 0d7d5e979fb..00000000000 --- a/src/compas/robots/resources/github.py +++ /dev/null @@ -1,131 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -try: - from urllib.request import urlopen -except ImportError: - from urllib2 import urlopen - -from .basic import AbstractMeshLoader -from .basic import _mesh_import - - -class GithubPackageMeshLoader(AbstractMeshLoader): - """Loads resources stored in Github. - - Attributes - ---------- - repository : str - Repository name including organization, - e.g. ``ros-industrial/abb``. - support_package : str - Name of the support package containing URDF, Meshes - and additional assets, e.g. 'abb_irb4400_support' - branch : str - Branch name, defaults to ``main``. - relative_path : str - Relative path of the support package within the repository. - If the repository itself is the support package, set - ``relative_path`` to ``'.'``. Defaults to ``support_package`` - """ - - HOST = "https://raw.githubusercontent.com" - - def __init__(self, repository, support_package, branch="main", relative_path=None): - super(GithubPackageMeshLoader, self).__init__() - self.repository = repository - self.support_package = support_package - self.branch = branch - self.schema_prefix = "package://" + self.support_package + "/" - self.relative_path = support_package if relative_path is None else relative_path - - def build_url(self, file): - """Returns the corresponding url of the file. - - Parameters - ---------- - file : str - File name. Following convention, the file should reside - inside a ``urdf`` folder. - - Returns - ------- - str - The file's url. - """ - relative_path_component = None if self.relative_path == "." else self.relative_path - url_components = [ - GithubPackageMeshLoader.HOST, - self.repository, - self.branch, - relative_path_component, - file, - ] - return "/".join(filter(None, url_components)) - - def load_urdf(self, file): - """Load a URDF file from a Github support package repository. - - Parameters - ---------- - file : str - File name. Following convention, the file should reside - inside a ``urdf`` folder. - """ - url = self.build_url("urdf/{}".format(file)) - return urlopen(url) - - def can_load_mesh(self, url): - """Determine whether this loader can load a given mesh URL. - - Parameters - ---------- - url : str - Mesh URL. - - Returns - ------- - bool - ``True`` if the URL uses the ``package://` scheme and the package name - matches the specified in the constructor, otherwise ``False``. - """ - return url.startswith(self.schema_prefix) - - def load_mesh(self, url): - """Loads a mesh from a Github repository URL. - - .. deprecated:: 1.13.3 - Use :meth:`load_meshes` instead. - - Parameters - ---------- - url : str - Mesh location - - Returns - ------- - :class:`~compas.datastructures.Mesh` - Instance of a mesh. - """ - return self.load_meshes(url)[0] - - def load_meshes(self, url): - """Load meshes from the given URL. - - A single mesh file can contain multiple meshes depending on the format. - - Parameters - ---------- - url : str - Mesh URL - - Returns - ------- - list[:class:`~compas.datastructures.Mesh`] - List of meshes. - """ - _prefix, path = url.split(self.schema_prefix) - url = self.build_url(path) - - return _mesh_import(url, url) diff --git a/src/compas_blender/artists/__init__.py b/src/compas_blender/artists/__init__.py index 6199ef4e5a9..0b96344672d 100644 --- a/src/compas_blender/artists/__init__.py +++ b/src/compas_blender/artists/__init__.py @@ -23,7 +23,6 @@ from compas.datastructures import Mesh from compas.datastructures import Network from compas.datastructures import VolMesh -from compas.robots import RobotModel from .artist import BlenderArtist from .boxartist import BoxArtist @@ -41,7 +40,6 @@ from .polygonartist import PolygonArtist from .polyhedronartist import PolyhedronArtist from .polylineartist import PolylineArtist -from .robotmodelartist import RobotModelArtist from .sphereartist import SphereArtist from .surfaceartist import SurfaceArtist from .torusartist import TorusArtist @@ -76,7 +74,6 @@ def register_artists(): Artist.register(Polygon, PolygonArtist, context="Blender") Artist.register(Polyhedron, PolyhedronArtist, context="Blender") Artist.register(Polyline, PolylineArtist, context="Blender") - Artist.register(RobotModel, RobotModelArtist, context="Blender") Artist.register(Sphere, SphereArtist, context="Blender") Artist.register(Surface, SurfaceArtist, context="Blender") Artist.register(Torus, TorusArtist, context="Blender") @@ -102,7 +99,6 @@ def register_artists(): "PolygonArtist", "PolyhedronArtist", "PolylineArtist", - "RobotModelArtist", "SphereArtist", "SurfaceArtist", "TorusArtist", diff --git a/src/compas_blender/utilities/__init__.py b/src/compas_blender/utilities/__init__.py index c17aadc6236..76867f5c228 100644 --- a/src/compas_blender/utilities/__init__.py +++ b/src/compas_blender/utilities/__init__.py @@ -1,80 +1,3 @@ -""" -******************************************************************************** -utilities -******************************************************************************** - -.. currentmodule:: compas_blender.utilities - -This package contains low-level functions for working and interacting with Blender. - - -collections -=========== - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - create_collection - create_collections - create_collections_from_path - clear_collection - clear_collections - - -data -==== - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - delete_unused_data - - -drawing -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - draw_points - draw_pointcloud - draw_lines - draw_cylinders - draw_spheres - draw_cubes - draw_mesh - draw_faces - draw_texts - draw_curves - draw_surfaces - - -objects -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - delete_object - delete_objects - delete_object_by_name - delete_objects_by_names - get_object_by_name - get_objects_by_names - -misc -==== - -.. autosummary:: - :toctree: generated/ - - unload_modules - -""" from .data import delete_unused_data from .objects import ( delete_object, diff --git a/src/compas_ghpython/artists/__init__.py b/src/compas_ghpython/artists/__init__.py index 9f175a8f6fb..ed689072174 100644 --- a/src/compas_ghpython/artists/__init__.py +++ b/src/compas_ghpython/artists/__init__.py @@ -1,75 +1,3 @@ -""" -******************************************************************************** -artists -******************************************************************************** - -.. currentmodule:: compas_ghpython.artists - -.. rst-class:: lead - -Artists for visualizing (painting) COMPAS geometry, robots, and data structures in Grasshopper using the GH Python component. -The artists in this package provide plugins for the pluggable methods of the base artists in :mod:`compas.artists`. -Therefore, they can be used directly, from here, or through the base artists :class:`~compas.artists.Artist`. - - -Primitive Artists -================= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - BoxArtist - CapsuleArtist - CircleArtist - ConeArtist - CurveArtist - CylinderArtist - FrameArtist - LineArtist - PointArtist - PolygonArtist - PolyhedronArtist - PolylineArtist - SphereArtist - SurfaceArtist - TorusArtist - VectorArtist - BrepArtist - - -Datastructure Artists -===================== - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - MeshArtist - NetworkArtist - VolMeshArtist - - -Robot Artist -============ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - RobotModelArtist - - -Base Classes -============ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - GHArtist - -""" from __future__ import absolute_import from compas.plugins import plugin @@ -97,8 +25,6 @@ from compas.datastructures import Network from compas.datastructures import VolMesh -from compas.robots import RobotModel - from .artist import GHArtist from .boxartist import BoxArtist from .capsuleartist import CapsuleArtist @@ -114,7 +40,6 @@ from .polygonartist import PolygonArtist from .polyhedronartist import PolyhedronArtist from .polylineartist import PolylineArtist -from .robotmodelartist import RobotModelArtist from .sphereartist import SphereArtist from .surfaceartist import SurfaceArtist from .torusartist import TorusArtist @@ -139,7 +64,6 @@ def register_artists(): Artist.register(Polygon, PolygonArtist, context="Grasshopper") Artist.register(Polyhedron, PolyhedronArtist, context="Grasshopper") Artist.register(Polyline, PolylineArtist, context="Grasshopper") - Artist.register(RobotModel, RobotModelArtist, context="Grasshopper") Artist.register(Sphere, SphereArtist, context="Grasshopper") Artist.register(Surface, SurfaceArtist, context="Grasshopper") Artist.register(Torus, TorusArtist, context="Grasshopper") @@ -165,7 +89,6 @@ def register_artists(): "PolygonArtist", "PolyhedronArtist", "PolylineArtist", - "RobotModelArtist", "SphereArtist", "SurfaceArtist", "TorusArtist", diff --git a/src/compas_ghpython/artists/robotmodelartist.py b/src/compas_ghpython/artists/robotmodelartist.py deleted file mode 100644 index 913ec110e19..00000000000 --- a/src/compas_ghpython/artists/robotmodelartist.py +++ /dev/null @@ -1,56 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import compas_ghpython -from compas.utilities import rgb_to_rgb - -# from compas_rhino.geometry.transformations import xtransform - -from compas.artists import RobotModelArtist -from .artist import GHArtist - - -class RobotModelArtist(GHArtist, RobotModelArtist): - """Artist for drawing robot models. - - Parameters - ---------- - model : :class:`~compas.robots.RobotModel` - Robot model. - **kwargs : dict, optional - Additional keyword arguments. - See :class:`~compas_ghpython.artists.GHArtist` and :class:`~compas.artists.RobotModelArtist` for more info. - - """ - - def __init__(self, model, **kwargs): - super(RobotModelArtist, self).__init__(model=model, **kwargs) - - # # again not really sure why this is here - # def transform(self, native_mesh, transformation): - # xtransform(native_mesh, transformation) - - # same here - # there is no reference to self... - def create_geometry(self, geometry, name=None, color=None): - if color: - color = rgb_to_rgb(color[0], color[1], color[2]) - - vertices, faces = geometry.to_vertices_and_faces(triangulated=False) - - mesh = compas_ghpython.draw_mesh(vertices, faces, color=color) - # Try to fix invalid meshes - if not mesh.IsValid: - mesh.FillHoles() - return mesh - - def draw(self): - """Draw the visual meshes of the robot model. - - Returns - ------- - list[:rhino:`Rhino.Geometry.Mesh`] - - """ - return self.draw_visual() diff --git a/src/compas_ghpython/utilities/__init__.py b/src/compas_ghpython/utilities/__init__.py index 27cea309f23..eb74b3fcf60 100644 --- a/src/compas_ghpython/utilities/__init__.py +++ b/src/compas_ghpython/utilities/__init__.py @@ -1,60 +1,3 @@ -""" -******************************************************************************** -utilities -******************************************************************************** - -.. currentmodule:: compas_ghpython.utilities - -This package contains utilities for working with COMPAS in Grasshopper. - -drawing -======= - -.. autosummary:: - :toctree: generated/ - - draw_frame - draw_points - draw_lines - draw_polylines - draw_faces - draw_cylinders - draw_pipes - draw_spheres - draw_mesh - draw_network - draw_brep - - -sets -==== - -.. autosummary:: - :toctree: generated/ - - list_to_ghtree - ghtree_to_list - - -timers -====== - -.. autosummary:: - :toctree: generated/ - - update_component - - -misc -==== - -.. autosummary:: - :toctree: generated/ - - unload_modules - create_id - -""" from __future__ import absolute_import from compas_rhino.utilities import unload_modules diff --git a/src/compas_rhino/artists/__init__.py b/src/compas_rhino/artists/__init__.py index 59e73d52ad4..dc625fc33c8 100644 --- a/src/compas_rhino/artists/__init__.py +++ b/src/compas_rhino/artists/__init__.py @@ -1,88 +1,3 @@ -""" -******************************************************************************** -artists -******************************************************************************** - -.. currentmodule:: compas_rhino.artists - - -Primitive Artists -================= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - CircleArtist - FrameArtist - LineArtist - PlaneArtist - PointArtist - PolygonArtist - PolylineArtist - VectorArtist - BrepArtist - - -Shape Artists -============= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - BoxArtist - CapsuleArtist - ConeArtist - CylinderArtist - PolyhedronArtist - SphereArtist - TorusArtist - - -Curve and Surface Artists -========================= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - CurveArtist - SurfaceArtist - - -Datastructure Artists -===================== - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - MeshArtist - NetworkArtist - VolMeshArtist - - -Robot Artist -============ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - RobotModelArtist - - -Base Classes -============ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - RhinoArtist - -""" from __future__ import absolute_import from compas.plugins import plugin @@ -113,7 +28,6 @@ from compas.datastructures import Network from compas.datastructures import VolMesh -from compas.robots import RobotModel import compas_rhino from .artist import RhinoArtist @@ -135,7 +49,6 @@ from .meshartist import MeshArtist from .networkartist import NetworkArtist from .volmeshartist import VolMeshArtist -from .robotmodelartist import RobotModelArtist from .curveartist import CurveArtist from .surfaceartist import SurfaceArtist @@ -172,7 +85,6 @@ def register_artists(): Artist.register(Mesh, MeshArtist, context="Rhino") Artist.register(Network, NetworkArtist, context="Rhino") Artist.register(VolMesh, VolMeshArtist, context="Rhino") - Artist.register(RobotModel, RobotModelArtist, context="Rhino") Artist.register(Curve, CurveArtist, context="Rhino") Artist.register(Surface, SurfaceArtist, context="Rhino") Artist.register(Brep, BrepArtist, context="Rhino") @@ -199,7 +111,6 @@ def register_artists(): "MeshArtist", "NetworkArtist", "VolMeshArtist", - "RobotModelArtist", "CurveArtist", "SurfaceArtist", "BrepArtist", diff --git a/src/compas_rhino/artists/robotmodelartist.py b/src/compas_rhino/artists/robotmodelartist.py deleted file mode 100644 index a36cd4ca4ee..00000000000 --- a/src/compas_rhino/artists/robotmodelartist.py +++ /dev/null @@ -1,252 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import time - -import Rhino.Geometry # type: ignore -import scriptcontext as sc # type: ignore -from System.Drawing import Color # type: ignore -from Rhino.DocObjects.ObjectColorSource import ColorFromObject # type: ignore -from Rhino.DocObjects.ObjectColorSource import ColorFromLayer # type: ignore -from Rhino.DocObjects.ObjectMaterialSource import MaterialFromObject # type: ignore - -from compas.artists import RobotModelArtist as BaseArtist - -import compas_rhino -from compas_rhino.artists import RhinoArtist -from compas_rhino.conversions import transformation_to_rhino - - -class RobotModelArtist(RhinoArtist, BaseArtist): - """Artist for drawing robot models. - - Parameters - ---------- - model : :class:`~compas.robots.RobotModel` - Robot model. - **kwargs : dict, optional - Additional keyword arguments. - For more info, see :class:`RhinoArtist` and :class:`RobotModelArtist`. - - """ - - def __init__(self, model, **kwargs): - super(RobotModelArtist, self).__init__(model=model, **kwargs) - - def transform(self, native_mesh, transformation): - T = transformation_to_rhino(transformation) - native_mesh.Transform(T) - - def create_geometry(self, geometry, name=None, color=None): - """Create a Rhino mesh corresponding to the geometry of the model. - - Parameters - ---------- - geometry : :class:`~compas.datastructures.Mesh` - A COMPAS mesh data structure. - name : str, optional - Name of the mesh object. - color : tuple[int, int, int], optional - Color of the mesh object. - - Returns - ------- - :rhino:`Rhino.Geometry.Mesh` - - """ - # Imported colors take priority over a the parameter color - if "mesh_color.diffuse" in geometry.attributes: - color = geometry.attributes["mesh_color.diffuse"] - - vertices, faces = geometry.to_vertices_and_faces(triangulated=False) - - mesh = Rhino.Geometry.Mesh() - - if name: - mesh.UserDictionary.Set("MeshName", name) - - if color: - r, g, b, a = color - mesh.UserDictionary.Set("MeshColor.R", r) - mesh.UserDictionary.Set("MeshColor.G", g) - mesh.UserDictionary.Set("MeshColor.B", b) - mesh.UserDictionary.Set("MeshColor.A", a) - - for v in vertices: - mesh.Vertices.Add(*v) - for face in faces: - mesh.Faces.AddFace(*face) - - mesh.Normals.ComputeNormals() - mesh.Compact() - - # Try to fix invalid meshes - if not mesh.IsValid: - mesh.FillHoles() - - return mesh - - def _enter_layer(self): - self._previous_layer = None - - if self.layer: - if not compas_rhino.rs.IsLayer(self.layer): - compas_rhino.create_layers_from_path(self.layer) - self._previous_layer = compas_rhino.rs.CurrentLayer(self.layer) - - compas_rhino.rs.EnableRedraw(False) - - def _exit_layer(self): - if self.layer and self._previous_layer: - compas_rhino.rs.CurrentLayer(self._previous_layer) - - self.redraw() - - def draw_collision(self): - """Draw all the collision geometries of the robot model. - - Returns - ------- - list[System.Guid] - The GUIDs of the created Rhino objects. - - """ - collisions = super(RobotModelArtist, self).draw_collision() - self._enter_layer() - - new_guids = [] - for mesh in collisions: - guids = self._add_mesh_to_doc(mesh) - new_guids.extend(guids) - - self._exit_layer() - return new_guids - - def draw_visual(self): - """Draw all the visual geometries of the robot model. - - Returns - ------- - list[System.Guid] - The GUIDs of the created Rhino objects. - - """ - visuals = super(RobotModelArtist, self).draw_visual() - self._enter_layer() - - new_guids = [] - for mesh in visuals: - guids = self._add_mesh_to_doc(mesh) - new_guids.extend(guids) - - self._exit_layer() - return new_guids - - def draw_attached_meshes(self): - """Draw all the geometries attached to the robot model. - - Returns - ------- - list[System.Guid] - The GUIDs of the created Rhino objects. - - """ - acms = super(RobotModelArtist, self).draw_attached_meshes() - self._enter_layer() - - new_guids = [] - for mesh in acms: - guids = self._add_mesh_to_doc(mesh) - new_guids.extend(guids) - - self._exit_layer() - return new_guids - - def draw(self): - """Draw the geometry of the model. - - Returns - ------- - list[System.Guid] - The GUIDs of the created Rhino objects. - - """ - return self.draw_visual() - - def redraw(self, timeout=None): - """Redraw the Rhino view. - - Parameters - ---------- - timeout : float, optional - The amount of time the artist waits before updating the Rhino view. - The time should be specified in seconds. - - Returns - ------- - None - - """ - if timeout: - time.sleep(timeout) - - compas_rhino.rs.EnableRedraw(True) - compas_rhino.rs.Redraw() - - def clear_layer(self): - """Clear the main layer of the artist. - - Returns - ------- - None - - """ - if self.layer: - compas_rhino.clear_layer(self.layer) - else: - compas_rhino.clear_current_layer() - - def _add_mesh_to_doc(self, mesh): - guid = sc.doc.Objects.AddMesh(mesh) - - color = None - if "MeshColor.R" in mesh.UserDictionary: - color = [ - mesh.UserDictionary["MeshColor.R"], - mesh.UserDictionary["MeshColor.G"], - mesh.UserDictionary["MeshColor.B"], - mesh.UserDictionary["MeshColor.A"], - ] - name = mesh.UserDictionary["MeshName"] if "MeshName" in mesh.UserDictionary else None - - obj = sc.doc.Objects.Find(guid) - - if obj: - attr = obj.Attributes - if color: - r, g, b, a = [i * 255 for i in color] - attr.ObjectColor = Color.FromArgb(a, r, g, b) - attr.ColorSource = ColorFromObject - - material_name = "robotmodelartist.{:.2f}_{:.2f}_{:.2f}_{:.2f}".format(r, g, b, a) - material_index = sc.doc.Materials.Find(material_name, True) - - # Material does not exist, create it - if material_index == -1: - material_index = sc.doc.Materials.Add() - material = sc.doc.Materials[material_index] - material.Name = material_name - material.DiffuseColor = attr.ObjectColor - material.CommitChanges() - - attr.MaterialIndex = material_index - attr.MaterialSource = MaterialFromObject - else: - attr.ColorSource = ColorFromLayer - - if name: - attr.Name = name - - obj.CommitChanges() - return [guid] diff --git a/src/compas_rhino/conduits/__init__.py b/src/compas_rhino/conduits/__init__.py index 92514018af2..debec314089 100644 --- a/src/compas_rhino/conduits/__init__.py +++ b/src/compas_rhino/conduits/__init__.py @@ -1,29 +1,3 @@ -""" -******************************************************************************** -conduits -******************************************************************************** - -.. currentmodule:: compas_rhino.conduits - -.. rst-class:: lead - -Display conduits can be used to visualize iterative processes -with less of a performance penalty than with regular geometry objects. - -Classes -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - BaseConduit - FacesConduit - LabelsConduit - LinesConduit - PointsConduit - -""" from __future__ import absolute_import from .base import BaseConduit diff --git a/src/compas_rhino/conversions/__init__.py b/src/compas_rhino/conversions/__init__.py index 5cacdd53149..098456f1afc 100644 --- a/src/compas_rhino/conversions/__init__.py +++ b/src/compas_rhino/conversions/__init__.py @@ -1,104 +1,3 @@ -""" -******************************************************************************** -conversions -******************************************************************************** - -.. currentmodule:: compas_rhino.conversions - -.. rst-class:: lead - -Conversions between Rhino geometry objects (:mod:`Rhino.Geometry`) and COMPAS geometry objects (:mod:`compas.geometry`). - -Exceptions -========== - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ConversionError - - -To Rhino -======== - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - arc_to_rhino - box_to_rhino - brep_to_rhino - capsule_to_rhino_brep - circle_to_rhino - circle_to_rhino_curve - cone_to_rhino - cone_to_rhino_brep - curve_to_rhino - cylinder_to_rhino - cylinder_to_rhino_brep - ellipse_to_rhino - ellipse_to_rhino_curve - frame_to_rhino - frame_to_rhino_plane - line_to_rhino - line_to_rhino_curve - mesh_to_rhino - plane_to_rhino - point_to_rhino - polygon_to_rhino - polyhedron_to_rhino - polyline_to_rhino - polyline_to_rhino_curve - sphere_to_rhino - surface_to_rhino - torus_to_rhino - torus_to_rhino_brep - transformation_to_rhino - transformation_matrix_to_rhino - vertices_and_faces_to_rhino - vector_to_rhino - - -To COMPAS -========= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - arc_to_compas - box_to_compas - brep_to_compas_box - brep_to_compas_cone - brep_to_compas_cylinder - brep_to_compas_sphere - circle_to_compas - cone_to_compas - curve_to_compas_circle - curve_to_compas_ellipse - curve_to_compas_line - curve_to_compas_polyline - cylinder_to_compas - ellipse_to_compas - extrusion_to_compas_box - extrusion_to_compas_cylinder - extrusion_to_compas_torus - line_to_compas - mesh_to_compas - plane_to_compas - plane_to_compas_frame - point_to_compas - polygon_to_compas - polyline_to_compas - sphere_to_compas - surface_to_compas - surface_to_compas_data - surface_to_compas_mesh - surface_to_compas_quadmesh - vector_to_compas - -""" from __future__ import absolute_import from .exceptions import ConversionError diff --git a/src/compas_rhino/forms/__init__.py b/src/compas_rhino/forms/__init__.py index 103314bcccd..7d3c0c10a78 100644 --- a/src/compas_rhino/forms/__init__.py +++ b/src/compas_rhino/forms/__init__.py @@ -1,36 +1,3 @@ -""" -******************************************************************************** -forms -******************************************************************************** - -.. currentmodule:: compas_rhino.forms - - -Classes -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - BrowserForm - ChartForm - ImageForm - SliderForm - TextForm - - -Base Classes -============ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - BaseForm - - -""" from __future__ import absolute_import from .base import BaseForm diff --git a/src/compas_rhino/geometry/__init__.py b/src/compas_rhino/geometry/__init__.py index adbb0d3da95..c6df0892184 100644 --- a/src/compas_rhino/geometry/__init__.py +++ b/src/compas_rhino/geometry/__init__.py @@ -1,88 +1,5 @@ -""" -******************************************************************************** -geometry -******************************************************************************** - -.. currentmodule:: compas_rhino.geometry - -Classes -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - RhinoCurve - RhinoNurbsCurve - RhinoNurbsSurface - - RhinoBrep - RhinoBrepVertex - RhinoBrepEdge - RhinoBrepFace - RhinoBrepLoop - RhinoBrepTrim - -Plugins -======= - -Booleans --------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - booleans.boolean_difference_mesh_mesh - booleans.boolean_intersection_mesh_mesh - booleans.boolean_union_mesh_mesh - -Curves ------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - curves.new_curve - curves.new_nurbscurve - curves.new_nurbscurve_from_interpolation - curves.new_nurbscurve_from_parameters - curves.new_nurbscurve_from_points - curves.new_nurbscurve_from_step - -TriMesh -------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - trimesh.trimesh_gaussian_curvature - trimesh.trimesh_mean_curvature - trimesh.trimesh_principal_curvature - trimesh.trimesh_slice - -""" from __future__ import absolute_import -from compas_rhino.conversions import RhinoGeometry - -from compas_rhino.conversions import RhinoBox -from compas_rhino.conversions import RhinoCircle -from compas_rhino.conversions import RhinoCone -from compas_rhino.conversions import RhinoCurve -from compas_rhino.conversions import RhinoCylinder -from compas_rhino.conversions import RhinoEllipse -from compas_rhino.conversions import RhinoLine -from compas_rhino.conversions import RhinoMesh -from compas_rhino.conversions import RhinoPlane -from compas_rhino.conversions import RhinoPoint -from compas_rhino.conversions import RhinoPolyline -from compas_rhino.conversions import RhinoSphere -from compas_rhino.conversions import RhinoSurface -from compas_rhino.conversions import RhinoVector - from .curves.nurbs import RhinoNurbsCurve from .surfaces.nurbs import RhinoNurbsSurface @@ -94,21 +11,6 @@ from .brep.trim import RhinoBrepTrim __all__ = [ - "RhinoGeometry", - "RhinoBox", - "RhinoCircle", - "RhinoCone", - "RhinoCurve", - "RhinoCylinder", - "RhinoEllipse", - "RhinoLine", - "RhinoMesh", - "RhinoPlane", - "RhinoPoint", - "RhinoPolyline", - "RhinoSphere", - "RhinoSurface", - "RhinoVector", "RhinoNurbsCurve", "RhinoNurbsSurface", "RhinoBrep", diff --git a/src/compas_rhino/utilities/__init__.py b/src/compas_rhino/utilities/__init__.py index 7d1d82e27a2..740bf74d88d 100644 --- a/src/compas_rhino/utilities/__init__.py +++ b/src/compas_rhino/utilities/__init__.py @@ -1,99 +1,3 @@ -""" -******************************************************************************** -utilities -******************************************************************************** - -.. currentmodule:: compas_rhino.utilities - - -layers -====== - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - create_layers - clear_layers - delete_layers - - -objects -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - delete_object - delete_objects - get_line_coordinates - get_objects - get_object_attributes - set_object_attributes - get_object_attributes_from_name - get_object_layers - get_object_types - get_object_names - get_object_name - get_point_coordinates - get_polyline_coordinates - get_polygon_coordinates - purge_objects - select_curve - select_curves - select_line - select_lines - select_mesh - select_meshes - select_point - select_points - select_polygon - select_polygons - select_polyline - select_polylines - select_surface - select_surfaces - - -drawing -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - draw_labels - draw_points - draw_lines - draw_polylines - draw_faces - draw_cylinders - draw_pipes - draw_spheres - draw_mesh - draw_curves - draw_surfaces - - -constructors -============ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - volmesh_from_polysurfaces - -misc -==== - -.. autosummary:: - :toctree: generated/ - - unload_modules - -""" from __future__ import absolute_import from .document import (