Skip to content

Commit

Permalink
first pass of look at objective
Browse files Browse the repository at this point in the history
  • Loading branch information
djrakita committed Dec 6, 2023
1 parent 7c9ea19 commit 95afe50
Show file tree
Hide file tree
Showing 15 changed files with 367 additions and 56 deletions.
2 changes: 0 additions & 2 deletions optima_refactor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ optima_interpolation = { path = "crates/optima_interpolation" }
optima_proximity = { path = "crates/optima_proximity" }
optima_universal_hashmap = { path = "crates/optima_universal_hashmap" }
nalgebra = { version="0.32.*", features=["rand", "serde-serialize"] }
argmin = "0.8.1"
argmin-math = "0.3.0"

[features]
default = [
Expand Down
1 change: 1 addition & 0 deletions optima_refactor/crates/optima_geometry/src/bin/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@


fn main() {

}
27 changes: 23 additions & 4 deletions optima_refactor/crates/optima_geometry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,34 @@ use optima_interpolation::get_range;
use optima_linalg::OVec;
use optima_sampling::SimpleSampler;

pub fn proj<T: AD, V: OVec<T>>(v: &V, u: &V) -> V {
return u.ovec_scalar_mul(&proj_scalar(v, u))
#[inline(always)]
pub fn pt_dis_to_line<T: AD, V: OVec<T>>(pt: &V, a: &V, b: &V, clamp: bool) -> (T, V) {
let b_minus_a = b.ovec_sub(a);
let pt_minus_a = pt.ovec_sub(a);

let p = proj(&pt_minus_a, &b_minus_a, clamp).ovec_add(&a);
let dis = p.ovec_sub(&pt).ovec_p_norm(&T::constant(2.0));

(dis, p)
}

/// project v onto u
#[inline(always)]
pub fn proj<T: AD, V: OVec<T>>(v: &V, u: &V, clamp: bool) -> V {
let mut proj = proj_scalar(v, u);
if clamp { proj = proj.clamp(T::zero(), T::one()); }

return u.ovec_scalar_mul(&proj);
}

#[inline(always)]
pub fn proj_scalar<T: AD, V: OVec<T>>(v: &V, u: &V) -> T {
let n = v.ovec_dot(u);
let d = u.ovec_dot(u);
let d = u.ovec_dot(u).max(T::constant(f64::MIN));
return n/d;
}

#[inline(always)]
pub fn get_orthonormal_basis<T: AD, V: OVec<T>>(initial_vector: &V, basis_dim: usize, seed: Option<u64>) -> Vec<V> {
let dim = initial_vector.len();
let basis_dim_copy = basis_dim.min(dim);
Expand All @@ -36,7 +54,7 @@ pub fn get_orthonormal_basis<T: AD, V: OVec<T>>(initial_vector: &V, basis_dim: u
for j in 0..i {
let tmp2 = out_vecs[j].clone();
// out_vecs[i] -= &proj(&tmp1, &tmp2)
out_vecs[i] = out_vecs[i].ovec_sub(&proj(&tmp1, &tmp2));
out_vecs[i] = out_vecs[i].ovec_sub(&proj(&tmp1, &tmp2, false));
}
}

Expand All @@ -49,6 +67,7 @@ pub fn get_orthonormal_basis<T: AD, V: OVec<T>>(initial_vector: &V, basis_dim: u
out_vecs
}

#[inline(always)]
pub fn get_points_around_circle<T: AD, V: OVec<T>>(center_point: &V, rotation_axis: &V, circle_radius: T, num_samples: usize, seed: Option<u64>) -> Vec<V> {
assert!(center_point.len() > 2);
assert_eq!(center_point.len(), rotation_axis.len());
Expand Down
1 change: 0 additions & 1 deletion optima_refactor/crates/optima_optimization2/src/argmin.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use ad_trait::differentiable_block::DifferentiableBlock2;
use ad_trait::differentiable_function::{DerivativeMethodTrait2, DifferentiableFunctionClass};
use argmin::core::*;
use nalgebra::DMatrix;

pub struct ArgminDiffBlockWrapper<'a, DC: DifferentiableFunctionClass, E: DerivativeMethodTrait2> {
diff_block: DifferentiableBlock2<'a, DC, E>
Expand Down
4 changes: 2 additions & 2 deletions optima_refactor/crates/optima_optimization2/src/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ fn simple_open_optimize<'a, DC, E>(objective_function: &DifferentiableBlock2<'a,
Ok(())
};

// let binding = constraints::Rectangle::new(Some(lower_bounds), Some(upper_bounds));
let binding = constraints::NoConstraints::new();
let binding = constraints::Rectangle::new(Some(lower_bounds), Some(upper_bounds));
// let binding = constraints::NoConstraints::new();
let problem = Problem::new(&binding, df, f);
let mut binding = cache.lock();
let cache = binding.as_mut().unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ impl<'a, T: AD, Q: OPairGroupQryTrait> OwnedPairGroupQry<'a, T, Q> {
OwnedPairGroupQry::<'a, T1, Q>::from_json_string(&json_str)
}
}
impl<'a, T: AD, Q: OPairGroupQryTrait> Clone for OwnedPairGroupQry<'a, T, Q> {
fn clone(&self) -> Self {
self.to_new_ad_type::<T>()
}
}

/*
impl<T: AD, P: O3DPose<T>, Q: OPairGroupQryTrait<T, P, ShapeTypeA=OParryShape<T, P>, ShapeTypeB=OParryShape<T, P>, SelectorType=ParryPairSelector>> OwnedPairGroupQry<T, P, Q> {
Expand Down Expand Up @@ -335,6 +340,23 @@ impl PairGroupQryOutputCategoryTrait for PairGroupQryOutputCategoryParryIntersec
type Output<T: AD, P: O3DPose<T>> = ParryIntersectGroupOutput;
}

pub struct EmptyParryPairGroupIntersectQry;
impl OPairGroupQryTrait for EmptyParryPairGroupIntersectQry {
type ShapeCategory = ShapeCategoryOParryShape;
type SelectorType = ParryPairSelector;
type ArgsCategory = ();
type OutputCategory = PairGroupQryOutputCategoryParryIntersect;

fn query<'a, T: AD, P: O3DPose<T>, S: PairSkipsTrait, A: PairAverageDistanceTrait<T>>(_shape_group_a: &Vec<<Self::ShapeCategory as ShapeCategoryTrait>::ShapeType<T, P>>, _shape_group_b: &Vec<<Self::ShapeCategory as ShapeCategoryTrait>::ShapeType<T, P>>, _poses_a: &Vec<P>, _poses_b: &Vec<P>, _pair_selector: &Self::SelectorType, _pair_skips: &S, _pair_average_distances: &A, _args: &<Self::ArgsCategory as PairGroupQryArgsCategory>::Args<'a, T>) -> <Self::OutputCategory as PairGroupQryOutputCategoryTrait>::Output<T, P> {
ParryIntersectGroupOutput {
intersect: false,
outputs: vec![],
aux_data: ParryOutputAuxData { num_queries: 0, duration: Default::default() },
}
}
}
pub type OwnedEmptyParryPairGroupIntersectQry<'a, T> = OwnedPairGroupQry<'a, T, EmptyParryPairGroupIntersectQry>;

////////////////////////////////////////////////////////////////////////////////////////////////////

pub struct ParryDistanceGroupQry;
Expand Down Expand Up @@ -430,6 +452,24 @@ impl PairGroupQryOutputCategoryTrait for PairGroupQryOutputCategoryParryDistance
type Output<T: AD, P: O3DPose<T>> = ParryDistanceGroupOutput<T>;
}

pub struct EmptyParryPairGroupDistanceQry;
impl OPairGroupQryTrait for EmptyParryPairGroupDistanceQry {
type ShapeCategory = ShapeCategoryOParryShape;
type SelectorType = ParryPairSelector;
type ArgsCategory = ();
type OutputCategory = PairGroupQryOutputCategoryParryDistance;

fn query<'a, T: AD, P: O3DPose<T>, S: PairSkipsTrait, A: PairAverageDistanceTrait<T>>(_shape_group_a: &Vec<<Self::ShapeCategory as ShapeCategoryTrait>::ShapeType<T, P>>, _shape_group_b: &Vec<<Self::ShapeCategory as ShapeCategoryTrait>::ShapeType<T, P>>, _poses_a: &Vec<P>, _poses_b: &Vec<P>, _pair_selector: &Self::SelectorType, _pair_skips: &S, _pair_average_distances: &A, _args: &<Self::ArgsCategory as PairGroupQryArgsCategory>::Args<'a, T>) -> <Self::OutputCategory as PairGroupQryOutputCategoryTrait>::Output<T, P> {
ParryDistanceGroupOutput {
min_dis_wrt_average: T::constant(f64::MAX),
min_raw_dis: T::constant(f64::MAX),
outputs: vec![],
aux_data: ParryOutputAuxData { num_queries: 0, duration: Default::default() },
}
}
}
pub type OwnedEmptyParryPairGroupDistanceQry<'a, T> = OwnedPairGroupQry<'a, T, EmptyParryPairGroupDistanceQry>;

////////////////////////////////////////////////////////////////////////////////////////////////////

/*
Expand Down Expand Up @@ -1645,7 +1685,12 @@ pub trait ProximityLossFunctionTrait<T: AD> {
fn loss(&self, distance: T, cutoff: T) -> T;
}

pub struct ProximityLossFunctionHinge { }
pub struct ProximityLossFunctionHinge;
impl ProximityLossFunctionHinge {
pub fn new() -> Self {
Self {}
}
}
impl<T: AD> ProximityLossFunctionTrait<T> for ProximityLossFunctionHinge {
#[inline(always)]
fn loss(&self, distance: T, cutoff: T) -> T {
Expand Down
1 change: 1 addition & 0 deletions optima_refactor/crates/optima_robotics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ optima_proximity = { path = "../optima_proximity" }
optima_sampling = { path = "../optima_sampling"}
optima_universal_hashmap = { path = "../optima_universal_hashmap" }
optima_optimization = { path = "../optima_optimization" }
optima_geometry = { path = "../optima_geometry" }
nalgebra = { version="0.32.*", features=["rand", "serde-serialize"] }
urdf-rs = { version="0.7.2" }
arrayvec = { version="0.7.4", features = ["serde"] }
Expand Down
60 changes: 49 additions & 11 deletions optima_refactor/crates/optima_robotics/src/robot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use optima_3d_spatial::optima_3d_pose::{O3DPose, O3DPoseCategoryIsometry3, O3DPo
use crate::utils::get_urdf_path_from_chain_name;
use serde_with::*;
use optima_3d_mesh::{SaveToSTL, ToTriMesh};
use optima_3d_spatial::optima_3d_vec::O3DVecCategoryArr;
use optima_console::output::{oprint, PrintColor, PrintMode};
use optima_console::tab;
use optima_file::path::{OAssetLocation, OPath, OPathMatchingPattern, OPathMatchingStopCondition, OStemCellPath};
Expand All @@ -27,7 +28,9 @@ use optima_proximity::shape_scene::ShapeSceneTrait;
use optima_proximity::shapes::ShapeCategoryOParryShape;
use optima_sampling::SimpleSampler;
use crate::robot_shape_scene::ORobotParryShapeScene;
use crate::robotics_optimization2::robotics_optimization_functions::{LookAtAxis, LookAtTarget};
use crate::robotics_optimization2::robotics_optimization_ik::{DifferentiableBlockIKObjective, DifferentiableFunctionClassIKObjective, DifferentiableFunctionIKObjective, IKGoal, IKGoalVecTrait};
use crate::robotics_optimization2::robotics_optimization_look_at::{DifferentiableFunctionClassLookAt, DifferentiableFunctionLookAt};

pub type ORobotDefault = ORobot<f64, O3DPoseCategoryIsometry3, OLinalgCategoryNalgebra>;
#[serde_as]
Expand Down Expand Up @@ -455,9 +458,13 @@ impl<T: AD, C: O3DPoseCategory + 'static, L: OLinalgCategory + 'static> ORobot<T
*/
self.dof_to_joint_and_sub_dof_idxs().iter().for_each(|(joint_idx, sub_dof_idx)| {
let joint = &self.joints[*joint_idx];
let l = joint.limit.lower()[*sub_dof_idx];
let u = joint.limit.upper()[*sub_dof_idx];
out.push((l,u));
if joint.is_present_in_model {
if joint.fixed_values().is_none() {
let l = joint.limit.lower()[*sub_dof_idx];
let u = joint.limit.upper()[*sub_dof_idx];
out.push((l, u));
}
}
});

out
Expand Down Expand Up @@ -921,24 +928,55 @@ impl<T: AD, C: O3DPoseCategory, L: OLinalgCategory + 'static> ORobot<T, C, L> {
}
}
impl<C: O3DPoseCategory, L: OLinalgCategory> ORobot<f64, C, L> {
pub fn get_ik_differentiable_block<'a, E, FQ, Q>(&'a self, derivative_method: E, filter_query: OwnedPairGroupQry<'a, f64, FQ>, distance_query: OwnedPairGroupQry<'a, f64, Q>, init_state: &[f64], ik_goal_link_idxs: Vec<usize>, dis_filter_cutoff: f64) -> DifferentiableBlock2<'a, DifferentiableFunctionClassIKObjective<C, L, FQ, Q>, E>
pub fn get_ik_differentiable_block<'a, E, FQ, Q>(&'a self, derivative_method: E, filter_query: OwnedPairGroupQry<'a, f64, FQ>, distance_query: OwnedPairGroupQry<'a, f64, Q>, init_state: &[f64], ik_goal_link_idxs: Vec<usize>, linf_dis_cutoff: f64, dis_filter_cutoff: f64, ee_matching_weight: f64, self_collision_avoidance_weight: f64, min_vel_weight: f64, min_acc_weight: f64, min_jerk_weight: f64) -> DifferentiableBlock2<'a, DifferentiableFunctionClassIKObjective<C, L, FQ, Q>, E>
where E: DerivativeMethodTrait2,
FQ: OPairGroupQryTrait<ShapeCategory=ShapeCategoryOParryShape, SelectorType=ParryPairSelector, OutputCategory=PairGroupQryOutputCategoryParryFilter>,
Q: OPairGroupQryTrait<ShapeCategory=ShapeCategoryOParryShape, SelectorType=ParryPairSelector, OutputCategory=PairGroupQryOutputCategoryParryDistance> {

let last_proximity_filter_state: Rc<RefCell<Option<Vec<f64>>>> = Rc::new(RefCell::new(None));
let filter_output: Rc<RefCell<Option<ParryFilterOutput>>> = Rc::new(RefCell::new(None));

let fk_res = self.forward_kinematics(&init_state.to_vec(), None);
let f2 = self.get_ik_objective_function(Cow::Owned(self.to_new_ad_type::<E::T>()), filter_query.clone(), distance_query.clone(), init_state, ik_goal_link_idxs.clone(), linf_dis_cutoff, dis_filter_cutoff, ee_matching_weight, self_collision_avoidance_weight, min_vel_weight, min_acc_weight, min_jerk_weight, last_proximity_filter_state.clone(), filter_output.clone());
let f1 = self.get_ik_objective_function(Cow::Borrowed(self), filter_query, distance_query, init_state, ik_goal_link_idxs, linf_dis_cutoff, dis_filter_cutoff, ee_matching_weight, self_collision_avoidance_weight, min_vel_weight, min_acc_weight, min_jerk_weight, last_proximity_filter_state.clone(), filter_output.clone());

let mut ik_goals: Vec<IKGoal<f64, C::P<f64>>> = vec![];
ik_goal_link_idxs.iter().for_each(|x| { ik_goals.push(IKGoal::new(*x, fk_res.get_link_pose(*x).as_ref().expect("error").clone(), 1.0)); });
DifferentiableBlockIKObjective::new(derivative_method, f1, f2)
}
pub fn get_look_at_differentiable_block<'a, E, FQ, Q>(&'a self, derivative_method: E, filter_query: OwnedPairGroupQry<'a, f64, FQ>, distance_query: OwnedPairGroupQry<'a, f64, Q>, init_state: &[f64], ik_goal_link_idxs: Vec<usize>, looker_link: usize, looker_axis: LookAtAxis, look_at_target: LookAtTarget<f64, O3DVecCategoryArr>, linf_dis_cutoff: f64, dis_filter_cutoff: f64, ee_matching_weight: f64, self_collision_avoidance_weight: f64, min_vel_weight: f64, min_acc_weight: f64, min_jerk_weight: f64, look_at_weight: f64) -> DifferentiableBlock2<'a, DifferentiableFunctionClassLookAt<C, L, FQ, Q>, E>
where E: DerivativeMethodTrait2,
FQ: OPairGroupQryTrait<ShapeCategory=ShapeCategoryOParryShape, SelectorType=ParryPairSelector, OutputCategory=PairGroupQryOutputCategoryParryFilter>,
Q: OPairGroupQryTrait<ShapeCategory=ShapeCategoryOParryShape, SelectorType=ParryPairSelector, OutputCategory=PairGroupQryOutputCategoryParryDistance> {
let last_proximity_filter_state: Rc<RefCell<Option<Vec<f64>>>> = Rc::new(RefCell::new(None));
let filter_output: Rc<RefCell<Option<ParryFilterOutput>>> = Rc::new(RefCell::new(None));

let linf_dis_cutoff = 0.07;
let f2 = DifferentiableFunctionIKObjective::new(Cow::Owned(self.to_new_ad_type::<E::T>()), ik_goals.to_new_generic_types::<E::T, C>(), init_state.to_vec().ovec_to_other_ad_type::<E::T>(), filter_query.to_new_ad_type::<E::T>(), distance_query.to_new_ad_type::<E::T>(), E::T::constant(dis_filter_cutoff), linf_dis_cutoff, last_proximity_filter_state.clone(), filter_output.clone(), E::T::constant(10.0), E::T::constant(0.1), E::T::constant(1.0), E::T::constant(0.5), E::T::constant(0.2));
let f1 = DifferentiableFunctionIKObjective::new(Cow::Borrowed(self), ik_goals, init_state.to_vec(), filter_query, distance_query, dis_filter_cutoff, linf_dis_cutoff, last_proximity_filter_state.clone(), filter_output.clone(), 10.0, 0.1, 1.0, 0.5, 0.2);
let f2 = self.get_look_at_objective_function(Cow::Owned(self.to_new_ad_type::<E::T>()), filter_query.clone(), distance_query.clone(), init_state, ik_goal_link_idxs.clone(), looker_link, looker_axis.clone(), look_at_target.clone(), linf_dis_cutoff, dis_filter_cutoff, ee_matching_weight, self_collision_avoidance_weight, min_vel_weight, min_acc_weight, min_jerk_weight, look_at_weight, last_proximity_filter_state.clone(), filter_output.clone());
let f1 = self.get_look_at_objective_function(Cow::Borrowed(self), filter_query, distance_query, init_state, ik_goal_link_idxs, looker_link, looker_axis.clone(), look_at_target.clone(), linf_dis_cutoff, dis_filter_cutoff, ee_matching_weight, self_collision_avoidance_weight, min_vel_weight, min_acc_weight, min_jerk_weight, look_at_weight, last_proximity_filter_state.clone(), filter_output.clone());

DifferentiableBlockIKObjective::new(derivative_method, f1, f2)
DifferentiableBlock2::new(derivative_method, f1, f2)
}
}
/// Objective Functions
impl<T: AD, C: O3DPoseCategory, L: OLinalgCategory> ORobot<T, C, L > {
pub fn get_ik_objective_function<'a, T1, FQ, Q>(&'a self, robot: Cow<'a, ORobot<T1, C, L>>, filter_query: OwnedPairGroupQry<'a, f64, FQ>, distance_query: OwnedPairGroupQry<'a, f64, Q>, init_state: &[f64], ik_goal_link_idxs: Vec<usize>, linf_dis_cutoff: f64, dis_filter_cutoff: f64, ee_matching_weight: f64, self_collision_avoidance_weight: f64, min_vel_weight: f64, min_acc_weight: f64, min_jerk_weight: f64, last_proximity_filter_state: Rc<RefCell<Option<Vec<f64>>>>, filter_output: Rc<RefCell<Option<ParryFilterOutput>>>) -> DifferentiableFunctionIKObjective<T1, C, L, FQ, Q>
where T1: AD,
FQ: OPairGroupQryTrait<ShapeCategory=ShapeCategoryOParryShape, SelectorType=ParryPairSelector, OutputCategory=PairGroupQryOutputCategoryParryFilter>,
Q: OPairGroupQryTrait<ShapeCategory=ShapeCategoryOParryShape, SelectorType=ParryPairSelector, OutputCategory=PairGroupQryOutputCategoryParryDistance>
{
let fk_res = self.forward_kinematics(&init_state.to_vec().ovec_to_other_ad_type::<T>(), None);

let mut ik_goals: Vec<IKGoal<T, C::P<T>>> = vec![];
ik_goal_link_idxs.iter().for_each(|x| { ik_goals.push(IKGoal::new(*x, fk_res.get_link_pose(*x).as_ref().expect("error").clone(), T::constant(1.0))); });

let f = DifferentiableFunctionIKObjective::new(robot, ik_goals.to_new_generic_types::<T1, C>(), init_state.to_vec().ovec_to_other_ad_type::<T1>(), filter_query.to_new_ad_type::<T1>(), distance_query.to_new_ad_type::<T1>(), T1::constant(dis_filter_cutoff), linf_dis_cutoff, last_proximity_filter_state.clone(), filter_output.clone(), T1::constant(ee_matching_weight), T1::constant(self_collision_avoidance_weight), T1::constant(min_vel_weight), T1::constant(min_acc_weight), T1::constant(min_jerk_weight));

f
}
pub fn get_look_at_objective_function<'a, T1, FQ, Q>(&'a self, robot: Cow<'a, ORobot<T1, C, L>>, filter_query: OwnedPairGroupQry<'a, f64, FQ>, distance_query: OwnedPairGroupQry<'a, f64, Q>, init_state: &[f64], ik_goal_link_idxs: Vec<usize>, looker_link: usize, looker_axis: LookAtAxis, look_at_target: LookAtTarget<f64, O3DVecCategoryArr>, linf_dis_cutoff: f64, dis_filter_cutoff: f64, ee_matching_weight: f64, self_collision_avoidance_weight: f64, min_vel_weight: f64, min_acc_weight: f64, min_jerk_weight: f64, look_at_weight: f64, last_proximity_filter_state: Rc<RefCell<Option<Vec<f64>>>>, filter_output: Rc<RefCell<Option<ParryFilterOutput>>>) -> DifferentiableFunctionLookAt<T1, C, L, FQ, Q>
where T1: AD,
FQ: OPairGroupQryTrait<ShapeCategory=ShapeCategoryOParryShape, SelectorType=ParryPairSelector, OutputCategory=PairGroupQryOutputCategoryParryFilter>,
Q: OPairGroupQryTrait<ShapeCategory=ShapeCategoryOParryShape, SelectorType=ParryPairSelector, OutputCategory=PairGroupQryOutputCategoryParryDistance> {
let ik_objective = self.get_ik_objective_function(robot, filter_query, distance_query, init_state, ik_goal_link_idxs, linf_dis_cutoff, dis_filter_cutoff, ee_matching_weight, self_collision_avoidance_weight, min_vel_weight, min_acc_weight, min_jerk_weight, last_proximity_filter_state, filter_output);

DifferentiableFunctionLookAt::new(ik_objective, looker_link, looker_axis, look_at_target.to_new_ad_type::<T1>(), T1::constant(look_at_weight))
}
}
impl<T: AD, C: O3DPoseCategory + 'static, L: OLinalgCategory + 'static> AsRobotTrait<T, C, L> for ORobot<T, C, L> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use optima_misc::arr_storage::ImmutArrTraitRaw;
use crate::robotics_components::ChainInfo;
use crate::robotics_traits::JointTrait;


pub fn compute_chain_info<T: AD, C: O3DPoseCategory + 'static, L, J: JointTrait<T, C>>(chainable_link_objects: &Vec<L>, chainable_joint_objects: &Vec<J>) -> ChainInfo {
let num_links = chainable_link_objects.len();
let num_joints = chainable_joint_objects.len();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod robotics_optimization_ik;
pub mod robotics_optimization_functions;
pub mod robotics_optimization_functions;
pub mod robotics_optimization_look_at;
Loading

0 comments on commit 95afe50

Please sign in to comment.