Transformation chain (Image, group)

I have implemented an skp importer which works for 99% of skp objects even very complex one and fail with that “simple” one:

Basically, it is a very simple skp
-Model
– Group x Scale Transformation
— Image x Scale Transformation
– Face

With SDK (skp2xml sample) I’m unable to find in this model the link between the Group and the Image, it is like
-Model
– Group x Scale Transformation
– Image x Scale Transformation
– Face

So I can’t compose both transformation correctly and don’t know where I can look in.

BusinessCard3.skp (2.0 MB)

There is a special set of SDK functions for Image handling:

http://extensions.sketchup.com/developer_center/sketchup_c_api/sketchup/image_8h.html

It doesn’t look much like a business card:

A Sketchup importer is in “C”, I will answer from the perspective of the Ruby API.

Before I answer your question, I will comment on the model arrangement.
The tan colored square is a Group that contains an Image. Perhaps you wanted the group of the copper colored square. The copper colored scalloped roofing tile is loose geometry consisting of edges and a face. It has not been grouped. There is no associated transformation with its edges and face. Instead the edge start and end method return vertex objects that have the position of them in absolute coordinates.

The tan colored square is a Sketchup “Image” object nested inside of a group. That group’s transformation could be referenced with the Ruby API by:

group_t = Sketchup.active_model.entities[6].transformation

That transformation is unscaled.
The tan colored image object is the only entity in the group, which is rare. The tan colored Image object’s transformation can be referenced with:

image_t = Sketchup.active_model.entities[6].entities[0].transformation

The image’s transformation is scaled.

The transformation that converts the Image definition local coordinates to world coordinates would be:

world_t = group_t * image_t
1 Like

Many thanks for you answer.
Is there a way to output in Ruby like “image_t .print()” to display all values of an object

About my post :
1/ it was a visit card but I removed it before publishing the post for evident reason
2/ the file is a simplier version of this model that I want to import. This is simplier for me to debug.

I found that the reason for a bad import was when an image coming from a bitmap file is inside a group.
I am currently unable to find the link betewwen Group and image with C API based on skp2xml SDKsample.
So I have both (image and group) Trsf matrix with correct values, but I can’t assume I need to combine them because I do not find the way to find Image SOUNDCREAM_RAGNO is child of Group#1.

By doing this, whan importing, the image is not at the right position and right scale as it appears in this is the import result As you can see, 3 images on 5 are correct but the two in the middle are not. Seems also this Lisboa model does not have same design way on each square

In Visual Studio, Group#1 does not seem to have any child… so I am lost

Thank for your help. I already have all images info with the API.
My problem is more about model hierarchy (Group-Image)

Not sure how exactly MapInstanceInfoFromGroup works but you (probably) inside of it should also check:

size_t numImages = 0;
SUEntitiesGetNumImages(entities, &numImages)

std::vector <SUImageRef> images(numImages, SU_INVALID);
SUEntitiesGetImages(entitiesRef, numImages, &images[0], &numImages);

And then you’ll need to multiply the image instance transform by the parent transform to get world coords (like in Ruby example)
The image polygon size you can get by SUImageGetDimensions

On your screenshots group is empty what (probably) means that you didn’t read Image-Typed instances from group entities.

I am using

  if (options_.export_images()) {
	  size_t num_images = 0;
	  SU_CALL(SUEntitiesGetNumImages(entities, &num_images));
	  if (num_images > 0) {
		  std::vector<SUImageRef> images(num_images);
		  SU_CALL(SUEntitiesGetImages(entities, num_images, &images[0], &num_images));
		  for (size_t i = 0; i < num_images; i++) {
			  WriteImage(images[i], entities_info);
		  }
	  }
  }

And then :

