# How to determine normal of a face in a rotated object

Here is a bit of code which draws the shape shown here.

This is just sample code, but in my actual project, I am going to be making a lot of these shapes and rotating them various ways…

I would like to be able to determine which direction (front, back, left, right, top, bottom) the flap is facing after the rotation. But I’m finding that after the rotation, the face still retains the normal it had in the original shape.

``````module JJones_example

def self.draw_shape(entities, points, flap_points, material)
sgroup = draw_edges(points, entities)
flap = draw_flap(flap_points, sgroup.entities, material)
[sgroup, flap]
end

def self.draw_edges(points, entities, with_callouts=false)
print("draw_edges")
last_point = points[points.length - 1] #.shift
points.each_with_index{ |point, i|
if with_callouts
self.make_text(g.entities, point, i)
end
last_point = point
}
g
end

def self.draw_flap(flap, entities, material)
print("draw_flap")
face.material = material
face.back_material = material
face
end

def self.move_thing(obj, distance, axis)
print("move_thing")
tr = Geom::Transformation.new([distance,0,0]) if axis == "x"
tr = Geom::Transformation.new([0,distance,0]) if axis == "y"
tr = Geom::Transformation.new([0,0,distance]) if axis == "z"
obj.transform! tr
end

def self.get_rotation(obj, axis, angle)
bounds = obj.bounds
ctr = bounds.center
rv = obj.transformation.zaxis if axis == "z"
rv = obj.transformation.yaxis if axis == "y"
rv = obj.transformation.xaxis if axis == "x"
ro = Geom::Transformation.rotation(ctr, rv, angle.degrees)
ro
end

def self.rotate_thing(obj, axis, angle)
print("rotate_thing")
ro = get_rotation(obj, axis, angle)
obj.transform! ro
end

def self.copy_and_transform(shape, distance, angle, axis)
print("copy_and_transform")
cp = shape.copy
self.move_thing(cp, distance, axis)
self.rotate_thing(cp, axis, angle)
cp
end

def self.get_material(model)
color = Sketchup::Color.new(255,255,255)
material.color = color
material.alpha = 0.5
material
end

end #module

model = Sketchup.active_model
entities = model.active_entities

model.start_operation("copy and rotate shape", true)

#           0        1      2       3       4       5       6      7
pts = [[0,0,0],[0,0,1],[0,1,0],[0,1,1],[1,0,0],[1,0,1],[1,1,0],[1,1,1]]
shape_pts = [pts,pts,pts,pts,pts,pts,pts,pts]
flap_pts = [pts,pts,pts,pts]

material = JJones_example.get_material(model)

sgroup, flap = JJones_example.draw_shape(entities, shape_pts, flap_pts, material)
print("flap normal: #{flap.normal}")

# now copy and transform the object, rotating it and moving it nearby
copy = JJones_example.copy_and_transform(sgroup, 1.5, 90, 'x')

#is flap on left, right, top, bottom, back, front?
faces = copy.entities.grep(Sketchup::Face)
flap2 = faces
print("flap2 normal: #{flap2.normal}")

model.commit_operation
``````

Here’s an image of the rotated shape, highlighting the face normal. As you can see, it still thinks the face is in the original orientation, before the object was rotated.

How can I find the normal of the face in the rotated shape?

Thank you for any clues
-j_jones

The groups (and components) have its own coordinate system, which is determined by the transformation applied to them. Therefore when you inspecting the entities inside the group you will see the coordinates according to this local coordinate system. If you want to get in a model (global) system you need to transform it… (If you have a more nested group/components level you will need to walk through on all and combine their transformation)

So, you need to determine the Group #transfomation of new `"copy"` , - which is containing the face - then apply it to the vector in question, using Vector3d #transform-instance_method.
e.g.:

``````tr_group = copy.transformation
print("flap2 normal: #{flap2.normal.transform(tr_group)}")
``````

__

__

• you can use `puts` instead of `print`

__

• When you are creating a face the edges - which bound the face - is automatically crated
or/and:
• You can use the the Edge #find_faces-instance_method to create all of the Faces that can be created with this edge.
2 Likes

Thank you that worked!

Maybe this should be a separate question, but now, if I rotate a rotated copy, the normal to the face makes sense, but the rotation is unexpected.

In this follow-on example I create three copies of the original shape and displace them along x, y and z as well as rotate each one 90 degrees along the respective displacement axis. So far so good.

``````module JJones_example

def self.draw_shape(entities, points, flap_points, material)
#print("draw_shape")
sgroup = draw_edges(points, entities)
flap = draw_flap(flap_points, sgroup.entities, material)
[sgroup, flap]
end

def self.draw_edges(points, entities, with_callouts=false)
#print("draw_edges")
last_point = points[points.length - 1] #.shift
points.each_with_index{ |point, i|
if with_callouts
self.make_text(g.entities, point, i)
end
last_point = point
}
g
end

