Class: MSPhysics::JointConnectionTool

Inherits:
Entity
  • Object
show all
Defined in:
RubyExtension/MSPhysics/joint_connection_tool.rb

Overview

Since:

  • 1.0.0

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Entity

#inspect, #to_s

Constructor Details

#initializeJointConnectionTool

class << self

Since:

  • 1.0.0



450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 450

def initialize
  # Initialize variables
  @color = {
    :picked     => Sketchup::Color.new(0, 0, 255),
    :identical  => Sketchup::Color.new(0, 0, 255),
    :connected  => Sketchup::Color.new(0, 225, 0),
    :potential  => Sketchup::Color.new(200, 40, 250),
    :curve      => Sketchup::Color.new(250, 250, 15)
  }
  @line_width = {
    :picked     => 2,
    :identical  => 2,
    :connected  => 2,
    :potential  => 2,
    :curve      => 3
  }
  @line_stipple = {
    :picked     => '',
    :identical  => '-',
    :connected  => '',
    :potential  => '_',
    :curve      => ''
  }
  @text_opts = {
    :font => 'Ariel',
    :size => 11,
    :bold => false,
    :align => TextAlignLeft,
    :color => Sketchup::Color.new(255, 255, 255),
    :hratio => 0.6,
    :vratio => 1.5,
    :padding => 10
  }
  @note = {
    :bg_color => Sketchup::Color.new(0, 180, 255, 230),
    :time => nil,
    :duration => 20,
    :text => nil,
    :pos => Geom::Point3d.new(0,0,0),
    :min => Geom::Point3d.new(0,0,0),
    :max => Geom::Point3d.new(0,0,0),
  }
  @warn = {
    :time => nil,
    :duration => 3,
    :bg_color => Sketchup::Color.new(255, 10, 10, 200),
    :text => nil,
    :pos => Geom::Point3d.new(0,0,0),
    :min => Geom::Point3d.new(0,0,0),
    :max => Geom::Point3d.new(0,0,0),
  }
  @cursor_pos = Geom::Point3d.new(0,0,0)
  @pins = [0,1,1,3,3,2,2,0, 4,5,5,7,7,6,6,4, 0,4,1,5,2,6,3,7]
  @scale = 1.03
  @control_down = false
  @shift_down = false
  @cursor_id = MSPhysics::CURSORS[:select]
  @parent = nil
  @picked = nil
  @picked_type = nil
  @identical = []
  @connected = []
  @potential = []
  @fbdata = {}
  @fjdata = {}
  @all_connections = nil
end

Class Method Details

.activateBoolean

Activate joint connection tool.

Returns:

  • (Boolean)

    success

Since:

  • 1.0.0



11
12
13
14
15
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 11

def activate
  return false if @@instance
  Sketchup.active_model.select_tool(self.new)
  true
end

.active?Boolean

Determine whether joint connection tool is active.

Returns:

  • (Boolean)

Since:

  • 1.0.0



27
28
29
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 27

def active?
  @@instance ? true : false
end

.connect_joint_id(body, id) ⇒ Set<Integer>

Connect joint ID to a group/component.

Parameters:

  • body (Sketchup::Group, Sketchup::ComponentInstance)
  • id (Integer)

Returns:

  • (Set<Integer>)

    The new joint ids of a group/component.

Since:

  • 1.0.0



309
310
311
312
313
314
315
316
317
318
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 309

def connect_joint_id(body, id)
  ids = get_connected_joint_ids(body)
  if Sketchup.version.to_i < 14
    ids.insert(id)
  else
    ids.add(id)
  end
  body.set_attribute('MSPhysics Body', 'Connected Joints', ids.to_a)
  ids
end

.deactivateBoolean

Deactivate joint connection tool.

Returns:

  • (Boolean)

    success

Since:

  • 1.0.0



19
20
21
22
23
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 19

def deactivate
  return false unless @@instance
  Sketchup.active_model.select_tool(nil)
  true
end

.disconnect_joint_id(body, id) ⇒ Set<Integer>

Note:

Manually wrap the operation.

Disconnect joint ID from a group/component.

Parameters:

  • body (Sketchup::Group, Sketchup::ComponentInstance)
  • id (Integer)

