#--
# *** This code is copyright 2004 by Gavin Kistner
# *** It is covered under the license viewable at http://phrogz.net/JS/_ReuseLicense.txt
# *** Reuse or modification is free provided you abide by the terms of that license.
# *** (Including the first two lines above in your source code usually satisfies the conditions.)
#++
# Author:: Gavin Kistner (mailto:gavin@refinery.com)
# Copyright:: Copyright (c)2004 Gavin Kistner
# License:: See http://Phrogz.net/JS/_ReuseLicense.txt for details
# Version:: 1.3, 2004-Oct-3
# Full Code:: link:../Geodesic_SketchUp.rb
#
# This file allows the user to add geodesic models to SketchUp (http://www.sketchup.com).
#
# See the Sketchup::Geodesic.create method for more information on creating and adding a geodesic dome/sphere.
#
# ====Version History
# 20040916 v1.0 Initial release; relies on Geodesic.rb
# 20040920 v1.1 Rewrite to use only SketchUp classes
# 20040920 v1.1.1 Fixed it to actually load ;)
# 20040920 v1.2.1 Added icosahedron option. Spruce up documentation.
# 20041003 v1.3 Added primitive picker to the dialog interface. (Thanks TBD!)
# The Sketchup::Geodesic class cannot be instantiated; it is a wrapper for the Sketchup::Geodesic.create method. See that method for more details.
class Sketchup::Geodesic
SQRT2 = Math.sqrt(2)
SQRT3 = Math.sqrt(3)
TETRA_Q = SQRT2 / 3
TETRA_R = 1.0 / 3
TETRA_S = SQRT2 / SQRT3
TETRA_T = 2 * SQRT2 / 3
GOLDEN_MEAN = (Math.sqrt(5)+1)/2
PRIMITIVES = {
:tetrahedron => {
:points => {
:a => Geom::Vector3d.new( -TETRA_S, -TETRA_Q, -TETRA_R ),
:b => Geom::Vector3d.new( TETRA_S, -TETRA_Q, -TETRA_R ),
:c => Geom::Vector3d.new( 0, TETRA_T, -TETRA_R ),
:d => Geom::Vector3d.new( 0, 0, 1 )
},
:faces => %w| acb abd adc dbc |
},
:octahedron => {
:points => {
:a => Geom::Vector3d.new( 0, 0, 1 ),
:b => Geom::Vector3d.new( 1, 0, 0 ),
:c => Geom::Vector3d.new( 0, -1, 0 ),
:d => Geom::Vector3d.new( -1, 0, 0 ),
:e => Geom::Vector3d.new( 0, 1, 0 ),
:f => Geom::Vector3d.new( 0, 0, -1 )
},
:faces => %w| cba dca eda bea
def ebf bcf cdf |
},
:icosahedron => {
:points => {
:a => Geom::Vector3d.new( 1, GOLDEN_MEAN, 0 ),
:b => Geom::Vector3d.new( 1, -GOLDEN_MEAN, 0 ),
:c => Geom::Vector3d.new( -1, -GOLDEN_MEAN, 0 ),
:d => Geom::Vector3d.new( -1, GOLDEN_MEAN, 0 ),
:e => Geom::Vector3d.new( GOLDEN_MEAN, 0, 1 ),
:f => Geom::Vector3d.new( -GOLDEN_MEAN, 0, 1 ),
:g => Geom::Vector3d.new( -GOLDEN_MEAN, 0, -1 ),
:h => Geom::Vector3d.new( GOLDEN_MEAN, 0, -1 ),
:i => Geom::Vector3d.new( 0, 1, GOLDEN_MEAN ),
:j => Geom::Vector3d.new( 0, 1, -GOLDEN_MEAN ),
:k => Geom::Vector3d.new( 0, -1, -GOLDEN_MEAN ),
:l => Geom::Vector3d.new( 0, -1, GOLDEN_MEAN ),
},
:faces => %w| iea iad idf ifl ile
eha ajd dgf fcl lbe
ebh ahj djg fgc lcb
khb kjh kgj kcg kbc |
}
}
PRIMITIVES.each_pair{ |primitive, pf|
PRIMITIVES[primitive] = pf[:faces].collect{ |pts|
pts.split('').collect{ |pt_name|
pf[:points][pt_name.to_sym]
}
}
}
# Adds a new geodesic dome/sphere to SketchUp inside a group.
#
# _frequency_:: The number of times to subdivide each face of the primitive; a frequency of 0 yields the primitive itself.
# _primitive_:: The type of primitive to use as a basis for the geodesic. May be one of :tetrahedron, :octahedron, or :icosahedron. Defaults to :octahedron.
# _radius_:: An initial radius for the geodesic, in inches. Defaults to 11.
#
# If +frequency+ is not supplied, a prompt will be shown to the user, asking for the value of the +frequency+ and +radius+ parameters.
# The number of faces in the geodesic is (faces in primitive) * 4^frequency. Note how quickly the number of faces grows:
#
# frequency tetrahedron octahedron icosahedron
# 0 4 8 20
# 1 16 32 80
# 2 64 128 320
# 3 256 512 1280
# 4 1024 2048 5120
# 5 4096 8192 20480
# 6 16384 32678 81920
#
# For this reason, you should take care not to specify an overly-large +frequency+ value. The following graphic gives a visual depiction of the frequency value:
#
# link:../Geodesics.png.
#
# A +frequency+ of 3 for an octahedron passes pretty well as a sphere (512 faces) and even a value of 2 for an icosahedron (320 faces).
#
# Returns the new Sketchup::Group instance holding the geodesic.
#
# Examples:
# Sketchup::Geodesic.create( 2 ) # Add an octahedron-based geodesic with radius of 36" and frequency 2
# Sketchup::Geodesic.create( 0, :tetrahedron ) # Add a tetrahedron of radius 36"
# Sketchup::Geodesic.create # Prompts the user for primitive, radius and frequency
def self.create( frequency = nil, primitive = :octahedron, radius = 36 )
if !frequency
prompts = [ 'Primitive:', 'Radius (in "):', 'Subdivisions:' ]
values = [ primitive.to_s, radius, 2 ]
primitive_types = ['tetrahedron|octahedron|icosahedron']
results = inputbox prompts, values, primitive_types, 'Create Geodesic'
return unless results
primitive, radius, frequency = results
primitive = primitive.to_sym
end
raise "The Geodesic class does not support the primitive type '#{primitive}'" unless PRIMITIVES[primitive]
model = Sketchup.active_model
model.start_operation "Create Geodesic"
group = model.active_entities.add_group
PRIMITIVES[primitive].each{ |face|
self.expand_face( face, frequency.to_i, radius.to_f, group.entities )
}
model.commit_operation
group
end
# Used by the Sketchup::Geodesic.create method; takes a 'face' (array of 3 Vector3d points),
# and recursively subdivides it the number of times specified by +frequency+; if +frequency+
# is 0, pushes the points out to a specific +radius+ and then adds the face to +entities+.
def self.expand_face( face, frequency, radius, entities )
if frequency < 1
entities.add_face(
face.collect{ |v|
v.normalize.scale_by( radius ).to_point3d
}
)
else
a,b,c = face
ab = a + (b-a).scale_by( 0.5 )
ac = a + (c-a).scale_by( 0.5 )
bc = b + (c-b).scale_by( 0.5 )
[
[ a, ab, ac ],
[ b, bc, ab ],
[ c, ac, bc ],
[ ab, bc, ac ]
].each{ |f|
self.expand_face( f, frequency-1, radius, entities )
}
end
end
end
# Wrapper module for the #smooth_all and #unsmooth_all methods.
module Smoothable
# Sets the smooth property of every edge in the group/model to the value of new_smooth; if new_smooth is not supplied, defaults to +true+.
def smooth_all( new_smooth = true )
model = Sketchup.active_model
model.start_operation "#{new_smooth ? 'Smooth' : 'Unsmooth'} All"
self.entities.each{ |e|
next unless e.is_a?( Sketchup::Edge )
e.smooth = new_smooth;
}
model.commit_operation
end
# Sets the smooth property of every edge in the group/model to false.
def unsmooth_all
self.smooth_all( false )
end
end
# Extending the SketchUp Group class to support a new Smoothable#smooth_all method.
class Sketchup::Group
include Smoothable
# Convenience method to move the group to a specified location, since I couldn't figure out anything simpler than:
#
# my_group.move!( Geom::Transformation.translation(Geom::Vector3d.new( x, y, z ) ) )
def move_to( x, y, z )
self.move!( Geom::Transformation.translation(Geom::Vector3d.new( x, y, z ) ) )
end
end
# Extending the SketchUp Model class to support a new Smoothable#smooth_all method.
class Sketchup::Model; include Smoothable; end
# Extensions to the Geom::Vector class in SketchUp
class Geom::Vector3d
# Modifies the receiving Vector3d, multiplying each x/y/z component by the argument.
def scale_by( n )
self.x *= n
self.y *= n
self.z *= n
self
end
def to_point3d
Geom::Point3d.new( *self.to_a )
end
end
UI.menu("Plugins").add_item("Create Geodesic") {
Sketchup::Geodesic.create
}