How can we have a component as code inside our plugin?

Dear Friends,
Is it possible to have a component inside our plugin? Do we need to change the component to an array as we do for the face? Your help will be highly appreciated.

It is not entirely clear what you want.

What and how you “do for the faces”…?
What do you mean by changing your face to an array? Or what do you want to do with “change the component to an array”?

A component is not inside a plugin but it is in Model.

For sure, you can create a component instance with ruby e.g. like:
Entitiesl#add_instance-instance_method
or
Group#to_component-instance_method

The Model#definitions method retrieves a definition list containing all of the component definitions in the model.
The ComponentDefinition#instances-instance_method is used to return any array of ComponentInstancesfor this ComponentDefinition.

I want to change the component to an array.

This example component comes from “Bed.skp” but I wish Bed will be inside our plugin code not as an external file.

I too am not sure I understand what you want, but…

The Ruby API handles components at a lower level than the GUI. It explicitly deals with ComponentDefinition objects and ComponentInstance objects. A ComponentDefinition object has an Entities collection that you can convert to an Array using #to_a (if that is what you meant). There is no implicit conversion to or from Array.

1 Like

Implicit conversion is also important for me otherwise Array will be useless. It seems my only way is to download the component from an external file.

Your Extension with have its own dedicated subfolder.

Within that make a new subfolder named say ‘Compos’.
Put your SKP file[s] in there - remember that these must be saved as a version which will be compatible with the oldest version of SketchUp supported by your extension…

Now you can easily make a reference to that Compos folder and the component’s SKP you need.
You can then load that component’s SKP into your model.definitions - checking that it doesn’t already exist - e.g. the extension was run before and so it has already loaded the component…

Your array approach would only bloat your extension’s file size, when a separate SKP will suffice…

1 Like

As slbaumgartner say there is no such a thing to convert component to array.

Even I do not understand what do you mean, or how do you convert t face to array…as you stated in your first post. I’m very curious how do you explain this?

Anyway, here’s what I think you’re looking for. Two alternatives:

  1. Create your geometry by code and “save” to DefinitionList e.g like this:
model = Sketchup.active_model
entities = model.active_entities

#Create a group...
group = entities.add_group
#..and add your geometry to it
face = group.entities.add_face([0,0,0],[1,0,0],[1,1,0],[0,1,0])
face.pushpull(-1)

# Convert the group to component instance
my_component_instance = group.to_component
# Assign variable to definition of this instance
my_comp_definition = my_component_instance.definition
# You can e.g. give name.
my_comp_definition.name = "The component"

# since the component is in a definitions list
# https://ruby.sketchup.com/Sketchup/DefinitionList.html
#
# You can erase this instance, if you want to "hide" at first...: 
my_component_instance.erase!

# Then you can place it with e.g.
# https://ruby.sketchup.com/Sketchup/Model.html#place_component-instance_method
repeat = true #(false  if you want only place once)
model.place_component(my_comp_definition, repeat)

#Alternatively you can insert by code e.g
point = Geom::Point3d.new 10,20,30
transform = Geom::Transformation.new point
my_component_instance = entities.add_instance(my_comp_definition, transform)


  1. As TIG suggested: Create your component in a separate file e.g. MAJ_Componet_01.skp
    ( make sure that you saved in earliest version of SU that your extension will support.)
    Place your file to your extension folder structure something like this:
    MAJ_Componet\MAJ_Componet_Data.rb
    MAJ_Componet\MAJ_Componet_01.skp
    MAY_Componet.rb
    (Or as TIG told the component can go into subfolder too. But my example below is without subfolder)

Then

model = Sketchup.active_model
entities = model.active_entities
path = File.join(File.dirname(__FILE__), "MAJ_Componet_01.skp")
definitions = model.definitions
my_comp_definition = definitions.load(path)

#Similar as above
repeat = true #(false  if you want only plece once)
model.place_component(my_comp_definition, repeat)

#Or
point = Geom::Point3d.new 10,20,30
transform = Geom::Transformation.new point
instance = entities.add_instance(my_comp_definition, transform)

I cannot explain it well. For sure converting face to points is so simple for you. My following code can explain it more…

face1.vertices.each do |v|
  pt = v.position.to_a
  @shape1 << [pt.x.round(6), pt.y.round(6), pt.z.round(6)]
end

Also, we can have some 3D points in our code and convert them to face. Mr. Tig solution is logical and you explain it well but it is not what I was looking for.

Just trying to help with communication; is what you want an array OF the component? Like a code that can array a certain component how you wish?

I don’t so. I think by the replies here that he wishes to Marshal (ie, Serialize) the SketchUp object into primitive description that can be reconstituted later.
But the SketchUp Ruby classes are not compatible with the core Marshal class. So he would need to write his own marshalling methods.