Returns:

  • (Set<Integer>)

    The new joint ids of a group/component.

Since:

  • 1.0.0



325
326
327
328
329
330
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 325

def disconnect_joint_id(body, id)
  ids = get_connected_joint_ids(body)
  ids.delete(id)
  body.set_attribute('MSPhysics Body', 'Connected Joints', ids.to_a)
  ids
end

.get_all_connections(consider_world) ⇒ Array<Array>

Get all joint connections.

Parameters:

  • consider_world (Boolean)

    Whether to return MSPhysics::Body instances or Sketchup::Group/ Sketchup::ComponentInstance instances in place of bodies.

Returns:

  • (Array<Array>)

    [ [joint_ent, joint_tra, child_body, parent_body, joint_id], ... ]

Since:

  • 1.0.0



223
224
225
226
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 223

def get_all_connections(consider_world)
  data = get_connection_data(consider_world)
  get_conections_from_data(data[0], data[1])
end

.get_conections_from_data(fbdata, fjdata) ⇒ Object

Get all joint connections from connection data.

Parameters:

  • fbdata (Hash)

    A hash of body data: { body => [connected_ids_set, definition, ptra, ptra_inv, loc_bb_min, loc_bb_max, loc_bb_center], ... }

  • fjdata (Hash)

    A hash of joint data: { joint_id => [[joint_ent, joint_tra, parent_body], ...], ... }

Since:

  • 1.0.0



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 118

def get_conections_from_data(fbdata, fjdata)
  # Map all bodies with their connected joint IDs
  jid_to_connected_bodies = {} # { joint_id => { child_body => [ptra, ptra_inv, loc_bb_min, loc_bb_max, loc_bb_center, flag], ... }, ... }
  fbdata.each { |body, bdata|
    bdata[0].each { |jid|
      next unless fjdata[jid]
      data = jid_to_connected_bodies[jid]
      unless data
        data = {}
        jid_to_connected_bodies[jid] = data
      end
      data[body] = [bdata[2], bdata[3], bdata[4].to_a, bdata[5].to_a, bdata[6], false]
    }
  }
  # Make connections
  connections = []
  jid_to_connected_bodies.each { |jid, connected_bodies|
    fjdata[jid].each { |jdata|
      closest_body = nil
      closest_dist = nil
      closest_binside = false
      jorigin = jdata[1].origin
      connected_bodies.each { |body, bdata|
        next if body == jdata[2]
        # compute joint origin in local body's coordinates
        ljpt = jorigin.transform(bdata[1])
        # compute ljpt closest distance to body bounding box
        min = bdata[2]
        max = bdata[3]
        rfpt = Geom::Point3d.new(ljpt)
        binside = true
        for i in 0..2
          if (ljpt[i] < min[i])
            rfpt[i] = min[i]
            binside = false
          elsif (ljpt[i] > max[i])
            rfpt[i] = max[i]
            binside = false
          end
        end
        if binside
          dist = bdata[4].distance(ljpt).to_f
        else
          dist = rfpt.distance(ljpt).to_f
        end
        if closest_body.nil? || (binside && !closest_binside) || (binside == closest_binside && dist < closest_dist)
          closest_body = body
          closest_dist = dist
          closest_binside = binside
        end
      }
      next unless closest_body
      connections << [jdata[0], jdata[1], closest_body, jdata[2], jid]
      connected_bodies[closest_body][5] = true
      pbdata = connected_bodies[jdata[2]]
      pbdata[5] = true if pbdata
    }
    connected_bodies.each { |body, bdata|
      next if bdata[5]
      closest_jdata = nil
      closest_dist = nil
      closest_binside = false
      min = bdata[2]
      max = bdata[3]
      fjdata[jid].each { |jdata|
        next if body == jdata[2]
        # compute joint origin in local body's coordinates
        ljpt = jdata[1].origin.transform(bdata[1])
        # compute ljpt closest distance to body bounding box
        rfpt = Geom::Point3d.new(ljpt)
        binside = true
        for i in 0..2
          if (ljpt[i] < min[i])
            rfpt[i] = min[i]
            binside = false
          elsif (ljpt[i] > max[i])
            rfpt[i] = max[i]
            binside = false
          end
        end
        if binside
          dist = bdata[4].distance(ljpt).to_f
        else
          dist = rfpt.distance(ljpt).to_f
        end
        if closest_jdata.nil? || (binside && !closest_binside) || (binside == closest_binside && dist < closest_dist)
          closest_jdata = jdata
          closest_dist = dist
          closest_binside = binside
        end
      }
      next unless closest_jdata
      connections << [closest_jdata[0], closest_jdata[1], body, closest_jdata[2], jid]
    }
  }
  # Return all connections
  connections