void CXmlExporter::WriteImage(SUImageRef image, XmlEntitiesInfo &entities_info) {

if (SUIsInvalid(image))
	return;

//SUDrawingElementRef drawing_element = SUImageToDrawingElement(image);
//SUEntityRef entity = SUImageToEntity(image);
double width(0.0);
double height(0.0);
SUImageGetDimensions(image, &width, &height);

size_t pixel_width(0);
size_t pixel_height(0);
SUImageGetPixelDimensions(image, &pixel_width, &pixel_height);

size_t data_size_in_byte(0);
size_t bits_per_pixel(0);
SUImageGetDataSize(image, &data_size_in_byte, &bits_per_pixel);

//data_size_in_byte = (pixel_width * pixel_height * bits_per_pixel);
std::vector<SUByte> pixel_data(data_size_in_byte);

SUTransformation trsf;
CTransform image_trsf;
SUImageGetTransform(image, &trsf);
image_trsf.SetMatrix(trsf.values);

CSUString file_name;
SUImageGetFileName(image, file_name);
SUResult answer = SUImageGetData(image, data_size_in_byte, &pixel_data[0]);
if (answer != SU_ERROR_NONE)
	return;

//Stride is the number of bytes allocated for one scanline of the bitmap
//int stride = ((pixel_width * 32 + 31) & ~31) / 32;

SUTextureRef texture = SU_INVALID;
answer = SUTextureCreateFromImageData(&texture, pixel_width, pixel_height, bits_per_pixel, &pixel_data[0]);
if (answer != SU_ERROR_NONE)
	return;
 
// Create Material == texture
XmlMaterialInfo image_material;
image_material.name_ = StringHelper::GetFileNameWithoutExtension(file_name.ansi());
image_material.has_texture_ = true;
image_material.has_color_ = false;
image_material.outline_invisible_ = true; // special case
image_material.color_ = SUColor();
image_material.has_alpha_ = false;
image_material.alpha_ = 1.0;
//if (cat_.ContainsTextureName(file_name.ansi()))
image_material.texture_file_name_ = StringHelper::GetFileName(file_name.ansi());//;cat_.GetMappedTextureFilePath(file_name.ansi());

// Write newly created Material top be mapped on a rectangular Face
WriteMaterial(image_material);
// Write Texture raw in File (textures\temp dir)

#ifndef WRITE_IMAGES_SOMETIMES_SKEWED
	WriteTextureFile(texture, image_material);
#endif

SUTextureRelease(&texture);

// Create Geometry ==  rectangular face with (u,v)
XmlFaceInfo anImageRectoFace;
XmlFaceVertex Pt0, Pt1, Pt2, Pt3;

// BE CAREFUL IMAGE DIMENSIONS MUST BE IN PIXELS !
// It seems that Transformation hold a scale factor itself
Pt0.vertex_ = CPoint3d(0.0, 0.0, 0.0);
Pt1.vertex_ = CPoint3d(0.0, pixel_height, 0.0);
Pt2.vertex_ = CPoint3d(pixel_width, pixel_height, 0.0);
Pt3.vertex_ = CPoint3d(pixel_width, 0.0, 0.0);

Pt0.texture_coord_ = CPoint3d(0.0, 0.0, 0.0);
Pt1.texture_coord_ = CPoint3d(0.0, 1.0, 0.0);
Pt2.texture_coord_ = CPoint3d(1.0, 1.0, 0.0);
Pt3.texture_coord_ = CPoint3d(1.0, 0.0, 0.0);

anImageRectoFace.outer_loop_vertices_.push_back(Pt0);
anImageRectoFace.outer_loop_vertices_.push_back(Pt1);
anImageRectoFace.outer_loop_vertices_.push_back(Pt2);
anImageRectoFace.outer_loop_vertices_.push_back(Pt3);

anImageRectoFace.bbox_.min_point = (SUPoint3D) Pt0.vertex_;
anImageRectoFace.bbox_.max_point = (SUPoint3D) Pt2.vertex_;

anImageRectoFace.material_info.name_ = image_material.name_;
anImageRectoFace.nb_uv_coord_ = (long)anImageRectoFace.outer_loop_vertices_.size();
anImageRectoFace.has_texture_ = true;

// Build BackFace from Front face
XmlFaceInfo anImageVersoFace = anImageRectoFace;
std::reverse(anImageVersoFace.outer_loop_vertices_.begin(), anImageVersoFace.outer_loop_vertices_.end());

//  add two "artificial" faced to our entities
XmlComponentDefinitionInfo imageDefinition;
imageDefinition.bbox_ = anImageRectoFace.bbox_;
imageDefinition.name_ = image_material.name_;
imageDefinition.entities_.faces_.push_back(anImageRectoFace);
imageDefinition.entities_.faces_.push_back(anImageVersoFace);

XmlComponentInstanceInfo imageInstance;
imageInstance.definition_name_ = imageDefinition.name_;
imageInstance.transform_ = image_trsf;

// Finally add Definition and instance to our model
model_info_.definitions_.push_back(imageDefinition);
model_info_.entities_.component_instances_.push_back(imageInstance);
model_info_.IncrementInstanceCount(imageDefinition.name_);

}

