#-- # *** 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 }