def self.draw_flap(flap, entities, material)
#print("draw_flap")
face.material = material
face.back_material = material
face
end

def self.get_rotation(obj, axis, angle)
#print("get_rotation")
bounds = obj.bounds
ctr = bounds.center
rv = obj.transformation.zaxis if axis == "z"
rv = obj.transformation.yaxis if axis == "y"
rv = obj.transformation.xaxis if axis == "x"
ro = Geom::Transformation.rotation(ctr, rv, angle.degrees)
ro
end

def self.rotate_thing(obj, axis, angle)
#print("rotate_thing")
ro = get_rotation(obj, axis, angle)
obj.transform! ro
end

def self.move_thing(obj, distance, axis)
#print("move_thing")
tr = Geom::Transformation.new([distance,0,0]) if axis == "x"
tr = Geom::Transformation.new([0,distance,0]) if axis == "y"
tr = Geom::Transformation.new([0,0,distance]) if axis == "z"
obj.transform! tr
end

def self.copy_and_transform(shape, distance, angle, axis)
#print("copy_and_transform")
cp = shape.copy
self.move_thing(cp, distance, axis)
self.rotate_thing(cp, axis, angle)
cp
end

def self.get_material(model)
#print("get_material")
color = Sketchup::Color.new(255,255,255)
material.color = color
material.alpha = 0.5
material
end

def self.get_normal(xgroup, face)
#print("get_normal")
# get the normal from a face in a transformed group
tr_group = xgroup.transformation
norm = face.normal.transform(tr_group)
norm
end

def self.make_text(group, txt)
#print("make_text")
# rotate text so it appears facing you instead of lying down
txt = tgroup.entities.add_3d_text(txt, TextAlignCenter, "Arial", true, false, 0.8, 0.0, 0.0, true, 0.25)
x_rotate = Geom::Transformation.rotation(ORIGIN, X_AXIS, 90.degrees)
tgroup.transform!(x_rotate)

# move text to center of cube
target = Geom::Point3d.new(0.5,0.5,0.5)
bounds = tgroup.bounds
xt = Geom::Transformation.translation(bounds.center.vector_to(target))
tgroup.transform!(xt)
tgroup
end

def self.delete_text(group)
#print("delete_text")
gz = group.entities.grep(Sketchup::Group)
if gz.length > 0
tg = gz
group.entities.erase_entities(tg)
#print("gz: #{gz}")
end
end

def self.copy_test(orig, dist, angle, axis, txt)
print("copy_test: #{orig}, #{dist}, #{angle}, #{axis}, #{txt}")
copy = JJones_example.copy_and_transform(orig, dist, angle, axis)
JJones_example.delete_text(copy)
tgroup = JJones_example.make_text(copy,txt)
# identify the flap's new normal
faces = copy.entities.grep(Sketchup::Face)
flap = faces
tr_group = copy.transformation
flap_normal = flap.normal.transform(tr_group)
print("#{txt} flap normal: #{flap_normal}")
copy
end

end #module

model = Sketchup.active_model
entities = model.active_entities

model.start_operation("copy and rotate shape", true)

#           0        1      2       3       4       5       6      7
pts = [[0,0,0],[0,0,1],[0,1,0],[0,1,1],[1,0,0],[1,0,1],[1,1,0],[1,1,1]]
shape_pts = [pts,pts,pts,pts,pts,pts,pts,pts]
flap_pts = [pts,pts,pts,pts]

material = JJones_example.get_material(model)

sgroup, flap = JJones_example.draw_shape(entities, shape_pts, flap_pts, material)
txt = 'O'
tgroup = JJones_example.make_text(sgroup,txt)
print("flap normal: #{flap.normal}")

copy1x = JJones_example.copy_test(sgroup, 3.0, 90, 'x', "1x")

copy1y = JJones_example.copy_test(sgroup, 3.0, 90, 'y', "1y")

copy1z = JJones_example.copy_test(sgroup, 3.0, 90, 'z', "1z")

#now copy a copy and rotate it, does the normal make sense?
#copy1zx = JJones_example.copy_test(copy1z, 1.5, 90, 'x', "1zx")
``````

But then if I copy and rotate a copy. (uncomment last line). Here I copy the 1z copy and displace it along ‘x’ and rotate it along ‘x’. The rotation of the copied copy winds up actually being along the ‘real world y’ axis instead of ‘real world x’ as I expected.

I would have expected the 1zx block to be rotated along the ‘x’ axis, but it is now rotated along ‘y’. This is so confusing.

Expected result rotation for 1zx (first z then x) didn’t get this though

-j_jones

When you set the axis of transformation you are using the object (aka local) axis. Until the object not rotated these axes are same direction as the global axes… but after rotation it will change.
The global axes defined in a Top Level Namespace as a constant : X_ASIS, Y_AXIS, Z_AXIS

Switch on the Show Component axes in Model Info>>Component menu, to see the orientation axes of groups and components when you are teasing.

``````    rv = Z_AXIS if axis == "z"