Difficulty in Obtaining the Correct Normal of a Face in a Dynamic Component

Hello, everyone,

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)?

can’t get correct normal in DC.skp (11.1 KB)

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!

:thinking: I’m curious what happens if you use Dynamic Component that actually works. The one in the attached model fails to redraw. (failed to parse...)

( If you use Scale Definition on that component, then you will get the expected result for the normal. )

Hi,Dezmo

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

Be careful, If you apply “Scale Definition” you physically change the model but you really just want to calculate the normal.

Please check my previous assumption, and repair the broken Dynamic Component:

Perhaps the broken DC causing unexpected behaviour.


@curic4su
The two methods give two different results for the model in the first post.

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
def calculate_normal_cross(face, transform)
  x, y, _ = face.normal.axes.map { |axis| axis.transform(transform) }
  x.cross(y)
end
ci = Sketchup.active_model.entities[0]
transform = ci.transformation
face = ci.definition.entities.grep(Sketchup::Face).first
puts calculate_normal_cross(face, transform)
puts calculate_normal_from_points(face, transform)

Returns:

(-0.768905, -4.93038e-32, 0.768905)
(-0.707107, 6.25284e-16, 0.707107)

EDIT:
As stated below, this will result the same… :blush:

puts calculate_normal_cross(face, transform).normalize
puts calculate_normal_from_points(face, transform).normalize

vector.normalize :wink:

1 Like

I guess I’m not fully awake yet… :blush:

Hi, Curic4su

# 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. :grinning:

Hi, Dezmo

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.” :joy: As long as the model appears “correct” in SketchUp, they expect my plugin to analyze it accurately. :face_exhaling: