• Login
sketchucation logo sketchucation
  • Login
⚠️ Libfredo 15.4b | Minor release with bugfixes and improvements Update

Returning a value from a Class method

Scheduled Pinned Locked Moved Developers' Forum
17 Posts 6 Posters 738 Views 6 Watching
Loading More Posts
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • J Offline
    johnwmcc
    last edited by johnwmcc 31 Dec 2014, 01:19

    To try to clarify and simplify the Ruby Tool I'm developing, I've developed a new PointsArray class, with several methods for handling arrays of Sketchup points.

    The methods work fine in the Ruby console, and seem to do what I intend.

    One method, for example, shifts a rectangular PointsArray in the Y direction, then rotates it 90 degrees about the Z axis.

    But when I use an assignment in my Ruby code, it picks up an unchanged value from the original array:

    
    # @profile_pointsH is a PointsArray of points in a rectangle with one corner cut off, wider than it is high.
    @profile_pointsH = PointsArray.new @profile[2..-1] 
    @profile_pointsV = @profile_pointsH.shiftYrotateZ90
    
    

    The output of puts @profile_pointsV.contents.inspect show that it has not changed orientation or position from the horizontal profile. (The attr_accessor is called :contents)

    The Class method is:

    
      def shiftYrotateZ90
      ## Transform to shift down by thickness and rotate 90 degrees to turn from horizontal to vertical orientation
      ## Calculate width and thickness
        ## width = @contents.max.x - @contents.min.x
        thickness = @contents.max.y - @contents.min.y
      ## Define transform to shift down
        shiftY = Geom;;Transformation.translation(Geom;;Vector3d.new 0.0, -thickness, 0.0)
      ## Define transform to rotate 90 degrees about Z_AXIS 
        rotateZ90 = Geom;;Transformation.rotation [0,0,0],Z_AXIS, 90.degrees
        ## Combine and transform from horizontal to vertical
        temp = self
        @contents.each_index {|i| temp[i] = @contents[i].transform (rotateZ90*shiftY)}
    puts temp.inspect
        temp
      end
    
    

    I've checked that the transforms in the method are correct, and indeed have verified that the values in temp.inspect show that the method HAS shifted and rotated the rectangle, and I understood that the method 'returns' the last line (the value of temp) as its value.

    I thought that this value would then be assigned to the @profile_pointsV object by the = operation.

    It IS created as a PointsArray but contains just the same points in the same order as the original @profile_pointsH PointsArray, and I can't see why.

    I'm missing something in my understanding of how Ruby works. Why does this give the expected output when I copy the method directly into the Ruby Console and run the assignment with values for @profile_pointsH, but not in SU code?

    Sorry if I'm being dense, but at the moment, I'm just not seeing where it's wrong.

    Any help would be most welcome - I'm feeling stuck at this point.

    1 Reply Last reply Reply Quote 0
    • A Offline
      Anton_S
      last edited by 31 Dec 2014, 01:41

      In this method you're returning self. In this method, you're changing @profile_pointsH and linking same array to to another variable @profile_pointsV. I.E as of result both variables point to the same object.

      You have to change temp = self to temp = self.clone or simply create a new PointsArray instance, like temp = PointsArray.new().

      1 Reply Last reply Reply Quote 0
      • T Offline
        TIG Moderator
        last edited by 31 Dec 2014, 11:15

        Or to put it another way... 😕
        Some class references - like Array - don't make a copy of the original, but still point at the original object - so any changes made to the copy will affect the original too - you sidestep the issue you need to make the copy using .clone [or .dup] - it's then a new separated Array.

        Simple examples:

        Real copies of simply assigned values...
        x=1 y=x y=2 x returns 1 y returns 3

        Arrays...
        a=[1, 2] b=a b[0]=3 b returns [3, 2] a returns [3, 2]

        Cloned...
        a=[1, 2] b=a.clone b[0]=3 b returns [3, 2] a returns [1, 2]

        TIG

        1 Reply Last reply Reply Quote 0
        • J Offline
          johnwmcc
          last edited by 31 Dec 2014, 18:04

          Thank you again, TIG, for your clarification. Unfortunately, the suggested solutions don't seem to make any difference.

          I was aware that a copy of an array is just a copy of the pointer to the contents, and as you describe, that a clone has separate contents.

          But I've tried temp = self.clone, and temp = PointsArray.new inside the method, and still get the ..V (supposedly transformed) arrays remaining the same as the (unchanged)..H original. I even reloaded SU in case something was being cached in the Ruby console even after I reloaded the .rb file.

          I've also tried @profile_pointsV = @profile_pointsH.clone.shiftYrotateZ90 and @profile_pointsV = @profile_pointsH.copy.shiftYrotateZ90 in the calling code, (where .copy is a method I wrote that does an element by element copy, leaving the original unchanged), both with and without having changed the ' temp = ...' line in the method.

          And I tried changing temp = self to temp = self.clone or to temp = PointsArray.new in both this and other functions like flip the array about its centre along an axis: still no difference to the result: the assigned value is the same as the unchanged original when I call the method from the mainline Tool code, and there are no console errors.

          
            def flip(axis)
              ## Flip the array along the specified axis about its centre
              ## Find the centre
              centrepoint = Geom;;Point3d.linear_combination(0.5, @contents.min, 0.5, @contents.max)
              puts "centrepoint = " + centrepoint.to_s
              temp = self.clone # originally just temp = self
              ## Scale the array around the centrepoint to reverse the values along specified axis
              flip = Geom;;Transformation.scaling(centrepoint, -axis.x,-axis.y,-axis.z)
              @contents.each_index {|i| temp[i] = @contents[i].transform(flip)}
              return temp
            end
          
          

          Nothing seems to work to perform the transform when run in the Tool code, but it DOES (bizarrely) seem to work when copied and pasted on its own into the Ruby console as a function, and called from there, as far as I can see.

          I didn't remember to mention it before, but both my class DrawFraming and the class PointsArray < Array are contained in the same file and wrapped in a module JWM, so the PointArray .new method, and the other methods should be (and certainly seem to be) accessible from within DrawFraming.

          Is there something wrong with the def initialize method?

          
          class PointsArray < Array
          ## Methods for manipulating arrays of points 
            attr_accessor ;contents
            def initialize(*args)
              @contents = args[0]
            end
          
          

          If so, what is wrong, and what should it be instead?

          I'm still stumped.

          Any further thoughts for me to pursue?

          I may have to go back to rewriting them just as functions in the DrawFraming class, and call them with parameters, but I was hoping to learn more about object oriented programming and using them ( PointsArrays) as new kinds of objects.

          PS. Progress of a kind. But as well as changing the target ..V variable, it also seems to change the original ..H one.

          If I change the code to:

          
          ...
             temp = self.clone
              @contents.each_index {|i| temp.contents[i] = @contents[i].transform (rotateZ90*shiftY)}
              temp
            end
          
          

          (where the change is to assign temp.contents[i] instead of temp[i]), at least the transform works to change the arrays. But it also changes the original ..H PointsArray - and I can't find a way of separating the two!

          I even tried calling:

          
          @profile_pointsV = @profile_pointsH.clone
          @profile_pointsV.shiftYrotateZ90
          
          

          and

          
          @profile_pointsV = @profile_pointsH.copy
          @profile_pointsV.shiftYrotateZ90
          
          

          but I STILL can't seem to separate the ..V and ..H variables' contents. If one shifts and rotates, so does the other.

          Is temp = self.cloneusing self to refer to the class, or to the instance, of PointsArray?

          I tried temp = PointsArray.new() but that doesn't work either - gives a Ruby error when the method it called. Doesn't like the () - without that, it doesn't error, but doesn't work either.

          Now even more confused!

          1 Reply Last reply Reply Quote 0
          • J Offline
            johnwmcc
            last edited by 31 Dec 2014, 21:23

            I'm now beginning to wonder if in some way my approach of defining a new class PointsArray is heading me down a blind alley.

            Would I do better just to define some extra methods for the class Array?

            As in, for example:

            
            class Array
            def newMethod
              # code for new method here, creating a new array newArray from a clone of the old one
              ...
              return newArray
            end
            
            

            Might there might be fewer traps for the unwary in that approach?

            I've already done the equivalent - extending an existing Class - (using code slightly adapted from Martin Rinehart's From Edges to Rubies) to add a method .to_matrix to the Geom::Transformation class, to output the transformation in a 4 x 4 matrix format that as well as displaying the Transformation matrix in human-readable form can also be read in as an array of arrays - altering Martin's code to insert square brackets and comma element separators appropriately.

            1 Reply Last reply Reply Quote 0
            • sdmitchS Offline
              sdmitch
              last edited by 31 Dec 2014, 21:25

               thickness = @contents.max.y - @contents.min.y
              

              Does this actually work for you? I get this error, Error: #<ArgumentError: comparison of Geom::Point3d with Geom::Point3d failed>

              Nothing is worthless, it can always be used as a bad example.

              http://sdmitch.blogspot.com/

              1 Reply Last reply Reply Quote 0
              • J Offline
                Jim
                last edited by 31 Dec 2014, 21:40

                I'd willing to have a look. Do you have the current source somewhere we can get at?

                Hi

                1 Reply Last reply Reply Quote 0
                • J Offline
                  johnwmcc
                  last edited by 31 Dec 2014, 22:05

                  @sdmitch said:

                   thickness = @contents.max.y - @contents.min.y
                  

                  Does this actually work for you? I get this error, Error: #<ArgumentError: comparison of Geom::Point3d with Geom::Point3d failed>

                  It does, for me. It was intended to select out the y coordinate (a scalar) from the point coordinates.

                  puts @contents[0].class outputs **Array**. Each point coordinate seems to be stored as a 3-element array, not a Point3d object - not really a surprise, since I defined and use them that way most of the time. I haven't needed so far to have a 'real' Point3d, but I would convert to one with Geom::Point3d.new(x, y , z).

                  If you are storing Point3d objects, would you need @contents.to_a first, before .max.y or min.y?

                  I wasn't sure if the .x and .y methods would work on arrays, but they seem to in my example code.

                  I'm unclear about the near-but-not-complete equivalence between SU Point3d(x, y, z) and Array[x, y, z]- it seems I can use either a lot of the time, but not quite always!

                  As you may have already gathered, though, I remain a relative novice in understanding the innards of Ruby, let alone SU Ruby! I mostly try to adapt others' code examples, thought I'm now trying much harder to understand why and how they work - with mixed results, as you see from my questions here!

                  1 Reply Last reply Reply Quote 0
                  • T Offline
                    TIG Moderator
                    last edited by 31 Dec 2014, 22:27

                    😲
                    May I inject a pause, for reflection...
                    If you explained [in words - NOT code] what it is you are trying to do, then we might better advise you...
                    My own scripts do many convoluted things, but I have never seen the necessity to write a whole new 'Point' class to do something...
                    I am sure that whatever it is you plan to do is quite possible, and probably considerably less complicated than you have so far contrived it to be... 😉

                    Happy New Year...
                    ☀

                    TIG

                    1 Reply Last reply Reply Quote 0
                    • J Offline
                      johnwmcc
                      last edited by 31 Dec 2014, 22:30

                      @jim said:

                      I'd willing to have a look. Do you have the current source somewhere we can get at?

                      Jim, that's an extremely generous offer, and I'd love you to look and comment.

                      My current source is a mess at the moment. Since I'm in the middle of rewriting it to try and change from in-line calculations to object based functions, and also to work with asymmetrical profiles, some of the later executed code is still 'old stuff', inconsistent, and can error distractingly.

                      Given another day or so, I could probably comment out the error-generating bits, and either attach a file here, or put the latest version on GitHub, where I have earlier versions too - see

                      Link Preview Image
                      Commits · johnwmcc/draw_framing

                      Sketchup ruby to draw timber framing in standard UK softwood sizes - Commits · johnwmcc/draw_framing

                      favicon

                      GitHub (github.com)

                      I'm still trying to figure out branching in GitHub - and haven't even started any branches as a result.

                      V 0.70 is the latest uploaded version, and works usably for symmetrical (rectangular) profiles. I have part-working but pre-object based versions that almost work for non-rectangular profiles saved locally - it was in trying to improve these that I thought it would be simpler to start over with PointsArray objects!

                      I had got in a horrible tangle trying to keep track of the profile rotation (in any of four quadrants), orientation (horizontal or vertical), flip state (basic profile shape can be flipped along Y, X, or both X and Y directions, or back to original), getting components drawn at the origin with axes aligned to the sides, and transforming both drawn geometry and created geometry to the original pickpoint and (if picked on a face) the face normal!

                      It's possible I'll have time to get a tidied up version that doesn't error unnecessarily by the end of this evening (UK time), but it's more likely it won't be until tomorrow - I'll post here again when I've done it.

                      Thanks again - the help on this forum is very generous, and I thank you both in retrospect and in advance.


                      PS. Happy New Year!

                      My posts here get stamped an hour later than I make them - perhaps I'm regarded as still on British Summer Time? We're back to GMT in the winter, so it isn't quite so nearly midnight as it may appear, but not long.

                      1 Reply Last reply Reply Quote 0
                      • sdmitchS Offline
                        sdmitch
                        last edited by 31 Dec 2014, 22:34

                        I figured that was the case regarding the points not being Point3d's

                        For me
                        @profile_pointsH = PointsArray.new @profile[2..-1] doesn't return anything. I assume because the class initialize merely saves @profile[2..-1] in @contents.

                        @profile_pointsV = @profile_pointsH.shiftYrotateZ90 returns the transformed values since it processes @contents and returns temp.


                        Johnwmcc.jpg

                        Nothing is worthless, it can always be used as a bad example.

                        http://sdmitch.blogspot.com/

                        1 Reply Last reply Reply Quote 0
                        • J Offline
                          johnwmcc
                          last edited by 1 Jan 2015, 00:02

                          @sdmitch said:

                          @profile_pointsH = PointsArray.new @profile[2..-1] doesn't return anything. I assume because the class initialize merely saves @profile[2..-1] in @contents.

                          @profile_pointsV = @profile_pointsH.shiftYrotateZ90 returns the transformed values since it processes @contents and returns temp.

                          Sorry, @profile[2..-1] contains the values:
                          [[0, 0, 0], [0.875, 0, 0], [1.75, 0.4375, 0], [1.75, 0.875, 0], [0, 0.875, 0], [0, 0, 0]]

                          which is a nominal 2" x 1" timber, with one corner planed off mid-line to mid-line.

                          I get quite sensible results when I run the code direct in the console as a function. But not when run in the tool.

                          Will post later a cleaned up version of the current non-working code as suggested by Jim.

                          1 Reply Last reply Reply Quote 0
                          • J Offline
                            johnwmcc
                            last edited by 1 Jan 2015, 00:41

                            Have now put a simplified but non-erroring partially working copy of draw_framing.rb and changelog.txt on GitHub as v0.7.2.2 at

                            Link Preview Image
                            Commits · johnwmcc/draw_framing

                            Sketchup ruby to draw timber framing in standard UK softwood sizes - Commits · johnwmcc/draw_framing

                            favicon

                            GitHub (github.com)

                            I've also attached an installable .rbz file to this post. If you install that, you'll get the other needed files to make the draw_framing.rb code work - icon files, etc - and also some earlier versions copied into Plugins, but not loading.

                            Run it inside Ruby console in SketchUp to see the behaviour I'm trying to fix - the V and H profiles always move together - V is drawn in 3px wide orange and H is 1px black.

                            Mouse movement should (but doesn't) rotate the profile smoothly round the first pick point changing profile rotation quadrant, and orientation (width horizontal or vertical) as you go. I HAVE had that working (in v0.7.0 if you'd like to try it) for a rectangular profile.

                            J


                            Installable v0.7.2.2 with other earlier versions included in file

                            1 Reply Last reply Reply Quote 0
                            • A Offline
                              Anton_S
                              last edited by 1 Jan 2015, 18:38

                              I think I got it:

                              Change PointsArray constructor to this:

                              
                              def initialize(*args)
                                @contents = []
                                if args[0].is_a?(Array)
                                  for i in 0...args[0].size
                                    @contents[i] = args[0][i].dup
                                  end
                                end
                              end
                              
                              

                              Change temp.clone in shiftYrotateZ90 to temp = PointsArray.new()

                              1 Reply Last reply Reply Quote 0
                              • J Offline
                                johnwmcc
                                last edited by 1 Jan 2015, 22:50

                                That's cracked it! At last, the @profile_pointsH and @profile_pointsV have separate contents.

                                And in retrospect, I can see why this works. Though not quite so sure (yet anyway) why the original PointsArray didn't.

                                Thank you so much.

                                Now I can get on with making the rest of it work again - I've had almost all the parts working at one time or another, but not using PointsArray - except for this last part, of getting the rotation and flipping to work together - one or the other but never both.

                                1 Reply Last reply Reply Quote 0
                                • J Offline
                                  johnwmcc
                                  last edited by 1 Jan 2015, 23:11

                                  @tig said:

                                  :shock:
                                  May I inject a pause, for reflection...
                                  If you explained [in words - NOT code] what it is you are trying to do, then we might better advise you...
                                  My own scripts do many convoluted things, but I have never seen the necessity to write a whole new 'Point' class to do something...
                                  I am sure that whatever it is you plan to do is quite possible, and probably considerably less complicated than you have so far contrived it to be... 😉

                                  Happy New Year...
                                  ☀

                                  TIG, a very helpful pause - I do get carried away, and I do tend sometimes to over-complicate! Your post crossed with my reply to Jim.

                                  I want to be able to draw frames for woodwork construction (mainly for our local amateur theatre stage scenery), using standard sizes of timber. Here's an example:

                                  Double window flat.jpg

                                  I have a working program (see last reply to Jim) which does this for rectangular timber. I'm trying to extend it to work with non-rectangular profile timber, and instead of PushPulling to length, to FollowMe round a selected path.

                                  In parallel with this aim, I'm also trying to teach myself better coding in Ruby, in particular, using objects with methods - IF it turns out to be appropriate (it may not, in this instance, as you rightly point out - but I did enjoy trying, anyway!)

                                  The drawing process in SU is:

                                  1. Use a Right Click to select the (nominal) timber size you want to use from a menu, or specify an actual custom size. Nominal sizes are 1/8th inch (3mm) or 1/4 inch (6mm) larger than the finished size. Remember the choice during the SU session, or until it is changed again.

                                  2. Pick a point, optionally on a face, optionally with an axis locked to X, Y, or Z_AXIS (toggle selected by cursor key). The timber's long axis (normal to its cross sectional profile) will be along the face normal if a face was picked, or the pre-selected axis if one was locked, or otherwise the Z_AXIS.

                                  3. Move or drag the mouse round the point, to set the direction of the timber profile with width horizontal or vertical, in any of four quadrants around the point. *If the camera view is looking 'down' the face normal or axis lock, then change the rotation so it still goes the right way round on screen, even though it is 'looking' at the 'back' of the face or axis.

                                  4. Use the TAB key to cycle the flip state of the profile (only useful for non-rectangular profiles, the next development that I'm trying to make). It can be flipped along its X, Y, or both X and Y directions, or restored to original.

                                  5. Click again or release the mouse button after dragging to fix the rotation, orientation and flip state of the cross section.

                                  6. Give a name to the component about to be created.

                                  7. Drag the mouse, or click again, with inferencing, or type a length in the VCB, to determine the length of timber to draw and create it as a component with its origin at the originally picked corner, and the component axes aligned with its width, thickness and length.

                                  8. For the drawn piece of timber, record its length and cross-section size and name in the ComponentInstance and its nominal or actual cross section in its ComponentDefinition.

                                  9. Return the tool to its original state, ready to pick again and draw another piece, usually though not necessarily picking on a corner, edge, or face of any previously drawn piece of timber, or another object.

                                  Ideally, you should be able to flip the cross section before or after the component is drawn, until the tool is next activated.

                                  [NOTE: * I added this bit about the camera viewpoint in a revision of the post]

                                  I hope that make the overall purpose clear.

                                  I've been fairly clear all along WHAT I wanted to do. The bits I've had most trouble with are HOW to do that in code, since I'm still not very familiar with Ruby, in particular how it handles arrays.

                                  I frequently get both the syntax and the semantics wrong - either can't get something to work at all, or it doesn't do what I expected it would, so I've experimented a lot, and usually get somewhere useful in the end. But I suspect that much of my code is horrible! I was trying both to simplify it, and learn about object-based programming in Ruby (which I understand in principle, and have done before in Visual Basic for Applications in MS Word, Access and to a small degree in Excel), and to a very limited extend in PHP.

                                  But all have been self-taught, or learnt from books and code examples on the Internet, with help from forums like this one (or similar for other languages).

                                  1 Reply Last reply Reply Quote 0
                                  • Dan RathbunD Offline
                                    Dan Rathbun
                                    last edited by 12 Jan 2015, 21:36

                                    @johnwmcc said:

                                    If you are storing Point3d objects, would you need @contents.to_a first, before .max.y or min.y?

                                    The min and max methods come from the Enumerable mixin module.
                                    Enumerable is mixed into Array, but not Geom::Point3d, nor Geom::Vector3d.
                                    To see what modules may be mixed into a class, use
                                    *Classname*.ancestors

                                    So to answer the question, yes, or if using your own collection class, you might mix in the Enumerable module into it.

                                    @johnwmcc said:

                                    It does, for me. It was intended to select out the y coordinate (a scalar) from the point coordinates.

                                    ary = [pt1, pt2, pt3, pt4, ... ]
                                    To select the point which has the minimum y value, from an array of points, do this:
                                    miny_pt = ary.min {|a,b| a.y <=> b.y }
                                    To select the point which has the maximum y value, from an array of points, do this:
                                    maxy_pt = ary.max {|a,b| a.y <=> b.y }
                                    ... then get your thickness:
                                    thickness = maxy_pt.y - miny_pt.y

                                    To translate: [ruby:247d5h9w]ary.max {|a,b| a.y <=> b.y }[/ruby:247d5h9w], into plain English:
                                    "iterate [ruby:247d5h9w]ary[/ruby:247d5h9w], each loop compare the current member ([ruby:247d5h9w]a[/ruby:247d5h9w])'s y value, against the next member ([ruby:247d5h9w]b[/ruby:247d5h9w])'s y value, and return the member that resolves to the maximum." The comparison is defined using Ruby's comparison operator, [ruby:247d5h9w]<=>[/ruby:247d5h9w].
                                    Remember that the entire member is returned, so later you still need to ask for just the y value.

                                    @johnwmcc said:

                                    I wasn't sure if the [ruby:247d5h9w].x[/ruby:247d5h9w] and [ruby:247d5h9w].y[/ruby:247d5h9w] methods would work on arrays, but they seem to in my example code.

                                    Because the SketchUp API adds those methods to the Ruby Array class. (It is part of making Ruby arrays compatible with API points and vectors.)

                                    @johnwmcc said:

                                    I'm unclear about the near-but-not-complete equivalence between SU [ruby:247d5h9w]Point3d(x, y, z)[/ruby:247d5h9w] and [ruby:247d5h9w]Array[x, y, z][/ruby:247d5h9w]- it seems I can use either a lot of the time, but not quite always!

                                    Read the API regarding how it adds to Array class:
                                    http://www.sketchup.com/intl/en/developer/docs/ourdoc/array

                                    I'm not here much anymore.

                                    1 Reply Last reply Reply Quote 0
                                    • 1 / 1
                                    • First post
                                      Last post
                                    Buy SketchPlus
                                    Buy SUbD
                                    Buy WrapR
                                    Buy eBook
                                    Buy Modelur
                                    Buy Vertex Tools
                                    Buy SketchCuisine
                                    Buy FormFonts

                                    Advertisement