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 (