@@ -24,7 +24,6 @@ import (
2424type Model interface {
2525 Frame
2626 ModelConfig () * ModelConfigJSON
27- ModelPieceFrames ([]Input ) (map [string ]Frame , error )
2827}
2928
3029// KinematicModelFromProtobuf returns a model from a protobuf message representing it.
@@ -137,6 +136,78 @@ func NewSimpleModel(name string) *SimpleModel {
137136 }
138137}
139138
139+ // NewSerialModel is a convenience constructor that builds a Model from a serial chain of frames.
140+ // Returns an error if duplicate frame names are detected.
141+ func NewSerialModel (name string , frames []Frame ) (* SimpleModel , error ) {
142+ seen := make (map [string ]bool )
143+ for _ , f := range frames {
144+ frameName := f .Name ()
145+ if seen [frameName ] {
146+ return nil , NewDuplicateFrameNameError (frameName )
147+ }
148+ seen [frameName ] = true
149+ }
150+
151+ m := NewSimpleModel (name )
152+ m .setOrdTransforms (frames )
153+ return m , nil
154+ }
155+
156+ // NewModelWithLimitOverrides constructs a new model identical to base but with the specified
157+ // joint limits overridden. Overrides are keyed by frame name. Each override replaces the
158+ // first DoF limit of the matching frame.
159+ func NewModelWithLimitOverrides (base * SimpleModel , overrides map [string ]Limit ) (* SimpleModel , error ) {
160+ clonedFrames := make ([]Frame , len (base .ordTransforms ))
161+ for i , f := range base .ordTransforms {
162+ cloned , err := clone (f )
163+ if err != nil {
164+ return nil , fmt .Errorf ("cloning frame %q: %w" , f .Name (), err )
165+ }
166+ clonedFrames [i ] = cloned
167+ }
168+
169+ for name , limit := range overrides {
170+ found := false
171+ for _ , f := range clonedFrames {
172+ if f .Name () == name && len (f .DoF ()) > 0 {
173+ f .DoF ()[0 ] = limit
174+ found = true
175+ break
176+ }
177+ }
178+ if ! found {
179+ return nil , fmt .Errorf ("frame %q not found or has no DoF" , name )
180+ }
181+ }
182+
183+ m := NewSimpleModel (base .name )
184+ m .setOrdTransforms (clonedFrames )
185+ m .modelConfig = base .modelConfig
186+ return m , nil
187+ }
188+
189+ // MoveableFrameNames returns the names of frames with non-zero DoF, in order.
190+ func (m * SimpleModel ) MoveableFrameNames () []string {
191+ var names []string
192+ for _ , f := range m .ordTransforms {
193+ if len (f .DoF ()) > 0 {
194+ names = append (names , f .Name ())
195+ }
196+ }
197+ return names
198+ }
199+
200+ // setOrdTransforms sets the internal ordered transforms and recomputes limits.
201+ func (m * SimpleModel ) setOrdTransforms (fs []Frame ) {
202+ m .ordTransforms = fs
203+ m .limits = []Limit {}
204+ for _ , transform := range m .ordTransforms {
205+ if len (transform .DoF ()) > 0 {
206+ m .limits = append (m .limits , transform .DoF ()... )
207+ }
208+ }
209+ }
210+
140211// GenerateRandomConfiguration generates a list of radian joint positions that are random but valid for each joint.
141212func GenerateRandomConfiguration (m Model , randSeed * rand.Rand ) []float64 {
142213 limits := m .DoF ()
@@ -176,7 +247,7 @@ func (m *SimpleModel) Transform(inputs []Input) (spatialmath.Pose, error) {
176247func (m * SimpleModel ) Interpolate (from , to []Input , by float64 ) ([]Input , error ) {
177248 interp := make ([]Input , 0 , len (from ))
178249 posIdx := 0
179- for _ , transform := range m .OrdTransforms () {
250+ for _ , transform := range m .ordTransforms {
180251 dof := len (transform .DoF ()) + posIdx
181252 fromSubset := from [posIdx :dof ]
182253 toSubset := to [posIdx :dof ]
@@ -191,36 +262,28 @@ func (m *SimpleModel) Interpolate(from, to []Input, by float64) ([]Input, error)
191262 return interp , nil
192263}
193264
194- // OrdTransforms gets the OrdTransforms.
195- func (m * SimpleModel ) OrdTransforms () []Frame {
196- return m .ordTransforms
197- }
198-
199265// InputFromProtobuf converts pb.JointPosition to inputs.
200266func (m * SimpleModel ) InputFromProtobuf (jp * pb.JointPositions ) []Input {
201267 inputs := make ([]Input , 0 , len (jp .Values ))
202268 posIdx := 0
203- for _ , transform := range m .OrdTransforms () {
269+ for _ , transform := range m .ordTransforms {
204270 dof := len (transform .DoF ()) + posIdx
205271 jPos := jp .Values [posIdx :dof ]
206272 posIdx = dof
207-
208273 inputs = append (inputs , transform .InputFromProtobuf (& pb.JointPositions {Values : jPos })... )
209274 }
210-
211275 return inputs
212276}
213277
214278// ProtobufFromInput converts inputs to pb.JointPosition.
215279func (m * SimpleModel ) ProtobufFromInput (input []Input ) * pb.JointPositions {
216280 jPos := & pb.JointPositions {}
217281 posIdx := 0
218- for _ , transform := range m .OrdTransforms () {
282+ for _ , transform := range m .ordTransforms {
219283 dof := len (transform .DoF ()) + posIdx
220284 jPos .Values = append (jPos .Values , transform .ProtobufFromInput (input [posIdx :dof ]).Values ... )
221285 posIdx = dof
222286 }
223-
224287 return jPos
225288}
226289
@@ -271,22 +334,11 @@ func (m *SimpleModel) DoF() []Limit {
271334 return m .limits
272335}
273336
274- // SetOrdTransforms sets the ordTransforms.
275- func (m * SimpleModel ) SetOrdTransforms (fs []Frame ) {
276- m .ordTransforms = fs
277- m .limits = []Limit {}
278- for _ , transform := range m .ordTransforms {
279- if len (transform .DoF ()) > 0 {
280- m .limits = append (m .limits , transform .DoF ()... )
281- }
282- }
283- }
284-
285337// MarshalJSON serializes a Model.
286338func (m * SimpleModel ) MarshalJSON () ([]byte , error ) {
287339 type serialized struct {
288340 Name string `json:"name"`
289- Model * ModelConfigJSON `json:"model"`
341+ Model * ModelConfigJSON `json:"model,omitempty "`
290342 Limits []Limit `json:"limits"`
291343 }
292344 ser := serialized {
@@ -301,7 +353,7 @@ func (m *SimpleModel) MarshalJSON() ([]byte, error) {
301353func (m * SimpleModel ) UnmarshalJSON (data []byte ) error {
302354 type serialized struct {
303355 Name string `json:"name"`
304- Model * ModelConfigJSON `json:"model"`
356+ Model * ModelConfigJSON `json:"model,omitempty "`
305357 Limits []Limit `json:"limits"`
306358 }
307359 var ser serialized
@@ -323,28 +375,14 @@ func (m *SimpleModel) UnmarshalJSON(data []byte) error {
323375 if ! ok {
324376 return fmt .Errorf ("could not parse config for simple model, name: %v" , ser .Name )
325377 }
326- m .SetOrdTransforms ( newModel .OrdTransforms ())
378+ m .ordTransforms = newModel .ordTransforms
327379 }
328380 m .baseFrame = baseFrame {name : frameName , limits : ser .Limits }
329381 m .modelConfig = ser .Model
330382
331383 return nil
332384}
333385
334- // ModelPieceFrames takes a list of inputs and returns a map of frame names to their corresponding static frames,
335- // effectively breaking the model into its kinematic pieces.
336- func (m * SimpleModel ) ModelPieceFrames (inputs []Input ) (map [string ]Frame , error ) {
337- poses , err := m .inputsToFrames (inputs , true )
338- if err != nil {
339- return nil , err
340- }
341- frameMap := map [string ]Frame {}
342- for _ , sFrame := range poses {
343- frameMap [sFrame .Name ()] = sFrame
344- }
345- return frameMap , nil
346- }
347-
348386// inputsToFrames takes a model and a list of joint angles in radians and computes the dual quaternion representing the
349387// cartesian position of each of the links up to and including the end effector. This is useful for when conversions
350388// between quaternions and OV are not needed.
@@ -353,7 +391,7 @@ func (m *SimpleModel) inputsToFrames(inputs []Input, collectAll bool) ([]*static
353391 return nil , NewIncorrectDoFError (len (inputs ), len (m .DoF ()))
354392 }
355393
356- poses := make ([]* staticFrame , 0 , len (m .OrdTransforms () ))
394+ poses := make ([]* staticFrame , 0 , len (m .ordTransforms ))
357395 // Start at ((1+0i+0j+0k)+(+0+0i+0j+0k)ϵ)
358396 composedTransformation := spatialmath .NewZeroPose ()
359397 posIdx := 0
@@ -505,15 +543,16 @@ func New2DMobileModelFrame(name string, limits []Limit, collisionGeometry spatia
505543 return nil , err
506544 }
507545
508- model := NewSimpleModel ( name )
546+ var frames [] Frame
509547 if len (limits ) == 3 {
510548 theta , err := NewRotationalFrame ("theta" , * spatialmath .NewR4AA (), limits [2 ])
511549 if err != nil {
512550 return nil , err
513551 }
514- model . SetOrdTransforms ( []Frame {x , y , theta , geometry })
552+ frames = []Frame {x , y , theta , geometry }
515553 } else {
516- model . SetOrdTransforms ( []Frame {x , y , geometry })
554+ frames = []Frame {x , y , geometry }
517555 }
518- return model , nil
556+
557+ return NewSerialModel (name , frames )
519558}
0 commit comments