end

.get_connected_bodies(joint, joint_parent) ⇒ Array

Get all bodies connected to a joint.

Parameters:

  • joint (Sketchup::Group, Sketchup::ComponentInstance)
  • joint_parent (Sketchup::Group, Sketchup::ComponentInstance, nil)

Returns:

  • (Array)

    [ connected_inst, ... ]

Since:

  • 1.0.0



246
247
248
249
250
251
252
253
254
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 246

def get_connected_bodies(joint, joint_parent)
  data = []
  get_all_connections(false).each { |jdata|
    if jdata[0] == joint && jdata[3] == joint_parent
      data << jdata[2]
    end
  }
  data
end

.get_connected_joint_ids(body) ⇒ Set<Integer>

Get all joint IDs connected to a group/component.

Parameters:

  • body (Sketchup::Group, Sketchup::ComponentInstance)

Returns:

  • (Set<Integer>)

Since:

  • 1.0.0



286
287
288
289
290
291
292
293
294
295
296
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 286

def get_connected_joint_ids(body)
  ids = body.get_attribute('MSPhysics Body', 'Connected Joints')
  ids = [] unless ids.is_a?(Array)
  ids_set = ::Set.new
  if Sketchup.version.to_i < 14
    ids.each { |id| ids_set.insert(id) if id.is_a?(Integer) }
  else
    ids.each { |id| ids_set.add(id) if id.is_a?(Integer) }
  end
  ids_set
end

.get_connected_joints(body) ⇒ Array<Array>

Get all joints connected to a body.

Parameters:

  • body (Sketchup::Group, Sketchup::ComponentInstance)

Returns:

  • (Array<Array>)

    [ [joint_ent, joint_tra, joint_parent], ... ]

Since:

  • 1.0.0



232
233
234
235
236
237
238
239
240
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 232

def get_connected_joints(body)
  data = []
  get_all_connections(false).each { |jdata|
    if jdata[2] == body
      data << [jdata[0], jdata[1], jdata[3]]
    end
  }
  data
end

.get_connection_data(consider_world) ⇒ Array<(Hash, Hash)>

Get all connection data.

Parameters:

  • consider_world (Boolean)

    Whether to return MSPhysics::Body instances or Sketchup::Group/ Sketchup::ComponentInstance instances in place of bodies.

Returns:

  • (Array<(Hash, Hash)>)

    An array of two values.

    • The first element is a Hash of body data: { body => [connected_ids_set, definition, ptra, ptra_inv, loc_bb_min, loc_bb_max, loc_bb_center], ... }

    • The second element is a Hash of joint data: { joint_id => [[joint_ent, joint_tra, parent_body], ...], ... }

Since:

  • 1.0.0



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 40

def get_connection_data(consider_world)
  if consider_world
    sim_inst = MSPhysics::Simulation.instance
    unless sim_inst
      raise(StandardError, "Simulation must be active in order to obtain body connection data!", caller)
    end
  end
  # Gather all bodies, joints, and their data.
  fbdata = {} # { body => [loc_bb, ptra, ptra_inv, connected_ids_set], ... }
  fjdata = {} # { joint_id => [[joint_ent, joint_tra, parent_body], ...], ... }
  bb_map = {}
  Sketchup.active_model.entities.each { |ent|
    next if !ent.is_a?(Sketchup::Group) && !ent.is_a?(Sketchup::ComponentInstance)
    ent_type = ent.get_attribute('MSPhysics', 'Type', 'Body')
    if ent_type == 'Body'
      next if ent.get_attribute('MSPhysics Body', 'Ignore', false)
      if consider_world
        body = sim_inst.find_body_by_group(ent)
        next unless body
      else
        body = ent
      end
      d = AMS::Group.get_definition(ent)
      bb = bb_map[d]
      unless bb
        bb = AMS::Group.get_bounding_box_from_faces(ent, true, nil, &MSPhysics::Collision::ENTITY_VALIDATION_PROC)
        bb_map[d] = bb
      end
      ptra = ent.transformation
      fbdata[body] = [get_connected_joint_ids(ent), d, ptra, ptra.inverse, bb.min, bb.max, bb.center]
      cents = ent.is_a?(Sketchup::ComponentInstance) ? ent.definition.entities : ent.entities
      cents.each { |cent|
        next if !cent.is_a?(Sketchup::Group) && !cent.is_a?(Sketchup::ComponentInstance)
        next if cent.get_attribute('MSPhysics', 'Type', 'Body') != 'Joint'
        jtype = cent.get_attribute('MSPhysics Joint', 'Type')
        next if !jtype.is_a?(String) || !MSPhysics::JOINT_NAMES.include?(jtype)
        jid = cent.get_attribute('MSPhysics Joint', 'ID', nil)
        next unless jid.is_a?(Integer)
        jtra = ptra * cent.transformation
        next unless AMS::Geometry.is_matrix_uniform?(jtra)
        xaxis = jtra.xaxis
        xaxis.reverse! if AMS::Geometry.is_matrix_flipped?(jtra)
        jtra = Geom::Transformation.new(xaxis, jtra.yaxis, jtra.zaxis, jtra.origin)
        jdata = [cent, jtra, body]
        jdatas = fjdata[jid]
        if jdatas
          jdatas << jdata
        else
          fjdata[jid] = [jdata]
        end
      }
    elsif ent_type == 'Joint'
      jtype = ent.get_attribute('MSPhysics Joint', 'Type')
      next if !jtype.is_a?(String) || !MSPhysics::JOINT_NAMES.include?(jtype)
      jid = ent.get_attribute('MSPhysics Joint', 'ID', nil)
      next unless jid.is_a?(Integer)
      jtra = ent.transformation
      next unless AMS::Geometry.is_matrix_uniform?(jtra)
      xaxis = jtra.xaxis
      xaxis.reverse! if AMS::Geometry.is_matrix_flipped?(jtra)
      jtra = Geom::Transformation.new(xaxis, jtra.yaxis, jtra.zaxis, jtra.origin)
      jdata = [ent, jtra, nil]
      jdatas = fjdata[jid]
      if jdatas
        jdatas << jdata
      else
        fjdata[jid] = [jdata]
      end
    end
  }
  return [fbdata, fjdata]
end

.get_curve_length(joint, joint_parent, loop = false) ⇒ Numeric

Get curve length of a CurvySlider or a CurvyPiston joint.

Parameters:

  • joint (Sketchup::Group, Sketchup::ComponentInstance)
  • joint_parent (Sketchup::Group, Sketchup::ComponentInstance, nil)
  • loop (Boolean) (defaults to: false)

Returns:

  • (Numeric)

    Curve length in inches.

Since:

  • 1.0.0



432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 432

def get_curve_length(joint, joint_parent, loop = false)
  length = 0.0
  last_pt = nil
  pts = get_points_on_curve(joint, joint_parent)
  pts.each { |pt|
    if last_pt
      length += last_pt.distance(pt)
    end
    last_pt = pt
  }
  if loop && last_pt
    length += pts[0].distance(last_pt)
  end
  length
end

.get_joints_by_id(joint_id) ⇒ Array

Get joint by its id.

Parameters:

  • joint_id (Integer)

Returns:

  • (Array)

    An array of joint data. Each joint data represents an array of two elements. The first element of joint data is joint entity. The second element of joint data is joint parent entity.

Since:

  • 1.0.0



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 261

