I’ve encountered a issue that has stopped my progress for several days. I am attempting to determine the normal of a face in a dynamic component, showed in the image below. The face is oriented towards the top left direction (negative x-axis and positive z-axis), at a 45-degree angle. Therefore, the normal should be [-0.707107, 0, 0.707107].

When I apply the component’s transformation to the normal, I do not achieve the expected result. Below is the Ruby script I used, which is specifically coded for the simplified model file attached:

# Demonstration code, only functional in the attached simplified model file
component_inst = Sketchup.active_model.entities[0]
component_inst.definition.entities.grep(Sketchup::Face).each {|face|
puts "normal: #{component_inst.transformation * face.normal}"
}
# Expected: [-0.707107, 0, 0.707107], Actual: [-0.540726, 0, 1.09337]

My question is: How can I accurately obtain the normal of a face that is part of a dynamic component (DC)?

I suspect the issue might be related to the dynamic component itself. Although I understand DCs have attributes like rotX, rotY, etc., which I assumed would be factored into the DC’s transformation, it seems the process is more complex than I initially thought.

I have learned much from this community and appreciate your guidance in advance. Thank you!

Thank you for pointing me towards “Scale Definition”! I believe it’s exactly what I needed.

Following your suggestion, I discovered a highly informative post: “Scale Definition” via Ruby. I’ll do more investigation on it.

However, this raises an additional question:

In Ruby programming, under what circumstances should I use “Scale Definition” (cost more resources i think) instead of merely applying a transformation, if I want to get the accurate normal/position of the entities within a component?

Can I just assumpt like following?

for DCs(Dynamic Componet), “Scale Definition” must be added.

for none-DCs(common solids), applying transformation is enough.

To calculate the normal of a face within a transformed component, you can use two approaches:

# Using Cross Product of Transformed Axes
def calculate_normal_cross(face, transform)
x, y, _ = face.normal.axes.map { |axis| axis.transform(transform) }
x.cross(y)
end

Or

# From Plane Created from Transformed Face Points
def calculate_normal_from_points(face, transform)
pts = face.vertices.map { |v| v.position.transform(transform) }
plane = Geom.fit_plane_to_points(pts)
Geom::Vector3d.new(plane[0], plane[1], plane[2])
end

# Using Cross Product of Transformed Axes
def calculate_normal_cross(face, transform)
x, y, _ = face.normal.axes.map { |axis| axis.transform(transform) }
x.cross(y)
end

Beautiful! Thank you very much for your help!

# From Plane Created from Transformed Face Points
def calculate_normal_from_points(face, transform)
pts = face.vertices.map { |v| v.position.transform(transform) }
plane = Geom.fit_plane_to_points(pts)
Geom::Vector3d.new(plane[0], plane[1], plane[2])
end

Thanks again! Actually, I used this solution for a while, but this solution requires me to keep the vertices always in the same order( all clockwise or all counter-clockwise), otherwise, the normal will be just opposite. and I don’t think i can always keep them in order in my project.

Sure, thanks for your remind. Maybe Model#start_operation/abort_operation will be a solution for me to keep the model unchanged.

Thank you! The purpose of my plugin is to analyze models provided by my customers, who mostly are cabinet designers. They are not concerned with “redraw errors.” As long as the model appears “correct” in SketchUp, they expect my plugin to analyze it accurately.