1 Like

Yes sir, an array of the component that can convert it to the component. We can do it for the face.

Thank you for understanding.

You can also do something like …


  def face_to_a(face)
    face.vertices.map(&:position).map { |pt|
      pt.to_a.map! {|coord| coord.round(6) } 
    }
  end

However, this is problematic. You are not taking into account faces with holes (inner loops) and the face vertex winding order.

Ie, face.vertices may return an array of Vertex objects that are not in order or not in the correct winding order to recreate the face so that it’s normal is pointing in the correct direction. (Ie you could get inside out faces.)

1 Like

So to continue … you need to instead iterate the face.loops (subtracting the face.outer_loop to get just inner loops,) and use the loop.vertices which will return the vertices in winding order.

You might also save the face.normal vector as an array, just to later be sure the face is recreated facing the correct direction.

I know Dan, We should draw the outer loop and take away inner loops. Also, we should save material separately. but it is possible to convert a face to array then convert array to face.

Of course, because the Entities#add_face method accepts an array of points, and the API accepts arrays of numerics in place of points.

1 Like

In fact, the component (model too) also consists of faces and edges. If you iterate all faces on particular component and get in the right order, you can do it.

Of course a lot of other things you have to take into account, like transformations, materilas, UVs etc.
Here is an other kind of methods you can deal with faces:

The code snippet create an array from existing component and copy to an other place. Then return this array (print to console.)
A created componet have a triangulated faces, but sould not be a problem to remove the extra edges…

References:

  • The PolygonMesh class contains methods to create polygon mesh structures.

  • The #add_faces_from_mesh method is used to add Face objects to the collection of entities from a PolygonMesh.

#dirty
#########
def copy_comp
  model = Sketchup.active_model
  entities = model.active_entities
  definitions = model.definitions
  faces = definitions.first.entities.grep(Sketchup::Face)

#create "copy array"
  faces_polygons_ponits = []
  faces.each{|face|
    (1..face.mesh.count_polygons).each{|i|
      faces_polygons_ponits<<[face.mesh.polygon_points_at(i)]
    }
  }

#recreate
  componentdefinition = definitions.add "My_comp_copy"
  cd_ents = componentdefinition.entities
  faces_polygons_ponits.each{|face_polygons|
    mesh = Geom::PolygonMesh.new
    face_polygons.each{|poly|
      mesh.add_polygon(poly)
    }
    cd_ents.add_faces_from_mesh(mesh,0)
  }

#insert
  point = Geom::Point3d.new 1,2,3
  transform = Geom::Transformation.new point
  my_component_instance = entities.add_instance(componentdefinition, transform)
  faces_polygons_ponits
end
copy_comp

1 Like

I did not suggest the mesh route as I cannot see a good way to keep UVs, materials and other properties (attributes, glued instances, etc.) matched to these “serialized faces”.


Perhaps it would be better to serialize to a property hash instead of an array:

face_hash = {
  :normal => [0,1,0], # vector array
  :outer_loop => [  # array of point arrays
    # each array is an array of 3 numeric coordinates
  ],
  :inner_loops => [ # array of loops
    # each loop is an array of point arrays
  ],
  :hidden => false, # or true
  :tag_layer => "", # layer name
  :cast_shadows => true, # or false
  :receive_shadows => true, # or false
  :material => {
    :front => {
      :name => "", # material name
      :path => "", # path string to SKM file
      :uv   => [ # array of UV coordinate arrays
      ],
      :projection => [1,0,0] # vector array or nil
    },
    :back  => {
      :name => "", # material name
      :path => "", # path string to SKM file
      :uv   => [ # array of UV coordinate arrays
      ],
      :projection => [1,0,0] # vector array or nil
    }
  },
  :dictionaries => [ # array of dictionary hashes
    {
      :name => "dictionary name",
      :attributes => {
        :key_name1 => "attr value",
        :key_name2 => "attr value"
        # etc.
      },
      :dictionaries => [ # array of dictionary hashes
        # may be empty unless there are child dictionaries
      ]
    },
    # etc.
  ],
  :gluing => [
    # array of glued instance persistent IDs
  ]
}

:question:

1 Like

Yea, it depends what details need to be there, just wanted to show an other way… but the hash would be surely more effective, and more easy to “read”.
Actually we can write and entirely “exporter/importer”, the question is whether it is worth it.

In any case if you have special material, texture applied to the component, that file must be attached to the code anyway. (as far as I know.)
In this case, it is easier to include the component file that can contain “everything”.

!?

1 Like

I agree, … which is why TIG’s usual suggestion has the most merit when this kind of question comes up every few years or so.

2 Likes