sketchucation logo sketchucation
    • Login
    โ„น๏ธ Licensed Extensions | FredoBatch, ElevationProfile, FredoSketch, LayOps, MatSim and Pic2Shape will require license from Sept 1st More Info

    Safe place to store user-defined parameters

    Scheduled Pinned Locked Moved Developers' Forum
    114 Posts 9 Posters 11.1k Views 9 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.
    • D Offline
      driven
      last edited by

      @Fredo

      I feel your pain, love your plugins, maybe someone at Trimble or Steve can tidy this up better then me.

      it writes a binary .plist in Users Plugins Folder, in your plugins subdirectory, you can then access it as you would the SU one, but it's your own.
      Once written, it can be read/write i.e. doesn't need a restart [unlike SU's which does]
      you can store all types of thing in it include binary images etc...

      #!/usr/bin/env ruby
      # DON'T RUN DIRECTLY FROM SKETCHUP SEE USAGE BELOW
      # put this in your folder and call it to create a binary .plist on the Users mac
      # write what you will
      require "osx/cocoa"
      require "pp"
      # Create the plist and assign values
      my_path = File.expand_path("~/Library/Application Support/SketchUp 2013/SketchUp/Plugins/FredoHome/__me_first/com.fredo.example.plist")
      myfile = my_path
      my_dict = OSX;;NSMutableDictionary.dictionary
      my_dict['The_Color'] = 'blue'
      my_dict['count'] = 15
      #
      # Output to File and print
      my_dict.writeToFile_atomically(myfile, true)
      pp my_dict
      
      =begin
      ### Usage
      
      make_plist = %x(exec ~"/Library/Application\ Support/SketchUp\ 2013/SketchUp/Plugins/FredoHome/__me_first/mac_plist.rb" 2>&1)
      my_plist = File.expand_path("~/Library/Application Support/SketchUp 2013/SketchUp/Plugins/FredoHome/__me_first/com.fredo.example")
      
      add2my_plist = %x(defaults write "#{my_plist}" animation-duration -float 0.12)
      
      inQuire = %x(defaults read "#{my_plist}")
      {
          "The_Color" = blue;
          "animation-duration" = "0.12";
          count = 15;
      }
      
      inQuire = %x(defaults read "#{my_plist}"  The_Color)
      blue
      =end
      

      the format of this bit is important com.fredo.example.plist for defaults to read and write.
      Lifted from the man page... https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/defaults.1.html

           Specifying value types for preference keys;
      
                       If no type flag is provided, defaults will assume the value is a string. For best results,
                       use one of the type flags, listed below.
      
           -string     Allows the user to specify a string as the value for the given preference key.
      
           -data       Allows the user to specify a bunch of raw data bytes as the value for the given preference
                       key.  The data must be provided in hexidecimal.
      
           -int[eger]  Allows the user to specify an integer as the value for the given preference key.
      
           -float      Allows the user to specify a floating point number as the value for the given preference
                       key.
      
           -bool[ean]  Allows the user to specify a boolean as the value for the given preference key.  Value must
                       be TRUE, FALSE, YES, or NO.
      
           -date       Allows the user to specify a date as the value for the given preference key.
      
           -array      Allows the user to specify an array as the value for the given preference key;
      
                             defaults write somedomain preferenceKey -array element1 element2 element3
      
                       The specified array overwrites the value of the key if the key was present at the time of
                       the write. If the key was not present, it is created with the new value.
      
           -array-add  Allows the user to add new elements to the end of an array for a key which has an array as
                       its value. Usage is the same as -array above. If the key was not present, it is created
                       with the specified array as its value.
      
           -dict       Allows the user to add a dictionary to the defaults database for a domain.  Keys and values
                       are specified in order;
      
                             defaults write somedomain preferenceKey -dict key1 value1 key2 value2
      
                       The specified dictionary overwrites the value of the key if the key was present at the time
                       of the write. If the key was not present, it is created with the new value.
      
           -dict-add   Allows the user to add new key/value pairs to a dictionary for a key which has a dictionary
                       as its value. Usage is the same as -dict above. If the key was not present, it is created
                       with the specified dictionary as its value.
      
           Specifying a host for preferences;
      
           Operations on the defaults database normally apply to any host the user may log in on, but may be
           restricted to apply only to a specific host.
      
                     If no host is provided, preferences operations will apply to any host the user may log in on.
      
           -currentHost
                     Restricts preferences operations to the host the user is currently logged in on.
      
           -host hostname
                     Restricts preferences operations to hostname.
      

      learn from the mistakes of others, you may not live long enough to make them all yourself...

      1 Reply Last reply Reply Quote 0
      • Chris FullmerC Offline
        Chris Fullmer
        last edited by

        Talking it over a little bit here with a Mac person, I still think that the ~/Library/Application Support/ folder is the right starting point. It exists on all Macs, and is specifically designed to be the location that support files get saved to. To me, that is the definition of what Fredo is looking for, and it is sanctioned by Mac. So it CAN'T be against their own "Every app is an island" viewpoint, or else they wouldn't have created it, right? ๐Ÿ˜„

        Lately you've been tan, suspicious for the winter.
        All my Plugins I've written

        1 Reply Last reply Reply Quote 0
        • Chris FullmerC Offline
          Chris Fullmer
          last edited by

          Ok, I'm reading through that great Link John (very helpful to me as I try to wrap my head around Macs). 2/3 of the way down, in Table 1.3 it does explain the purpose of the Application Support folder:

          @unknownuser said:

          Use this directory to store all app data files except those associated with the userโ€™s documents. For example, you might use this directory to store app-created data files, configuration files, templates, or other fixed or modifiable resources that are managed by the app. An app might use this directory to store a modifiable copy of resources contained initially in the appโ€™s bundle. A game might use this directory to store new levels purchased by the user and downloaded from a server.
          All content in this directory should be placed in a custom subdirectory whose name is that of your appโ€™s bundle identifier or your company.

          Lately you've been tan, suspicious for the winter.
          All my Plugins I've written

          1 Reply Last reply Reply Quote 0
          • D Offline
            driven
            last edited by

            I'll wait till your finished before I bite your head off...

            learn from the mistakes of others, you may not live long enough to make them all yourself...

            1 Reply Last reply Reply Quote 0
            • S Offline
              slbaumgartner
              last edited by

              @chris fullmer said:

              Talking it over a little bit here with a Mac person, I still think that the ~/Library/Application Support/ folder is the right starting point. It exists on all Macs, and is specifically designed to be the location that support files get saved to. To me, that is the definition of what Fredo is looking for, and it is sanctioned by Mac. So it CAN'T be against their own "Every app is an island" viewpoint, or else they wouldn't have created it, right? ๐Ÿ˜„

              On iOS, apps are quite strictly sandboxed. Each app gets its own virtual disk image and can't read or write anything outside that image.

              But in Mac OS X, this is not yet the case. An app can read and write anything for which the user has adequate permission anywhere on any drive. And, using shell scripting such as John has provided, a plugin can ultimately bypass the app's own API and rules to execute arbitrary UNIX commands.

              The sandboxing rules today on OS X only exist as conventions that developers are asked to respect (required to respect if their code is published via the App Store). But, as John has alluded, Apple is trying to beef up the sandboxing because so many apps spew files all over the disk and this makes it difficult or impossible to completely remove an app. Rumor is that the next edition ("Mavericks", expected in the Fall) will have some changes in this regard.

              So, whereas ~/Library/Application Support/<appnane>/... has been the conventional place for applications to save helper files, this could change.

              Based on all of this, it seems to me that the only place that simultaneously provides:

              • assured read and write permission for the user
              • logical/code consistency between Windows and Mac
              • support within SketchUp for readily finding the folder by a plugin

              is a plugin's own subfolder within whatever folder SketchUp is using to store user-added extensions. That is, when you tell SU to add an extension, SU itself chooses the appropriate folder ("Plugins" somewhere) and unzips your rbz file there. You have to have write permission in that folder, else SU couldn't install the extension. At that point your plugin code can query to find out where it was installed and can create more subfolders as well as write and read arbitrary files there. The plugin should not hard code any path such as "C:\Program Files\SketchUp\Plugins" or "~/Library/Application Support/SketchUp 2013/SketchUp/Plugins". It should use either Sketchup.find_support_file(...) or File.dirname(FILE) or something similar to get SU to reveal where the extension was loaded, and then use relative paths from there.

              That approach both allows arbitrary files to be written and read, but also will survive if SU is compelled to change the physical folder where extensions are stored because OS security rules were tightened.

              Steve

              1 Reply Last reply Reply Quote 0
              • Chris FullmerC Offline
                Chris Fullmer
                last edited by

                @driven said:

                I'll wait till your finished before I bite your head off...

                I'm done John, you can start chewing ๐Ÿ˜†

                Lately you've been tan, suspicious for the winter.
                All my Plugins I've written

                1 Reply Last reply Reply Quote 0
                • Chris FullmerC Offline
                  Chris Fullmer
                  last edited by

                  @Steve - Writing to a plugin's folder on Windows can fail though, so its not really a god solution for Windows. And up until 2013, it should have been equally likely to fail on Mac as well, because plugins were being written to /Library/Application Support, which is public, which requires admin rights. Only since 2013 have Plugins been written to ~/Library/Application Support/..., which is completely writeable by the current user.

                  Also, we're not writing in iOS, so their extreme sandboxing isn't a concern here (yet?). It will be interesting to see what Maverick implements. But its hard enough to develop for backwards compatibility and current compatibility....forward speculative compatibility might be a little too speculative for my taste.

                  Chris

                  PS - for the record, I have nothing to do with Macs and the decisions we make about how we handle the Mac files system, so don't let me ignorance scare you too much. They won't let me ruin SU on your Mac any time soon ๐Ÿ˜„

                  Lately you've been tan, suspicious for the winter.
                  All my Plugins I've written

                  1 Reply Last reply Reply Quote 0
                  • S Offline
                    slbaumgartner
                    last edited by

                    @chris fullmer said:

                    @Steve - Writing to a plugin's folder on Windows can fail though, so its not really a god solution for Windows.

                    Chris - why would the write fail? It seems that if the user could install a plugin there, he should also be able to write a file there. I'm missing something about the real core of the problem...

                    @chris fullmer said:

                    And up until 2013, it should have been equally likely to fail on Mac as well, because plugins were being written to /Library/Application Support, which is public, which requires admin rights. Only since 2013 have Plugins been written to ~/Library/Application Support/..., which is completely writeable by the current user.

                    Yeah, /Library/Application Support really should be only for files installed along with the app itself that need to affect all users. User-installed extensions, etc, should have been in ~/Library/Application Support all along. Before anybody bites my head off, I know that there have been previous discussions here disparaging ~/Library, but I think they were and still are wrong. However, that does leave the question on a shared computer of where/how to install extensions that you want to affect all users' instances of SU.

                    @chris fullmer said:

                    Also, we're not writing in iOS, so their extreme sandboxing isn't a concern here (yet?). It will be interesting to see what Maverick implements. But its hard enough to develop for backwards compatibility and current compatibility....forward speculative compatibility might be a little too speculative for my taste.

                    Chris

                    PS - for the record, I have nothing to do with Macs and the decisions we make about how we handle the Mac files system, so don't let me ignorance scare you too much. They won't let me ruin SU on your Mac any time soon ๐Ÿ˜„

                    Yeah, I know SU isn't for iOS, just thought I'd throw that in as an example of how Apple's thought processes may be running. OS X is ultimately a wrapper around a full Unix system, and without losing all of the users (like John and I) who like to step behind the curtain now and then, there are limits on how far Apple can go.

                    And don't worry - whatever you come up with to mess up SU on my Mac, I'll find a way to defeat it

                    Steve

                    1 Reply Last reply Reply Quote 0
                    • D Offline
                      driven
                      last edited by

                      @chris fullmer said:

                      Only since 2013 have Plugins been written to ~/Library/Application Support/..., which is completely writeable by the current user.

                      For the record, SU could always use User Library and Steve and myself as well as many others have mostly done so...

                      AS far as I'm aware @Last suggested User Library or at least didn't stop it. It was always the mis-matching that caused problems,.
                      Progressbar.rb bought it to a head when yourself and others started linking to it and when it was in the 'other' folder your plugins would fail.
                      Unfortunately the wrong solution was quickly established on SCF and Google 'then' followed suit.

                      that my recollection, though flawed it my be...

                      john

                      learn from the mistakes of others, you may not live long enough to make them all yourself...

                      1 Reply Last reply Reply Quote 0
                      • fredo6F Offline
                        fredo6
                        last edited by

                        @unknownuser said:

                        On Mac, there seems to be difference between copying a file in a directory and creating the file or folder from within a program. This is a basic things for security. The problem users have with DEFPARAM_Dir folder is exactly that. Ruby cannot create it, but you can create it manually.

                        What I am looking for is a safe place where Ruby can create folders and files.

                        Now, if, as suggested by Chris, I use /Library/Application Support/ as the root, the question is whether the root should not be /Library/Application Support/Sketchup, so that all SU plugins could create their own subfolders there. This would be much cleaner than if I create my folder at /Library/Application Support/LibFredo6.

                        My feeling is that this question should be taken seriously by the Sketchup application itself and supported from Ruby so that script writers don't have to bother.

                        Final word: excuse my ignorance, but what is the subtle difference between /Library/Application Support/ and ~/Library/Application Support/

                        On Mac, there seems to be difference between copying a file in a directory and creating the file or folder from within a program. This is a basic things for security. The problem users have with DEFPARAM_Dir folder is exactly that. Ruby cannot create it, but you can create it manually.

                        What I am looking for is a safe place where Ruby can create folders and files.

                        Now, if, as suggested by Chris, I use /Library/Application Support/ as the root, the question is whether the root should not be /Library/Application Support/Sketchup, so that all SU plugins could create their own subfolders there. This would be much cleaner than if I create my folder at /Library/Application Support/LibFredo6.

                        My feeling is that this question should be taken seriously by the Sketchup application itself and supported from Ruby so that script writers don't have to bother.

                        Final word: excuse my ignorance, but what is the subtle difference between /Library/Application Support/ and ~/Library/Application Support/?

                        Fredo

                        1 Reply Last reply Reply Quote 0
                        • D Offline
                          driven
                          last edited by

                          %(#FF0040)[File.expand_path("~")
                          /Users/johns_iMac
                          ]

                          learn from the mistakes of others, you may not live long enough to make them all yourself...

                          1 Reply Last reply Reply Quote 0
                          • S Offline
                            slbaumgartner
                            last edited by

                            @fredo6 said:

                            @unknownuser said:

                            On Mac, there seems to be difference between copying a file in a directory and creating the file or folder from within a program. This is a basic things for security.

                            From what I know of Unix, this doesn't make sense to me. Can you give me an example?

                            @fredo6 said:

                            The problem users have with DEFPARAM_Dir folder is exactly that. Ruby cannot create it, but you can create it manually.

                            I'll have to try that out and see if I can understand why that happens. Does this occur on all versions of SU, including 2013, or only on older ones?

                            @fredo6 said:

                            What I am looking for is a safe place where Ruby can create folders and files.

                            Now, if, as suggested by Chris, I use /Library/Application Support/ as the root, the question is whether the root should not be /Library/Application Support/Sketchup, so that all SU plugins could create their own subfolders there. This would be much cleaner than if I create my folder at /Library/Application Support/LibFredo6.

                            My feeling is that this question should be taken seriously by the Sketchup application itself and supported from Ruby so that script writers don't have to bother.

                            Final word: excuse my ignorance, but what is the subtle difference between /Library/Application Support/ and ~/Library/Application Support/?

                            Fredo

                            First, you should understand that "~" is Unix shorthand for the user's home directory, usually /Users/<username>. /Library/Application Support is a central folder that all users can normally read, but non-admin users may not be able to write (varies). ~/Library is by default hidden by Finder, so many users don't even know it exists, but it is owned and normally writable by the user. It is also not normally viewable or writable by other users, and is meant to hold the application settings that are personal to that user.

                            And, yes, you should work within a subfolder of ~/Library/Application Support, not right there in the base. SU 2013 puts subfolders in ~/Library/Application Support/SketchUp 2013/SketchUp/. For example, that's where the Plugins fol

                            On Mac, there seems to be difference between copying a file in a directory and creating the file or folder from within a program. This is a basic things for security. [/quote]

                            From what I know of Unix, this doesn't make sense to me. Can you give me an example?

                            @fredo6 said:

                            The problem users have with DEFPARAM_Dir folder is exactly that. Ruby cannot create it, but you can create it manually.

                            I'll have to try that out and see if I can understand why that happens. Does this occur on all versions of SU, including 2013, or only on older ones?

                            [quote="fredo6":3f9n25ud]

                            What I am looking for is a safe place where Ruby can create folders and files.

                            Now, if, as suggested by Chris, I use /Library/Application Support/ as the root, the question is whether the root should not be /Library/Application Support/Sketchup, so that all SU plugins could create their own subfolders there. This would be much cleaner than if I create my folder at /Library/Application Support/LibFredo6.

                            My feeling is that this question should be taken seriously by the Sketchup application itself and supported from Ruby so that script writers don't have to bother.

                            Final word: excuse my ignorance, but what is the subtle difference between /Library/Application Support/ and ~/Library/Application Support/?

                            Fredo[/quote:3f9n25ud]

                            First, you should understand that "~" is Unix shorthand for the user's home directory, usually /Users/<username>. /Library/Application Support is a central folder that all users can normally read, but non-admin users may not be able to write (varies). ~/Library is by default hidden by Finder, so many users don't even know it exists, but it is owned and normally writable by the user. It is also not normally viewable or writable by other users, and is meant to hold the application settings that are personal to that user.

                            And, yes, you should work within a subfolder of ~/Library/Application Support, not right there in the base. SU 2013 puts subfolders in ~/Library/Application Support/SketchUp 2013/SketchUp/. For example, that's where the Plugins folder lives.

                            Steve

                            1 Reply Last reply Reply Quote 0
                            • D Offline
                              driven
                              last edited by

                              @fredo6 said:

                              The problem users have with DEFPARAM_Dir folder is exactly that. Ruby cannot create it, but you can create it manually.

                              Fredo,

                              excuse me, but that's a crock, the only reason it will fail to write is if the encoding is wrong...

                              UFT8 and Unix(LF) will always write to SU from ruby on the mac, if there errors it will fail, but it will write.

                              where is an example of it failing to?

                              john

                              learn from the mistakes of others, you may not live long enough to make them all yourself...

                              1 Reply Last reply Reply Quote 0
                              • D Offline
                                driven
                                last edited by

                                This is most likely your problem...

                                this is how mac interprets your file, where it actually runs fine

                                /ToolsOnSurface.def: ASCII text, with very long lines

                                my mac's Terminal.app is set

                                LANG="en_US"
                                LC_COLLATE="en_US.UTF-8"
                                LC_CTYPE="en_US.UTF-8"
                                LC_MESSAGES="en_US.UTF-8"
                                LC_MONETARY="en_US.UTF-8"
                                LC_NUMERIC="en_US.UTF-8"
                                LC_TIME="en_US.UTF-8"
                                LC_ALL="en_US.UTF-8"
                                

                                SU is set

                                 `locale`
                                LANG=
                                LC_COLLATE="C"
                                LC_CTYPE="C"
                                LC_MESSAGES="C"
                                LC_MONETARY="C"
                                LC_NUMERIC="C"
                                LC_TIME="C"
                                LC_ALL=
                                

                                given an Asian and maybe some European locale things may be problematic...

                                learn from the mistakes of others, you may not live long enough to make them all yourself...

                                1 Reply Last reply Reply Quote 0
                                • D Offline
                                  driven
                                  last edited by

                                  yes, that it... or maybe not I do some checking

                                  learn from the mistakes of others, you may not live long enough to make them all yourself...

                                  1 Reply Last reply Reply Quote 0
                                  • fredo6F Offline
                                    fredo6
                                    last edited by

                                    @driven said:

                                    excuse me, but that's a crock, the only reason it will fail to write is if the encoding is wrong...

                                    UFT8 and Unix(LF) will always write to SU from ruby on the mac, if there errors it will fail, but it will write.

                                    I think the problem of security may be about creating a folder from Ruby. The most frequent issue reported is about the creation of DEFPARAM_dir in the SU Plugins directory.

                                    Fredo

                                    1 Reply Last reply Reply Quote 0
                                    • thomthomT Offline
                                      thomthom
                                      last edited by

                                      @slbaumgartner said:

                                      @chris fullmer said:

                                      @Steve - Writing to a plugin's folder on Windows can fail though, so its not really a god solution for Windows.

                                      Chris - why would the write fail? It seems that if the user could install a plugin there, he should also be able to write a file there. I'm missing something about the real core of the problem...

                                      Under windows the Program Files folder is locked down. They only want application packages installed - but the user should not add/remove files to it. And often users cannot add files to the Plugins folder - but Windows won't throw an error - instead place the files in the Virtual Store creating a shim which cause all sort of confusion and problems. You have to run as user with administrator rights and UAC turned off to avoid these issues.

                                      Thomas Thomassen โ€” SketchUp Monkey & Coding addict
                                      List of my plugins and link to the CookieWare fund

                                      1 Reply Last reply Reply Quote 0
                                      • TIGT Offline
                                        TIG Moderator
                                        last edited by

                                        This discussion seems to go on and on and ignore the simplest of solutions.
                                        Find the users 'Temp' folder [names vary with OS etc]...
                                        I have previously outlined how to get this for MAC and PC.
                                        This 'Temp' folder is useful for holding temporary data, it can be purged by the system, so permament data needs to reside elsewhere...
                                        This 'Local/Temp' folder is in a 'folder tree'.
                                        The folder containing the 'Temp' folder can be found using File.dirname(tempfolderpath).
                                        That container folder can be used to make your own subfolder to hold your more permanent data.
                                        That container folder has the same unrestricted rights for the User as the Temp folder itself.
                                        It is intended to hold app-specific folders/data...
                                        Just open the C:/Users/User/Local folder to see this...

                                        What is the problem in doing it this way?
                                        I have done it thus for a long time, without any issues whatsoever... ๐Ÿ˜•

                                        TIG

                                        1 Reply Last reply Reply Quote 0
                                        • S Offline
                                          slbaumgartner
                                          last edited by

                                          @fredo6 said:

                                          I think the problem of security may be about creating a folder from Ruby. The most frequent issue reported is about the creation of DEFPARAM_dir in the SU Plugins directory.

                                          Fredo

                                          Dir.mkdir should work fine in any folder where the user has write permission.

                                          Do you know exactly what error message they get when this fails? That would help with diagnosis. Unless you trap them in your code, the error messages will appear in the Ruby Console. Two possibilities that come to mind:

                                          Errno::EACCES: Permission denied - <name of the dir you tried to create> This means this user doesn't have write permission for the base folder in which you are trying to create a new dir. Could happen if they've somehow gotten your plugin installed in ~/Library/Application Support/..../Plugins but you are trying to create your directory in /Library/Application Support/.../Plugins. That could happen in SU 8 and earlier if you picked up SU's default path to plugins (/Library...) where an ordinary user might not have write permission. In this situation, the plugin itself must have been installed in ~/Library because a user always has permission there. SU 2013 has changed to keep plugins in ~/Library

                                          Errno::ENOENT: No such file or directory - <the path you gave when you called Dir.mkdir> The path you gave does not exist (the base path leading up to your new dir, which of course does not itself exist unless the mkdir call succeeds). Note that Dir.mkdir does not expand "~", so you have to do it if you want to work in ~/Library/..., e.g. "/Users/Steve/Library/..."

                                          Hope that helps
                                          Steve

                                          1 Reply Last reply Reply Quote 0
                                          • S Offline
                                            slbaumgartner
                                            last edited by

                                            @thomthom said:

                                            @slbaumgartner said:

                                            @chris fullmer said:

                                            @Steve - Writing to a plugin's folder on Windows can fail though, so its not really a god solution for Windows.

                                            Chris - why would the write fail? It seems that if the user could install a plugin there, he should also be able to write a file there. I'm missing something about the real core of the problem...

                                            Under windows the Program Files folder is locked down. They only want application packages installed - but the user should not add/remove files to it. And often users cannot add files to the Plugins folder - but Windows won't throw an error - instead place the files in the Virtual Store creating a shim which cause all sort of confusion and problems. You have to run as user with administrator rights and UAC turned off to avoid these issues.

                                            Whoa! I didn't know about this Virtual Store/shim stuff. That sounds like Windows setting you up for problems! Simulating a write to a place you don't have permission to write (to install the plugin) then later refusing permission to write to the same place!!??? ๐Ÿ˜ฎ

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

                                            Advertisement