def get_joints_by_id(joint_id)
  data = []
  Sketchup.active_model.entities.each { |ent|
    next if !ent.is_a?(Sketchup::Group) && !ent.is_a?(Sketchup::ComponentInstance)
    type = ent.get_attribute('MSPhysics', 'Type', 'Body')
    if type == 'Body'
      next if ent.get_attribute('MSPhysics Body', 'Ignore', false)
      cents = ent.is_a?(Sketchup::ComponentInstance) ? ent.definition.entities : ent.entities
      cents.each { |cent|
        next if !cent.is_a?(Sketchup::Group) && !cent.is_a?(Sketchup::ComponentInstance)
        next if cent.get_attribute('MSPhysics', 'Type', 'Body') != 'Joint'
        id = cent.get_attribute('MSPhysics Joint', 'ID')
        data << [cent, ent] if id == joint_id
      }
    elsif type == 'Joint'
      id = ent.get_attribute('MSPhysics Joint', 'ID')
      data << [ent, nil] if id == joint_id
    end
  }
  data
end

.get_points_on_curve(joint, joint_parent) ⇒ Array<Geom::Point3d>

Get all points on a CurvySlider or a CurvyPiston joint.

Parameters:

  • joint (Sketchup::Group, Sketchup::ComponentInstance)
  • joint_parent (Sketchup::Group, Sketchup::ComponentInstance, nil)

Returns:

  • (Array<Geom::Point3d>)

    An array of points in global space.

Since:

  • 1.0.0



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 356

def get_points_on_curve(joint, joint_parent)
  # Find the closest edge
  closest_dist = nil
  closest_edge = nil
  AMS::Group.get_entities(joint).each { |e|
    next unless e.is_a?(Sketchup::Edge)
    dist1 = e.start.position.distance(ORIGIN)
    dist2 = e.end.position.distance(ORIGIN)
    dist = dist1 < dist2 ? dist1 : dist2
    if closest_dist.nil? || dist < closest_dist
      closest_dist = dist
      closest_edge = e
      break if closest_dist < 1.0e-8
    end
  }
  # Verify
  return Array.new() unless closest_edge
  # Preset data
  edges = { closest_edge => 0 }
  edges[closest_edge] = 0
  vertices = {}
  # Get all preceding edge vertices
  edge = closest_edge
  v = edge.start
  vertices[0] = v
  count = -1
  while true
    next_edge = v.edges[0]
    if next_edge == edge
      if v.edges[1]
        next_edge = v.edges[1]
      else
        break
      end
    end
    break if edges.has_key?(next_edge)
    v = next_edge.other_vertex(v)
    vertices[count] = v
    edges[next_edge] = count
    edge = next_edge
    count -= 1
  end
  # Get all consequent edge vertices
  edge = closest_edge
  v = edge.end
  vertices[1] = v
  count = 2
  while true
    next_edge = v.edges[0]
    if next_edge == edge
      if v.edges[1]
        next_edge = v.edges[1]
      else
        break
      end
    end
    break if edges.has_key?(next_edge)
    v = next_edge.other_vertex(v)
    vertices[count] = v
    edges[next_edge] = count
    edge = next_edge
    count += 1
  end
  # Return sorted vertices
  tra = joint.transformation
  if joint_parent
    tra = joint_parent.transformation * tra
  end
  vertices.keys.sort.map { |i| vertices[i].position.transform(tra) }
end

.set_connected_joint_ids(body, ids) ⇒ Object

Set connected joint IDs of a group/component.

Parameters:

  • body (Sketchup::Group, Sketchup::ComponentInstance)
  • ids (Array<Integer>, Set<Integer>)

Since:

  • 1.0.0



301
302
303
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 301

def set_connected_joint_ids(body, ids)
  body.set_attribute('MSPhysics Body', 'Connected Joints', ids.to_a)
end

.toggle_connect_joint_id(body, id) ⇒ Set<Integer>

Note:

Manually wrap the operation.

Toggle connect joint ID to a group/component.

Parameters:

  • body (Sketchup::Group, Sketchup::ComponentInstance)
  • id (Integer)

Returns:

  • (Set<Integer>)

    The new joint ids of a group/component.

Since:

  • 1.0.0



337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'RubyExtension/MSPhysics/joint_connection_tool.rb', line 337

def toggle_connect_joint_id(body, id)
  ids = get_connected_joint_ids(body)
  if ids.include?(id)
    ids.delete(id)
  else
    if Sketchup.version.to_i < 14
      ids.insert(id)
    else
      ids.add(id)
    end
  end
  body.set_attribute('MSPhysics Body', 'Connected Joints', ids.to_a)
  ids
end