But where you multiply:

SUTransformation trsf;
CTransform image_trsf;
SUImageGetTransform(image, &trsf); // <- this is local transform
image_trsf.SetMatrix(trsf.values);

by group (Group#1) transform?

You are passing XmlEntitiesInfo &entities_info which probably contains parent transform but you didn’t used it.

model_info_.entities_.component_instances_.push_back(imageInstance);
I’m not sure, but it looks like you are adding this image in to the model level not Group#1 level.

Image vertex (of course if the Image is child of the group) should be transformed by:

worldTransform (probaby identity) * groupTransform (image parent I'm guessing: Group#1) * image_trsf

My problem is only that I am unable to find where is the link between group#1 and image so I can’t combine the two transformations that I already have. Group#1 seems empty although it holds image inside it.

SUEntitiesRef modelEntitiesRef = SU_INVALID;
SUModelGetEntities(modelRef, &modelEntitiesRef);

SUEntitiesGetNumGroups(modelEntitiesRef, &numGroups);
std::vector<SUGroupRef> groups(numGroups, SU_INVALID);
SUEntitiesGetGroups(modelEntitiesRef, numGroups, &groups[0], &numGroups);

for (auto &groupRef : groups)
{
  SUEntitiesRef groupEntitiesRef = SU_INVALID;
  SU_CALL(SUGroupGetEntities(groupRef, &groupEntitiesRef));

  SUEntitiesGetNumImages(groupEntitiesRef, &numImages);
  std::vector <SUImageRef> images(numImages, SU_INVALID);
  SUEntitiesGetImages(groupEntitiesRef, numImages, &images[0], &numImages);

  for (auto &imageRef : images)
   // process images - now the parent transform is the groupTransform
}

Every group should have they entities, accessible by SUGroupGetEntities or this should be the same by SUGroupGetDefinition and then by SUComponentDefinitionGetEntities

1 Like

A group is a special kind of component instance (that is hidden from the “In Model” component browser.)

So (being instance objects) groups do not own child entities, their definitions do.
Groups (like all component instances) have a component definition (whose “group?” flag is set true.)

In the Ruby API, there is a wrapper method: group.entities() that is a shortcut for group.definition.entities(). (The API was written to mimic the GUI interface, that obscures the fact that groups are really special component instances with definitions.)

In the C API, there is also the wrapper function that allows the direct access of a group definition’s entities collection, directly from the group instance. @RaPIT shows the use of SUGroupGetEntities() in the example above.

In both APIs, the return from these methods / functions is a reference to an Entities collection object, which is the same for the root level of the model object, and any component definition object (be it a component definition, a group definition or an Image definition.)

So FYI, in the SketchUp DOM, Images are special component instances also, that have a definition whose “image?” flag is set true. But be careful when accessing image objects. You should only do what is exposed to the APIs, as some image definition manipulation can corrupt the model file and crash the SketchUp application. (For this reason both APIs try to make it difficult to modify an Image object’s definition, which is meant to protect the SKP file data and prevent SketchUp crashes.)
However, accessing the image data via established API functions should not cause a problem.

2 Likes

Many thanks to all experts who answered me. I’m close to 99.9% of success with your help.

For those who need some info :

I have changed some part of skp2xml source code (see below) to pass as a parameter the group Trsf.
It works well on Lisboa sample.
Look Great

Still have a problem for Business card sample bacause I will add same image twice

Should I remove one call ?

So I put the code for anyone who want to achieve same stuff :

void CXmlExporter::WriteGroups(SUEntitiesRef entities, XmlEntitiesInfo &entities_info) {

...
    			  SUTransformation trsf;
    			  SU_CALL(SUGroupGetTransform(group, &trsf));
    			  group_info.transform_.SetMatrix(trsf.values);
    			  WriteComponentDefinition(group_component_definition);
    			  WriteEntities(group_entities, group_info.entities_, group_info.transform_);

void CXmlExporter::WriteEntities(SUEntitiesRef entities, XmlEntitiesInfo &entities_info, const CTransform &trsf) 

void CXmlExporter::WriteGeometries(SUEntitiesRef entities,  XmlEntitiesInfo &entities_info, const CTransform &trsf) 

void CXmlExporter::WriteImage(SUImageRef image, XmlEntitiesInfo &entities_info, const CTransform &group_trsf) 
{
...
	SUTransformation suTrsf;
	SUImageGetTransform(image, &suTrsf);

	CTransform image_trsf;
	image_trsf.SetMatrix(suTrsf.values);
	
	CTransform final_trsf;
	final_trsf = (group_trsf * image_trsf);
...

	XmlComponentInstanceInfo imageInstance;
	imageInstance.definition_name_ = imageDefinition.name_;
	imageInstance.transform_ = final_trsf;
....
	// Finally add Definition and instance to our model
	model_info_.definitions_.push_back(imageDefinition);
	model_info_.entities_.component_instances_.push_back(imageInstance);
	model_info_.IncrementInstanceCount(imageDefinition.name_);
}

What this doing?

Component definition should be threated as a “geometry template” or “class”, if you are going to export existing geometry you should write only GroupInstances / ComponentInstances.

In your example it looks a bit like you exported content of group definition at origin.

1 Like

Hello Piotr,

Thanks for your time.

If I remove the line, it works for the visit card sample but all component based files are not any more imported.
@DanRathbun said “So FYI, in the SketchUp DOM, Images are special component instances also, that have a definition whose “image?” flag is set true.” So image is written because of presence in Component Definition and also it presence in Group itself.

Geometry is written once only for definition and used by all instances like in SU.
For images I don’t know what to do to avoid it twice.

What I have done for my import is using skp_to_xml sample. Basicallly it fills C++ classes.
so the flow is : skp → xml classes → save in private format

In the SDK sample these classes are normally saved as xml. I am using them as helper.

The code is taken xmlexporter.cpp in C SDK skp_to_xml sample “SketchUpSDK\samples\C++\skp_to_xml\common”

xmlexporter.zip (4.8 KB)

I must admit I am a little bit lost on skp DOM. Question I’m usually wondering is when and where can I have the info (material, parent object, ) I need at some moment for complex models with groups and components or even both and in both direction Groups in Components, Components in Groups where material is on Component or Group and not on the end face itself. I am very close now to make it working well although.

Unfortunately, the best way to understand SU hierarchy is to experiment in SU with ruby console and Ruby API.

Sketchup → Window → Ruby Console and then http://ruby.sketchup.com

Materials rule is (as far as I know, and of course if this is not the “Color by Layer” mode :blush: ) :

  • use face material if it was defined (SUFaceGetFrontMaterial / SUFaceGetBackMaterial)
  • if not, use first material that you found on the group/component level when you traversing up from a face to the model level, (SUDrawingElementGetMaterial)
  • if not, use the default one.

The simplest way, (I think it should work) try to treat Images as a “special type” of face, convert it to your-face-type.

Then this shouldn’t be necessary:

And this looks a bit like you’re adding any Images (wherever it is) to the root entities of the model.

1 Like

Many thanks for all these informations

I have tried your proposal :
`model_info_.definitions_.push_back(imageDefinition);
But in my case it can’t work.

So because of your help and your comments, here is how I have handled the problem :

void CXmlExporter::WriteComponentDefinition(SUComponentDefinitionRef component_definition) {
	std::string name = GetComponentDefinitionName(component_definition);
....


	// We need to avoid image export when going through definition
	// it will be exported in component or group instances
	options_.set_export_images(false);
	WriteEntities(entities, info.entities_, CTransform());
	options_.set_export_images(true);

....
}

This time all models are 100% imported correctly

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.