Compare commits

..

232 Commits

Author SHA1 Message Date
Jedd Morgan 3d0117f701 Removed old workflows (#442)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2026-02-06 09:27:17 +03:00
Mucahit Bilal GOKER 08fe6f07cf Merge pull request #450 from specklesystems/bilal/cnx-3067-running-sketchup-when-speckle-folder-is-missing
fix: Make initialization robust when app data directory is missing
2026-02-06 08:08:06 +03:00
bimgeek 37be889932 revert check 2026-02-05 17:57:41 +03:00
Mucahit Bilal GOKER ed124dd288 init fix 2026-02-05 17:12:19 +03:00
Oğuzhan Koral 760d8a033d Treat grouped mesh as sketchup entity (#449)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2026-01-30 10:34:27 +03:00
Oğuzhan Koral 96cd122645 Fix: add definition attributes to block instances (#448)
* wip

* get definition and instance props under properties

* Reordering docs
2026-01-16 15:38:33 +03:00
Oğuzhan Koral 533768eb14 strip out @ from detached props (#447) 2026-01-16 14:22:50 +03:00
Oğuzhan Koral c88d3c632d skip if elevation or units null (#446)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2026-01-16 10:27:38 +03:00
Oğuzhan Koral 72f5185992 feat: do not try to hightlight on no selection (#445) 2026-01-13 10:40:31 +03:00
Oğuzhan Koral b8db07a66b Feat: add account (#443)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
* fix(macos): appdata path

* feat: add account via sqlite

* revert: material manager

* revert: dui url

* doc
2025-10-26 09:08:54 +03:00
Oğuzhan Koral 366b961039 Do not init diff materials (#440)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2025-10-14 14:02:59 +03:00
Oğuzhan Koral e244a7e2e5 Convert argb to int always to be safe (#439)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2025-10-14 13:28:52 +03:00
Mucahit Bilal GOKER 9b55764b38 replace logos (#437) 2025-10-03 12:28:26 +03:00
Oğuzhan Koral 4b8012f94b fix(macos): appdata path (#438) 2025-09-17 20:35:49 +03:00
Oğuzhan Koral 9871000d84 no more netlify url (#436)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2025-07-23 17:47:27 +01:00
kekesidavid 17a056b3b9 fix(sketchup) fix for orphan edges (#435)
* fix for orphan edges

* fix
2025-07-10 13:27:34 +03:00
Oğuzhan Koral a8c5bd573f Try adding object ids to render material proxies (#434)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2025-06-26 22:12:23 +03:00
Oğuzhan Koral 1d2fe047bf Fix: initializing sketchup connector on large models takes a lot (#433)
* Attach observers to only orphan edges

* Do not try to read entities on load
2025-06-26 19:36:35 +03:00
Jedd Morgan 3465f86605 chore(ci): Replace GitVersion (#431) 2025-06-26 11:58:30 +03:00
Oğuzhan Koral 540e727b73 Iterate sections planes over level proxies (#432) 2025-06-23 18:59:24 +03:00
Oğuzhan Koral 8c576f820c Default to unnamed folder name if name is empty string (#429) 2025-05-22 17:28:38 +03:00
Jedd Morgan df3b673064 refactor(ci): Update workflow to use new consolidated deployment workflow (#428)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
* Updated workflow

* update workflow

* target main
2025-04-25 10:43:27 +01:00
Oğuzhan Koral 69bde5539c Add serverUrl and workspaceSlug to model cards (#427)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2025-04-21 15:06:19 +03:00
Oğuzhan Koral aa12b7b11b remove unnessary logging (#426) 2025-04-21 14:11:45 +03:00
Oğuzhan Koral e0b3b3cca2 Convert revit data object natively (#425) 2025-04-21 14:08:20 +03:00
Jedd Morgan ea9a5741d7 Update config.yml (#424) 2025-04-21 10:30:19 +03:00
Oğuzhan Koral 1fe1c8d5a8 Fix the source of the issue (#423)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2025-03-28 16:47:56 +03:00
Oğuzhan Koral ac58560a69 Verbose about menu commands and guard about name (#422)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2025-03-24 15:32:39 +03:00
Oğuzhan Koral d0b234bf3d delete ruby.yml temp (#421)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2025-03-19 13:32:09 +03:00
oguzhankoral 981fe05c15 deploy over main 2025-03-19 11:50:17 +03:00
Oğuzhan Koral 918550465b No more beta (#418) 2025-03-19 10:20:46 +03:00
Oğuzhan Koral 963d4f1738 Add remove account and remove models (#417) 2025-03-19 10:16:34 +03:00
oguzhankoral d809732280 Merge branch 'dui3/alpha'
# Conflicts:
#	.circleci/config.yml
2025-03-17 23:16:02 +03:00
oguzhankoral c4e38a11b7 run on ubuntu-22.04 2025-03-17 20:03:42 +03:00
oguzhankoral 7abe215bc2 sorry circleci, have you back for now 2025-03-17 20:01:04 +03:00
oguzhankoral 0f269430bc no more circleci 2025-03-17 19:29:39 +03:00
Oğuzhan Koral b8a298c54b Fix: various papercuts (#409)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
* Do not report converted grouped meshes

* remember last selected account on db
2025-03-10 18:36:58 +03:00
Oğuzhan Koral e1d33cd250 fix the highlight freeze (#408) 2025-03-10 16:18:07 +03:00
Oğuzhan Koral 4563dec9bc Debounce progress update (#407) 2025-03-07 21:07:44 +03:00
Oğuzhan Koral de284083fd add defintiion and layer name to instance proxy (#406)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2025-03-07 12:19:11 +03:00
Oğuzhan Koral 85ee66ca4d Fix(instances): instance name prop is missing (#405)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
* add name to instances

* set name only if it is not empty
2025-03-07 12:04:16 +03:00
Jedd Morgan 5365dc858e Jedd/cxpla 169 update connector deployment ci to error on publishing zero (#404)
* Update deploy.yml

* Update build.yml
2025-03-05 11:47:56 +03:00
Oğuzhan Koral 69f9544638 get accounts always from sqlite - not from cached state (#403)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2025-02-27 14:57:28 +03:00
Oğuzhan Koral bf5409cf90 Feat(dui3): add properties (#402)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
* rename sketchup_attributes with properties for send

* receive properties

* handle send receive properties
2025-02-18 22:32:05 +03:00
Oğuzhan Koral bd4f668526 Make selection is default (#401)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2025-01-16 19:34:16 +03:00
Oğuzhan Koral d21df0c6fd Update build.yml
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2024-12-20 17:33:55 +03:00
Oğuzhan Koral d808fdaa09 Update build.yml 2024-12-20 17:31:44 +03:00
Oğuzhan Koral b32d23b9d6 Do not check speckle_type from level anymore (#400)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2024-12-19 20:44:13 +03:00
Oğuzhan Koral d1b8ba95df Fix(arc): CNX-712 civil2skp alignments missing pieces (#399)
* Normalize plane axis always

* measure based fix

* backward compatible arcs without measures
2024-12-13 00:26:44 +03:00
Oğuzhan Koral 5e6825bbdb Feat(dui3): sketchup send batches sequentially (#397)
* feat: send batches in sequence and create commit later

* Remove load tests
2024-12-12 12:26:11 +03:00
Oğuzhan Koral 3e5ba136be Align arcs with new object model (#396) 2024-11-11 14:33:23 +00:00
oguzhankoral 294b49a11c Add debug notes 2024-11-11 12:01:09 +00:00
Oğuzhan Koral 9e0cca447f Rename the menu title as Speckle Beta (#393)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2024-10-26 23:00:11 +03:00
Oğuzhan Koral bc3f23d8dd Add units to root object on send (#392)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2024-09-23 19:35:01 +03:00
oguzhankoral 4fcf11eaa8 Fix url 2024-09-19 14:27:13 +03:00
Oğuzhan Koral 1f45902837 Add workspace id to model cards (#391)
Co-authored-by: Oguzhan Koral <oguzhankoral@192.168.1.104>
2024-09-16 16:49:22 +03:00
Oğuzhan Koral bc55543e23 Feat(dui3): add new properties to receiver model card (#390)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
* Add new properties to receiver model card

* Update state correctly from read data at doc init
2024-09-11 15:11:54 +03:00
Oğuzhan Koral c1b8ec7036 Create DUI3Config.db file and objects table if not exists (#389)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2024-09-06 02:12:12 +03:00
Alan Rynne abe9de1b4a fix: Update gitversion to v6 to align with csharp connectors (#388)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2024-09-03 12:21:42 +02:00
Alan Rynne 09cc83eb14 fix: Prefix tag trigger with v as GitVersion expects (#387)
Build and deploy / build (push) Has been cancelled
Build and deploy / deploy-installers (push) Has been cancelled
2024-09-03 12:01:15 +02:00
Alan Rynne 8f7b3fce0c Added config for dui3/alpha branch. All versions from this branch will be suffixed with beta (#386) 2024-09-03 11:36:07 +02:00
Oğuzhan Koral f121f1adfe Feat(dui3): performance hunting v1 (#385)
* Get rid of unneccessary from_sketchup check everytime

* Correct the hard edge types
2024-09-02 01:24:52 +03:00
Oğuzhan Koral 522ace443a Fix(dui3): CNX-392 sketchup update configs properly (#384)
* Align old update config logic with new DUI3 needs

* Fix the key casing to snake for SketchUp
2024-08-30 15:51:17 +03:00
Oğuzhan Koral 8f97f59a9a Get is dev mode info from config binding and patch with packager (#383) 2024-08-29 19:18:16 +03:00
Oğuzhan Koral 8e9d98cd55 Check nullability (#382) 2024-08-29 15:51:11 +03:00
Oğuzhan Koral 746230caa0 Rename window title too (#381) 2024-08-29 14:30:18 +03:00
Oğuzhan Koral 03e80c6361 Rename the button names (#380) 2024-08-29 14:26:56 +03:00
Oğuzhan Koral bc547c13db Disable retrieving settings from add_send_model_card (#379) 2024-08-28 17:19:36 +03:00
Oğuzhan Koral 173df6128e Namepace alignment from arcgis commits (#378) 2024-08-26 16:30:48 +03:00
Oğuzhan Koral 666d882ec6 Feat(layers): CNX-352 rvt2skp levels have separate tag (#377)
* Place levels and main instance to related model layers

* Cleanup and renaming

* Do not override layers in model instance

* Extract project-model layer and folder creation

* Set layer for section planes
2024-08-26 11:08:24 +03:00
Alan Rynne 6ea13c4edc fix: Sketchup deploy was trying to pull branch that no longer exist (#376) 2024-08-22 15:11:43 +02:00
oguzhankoral 39bdff1c02 Assign materials to back too by default 2024-08-22 15:34:03 +03:00
Alan Rynne 45fc851722 feat: Adds "public release" input to deploy step if build is tagged (#375) 2024-08-22 13:07:27 +02:00
Oğuzhan Koral ede81f3a7d Feat(dui3): CNX-203 skp create tag folders for project model and document names (#374)
* WIP

* Place objects to correct layers

* Fix the rhino layers

* Place rhino layers to model folder correctly

* Clean model folder first

* Handle revit layers nicely

* Clean the all folders under the model
2024-08-22 13:51:31 +03:00
Oğuzhan Koral bae9f30445 Handle back materials (#373) 2024-08-19 13:12:31 +03:00
Oğuzhan Koral 527cefa3ef Introduce converter error for warnings (#372) 2024-08-19 12:32:28 +03:00
Oğuzhan Koral 830aee116e Re-Fix the specific face problem (#371) 2024-08-16 15:18:19 +03:00
Oğuzhan Koral 243fbea769 Include near faces (#370) 2024-08-16 00:38:24 +03:00
Oğuzhan Koral 9c885258d9 Disable ToNativeV1 (#369) 2024-08-16 00:16:20 +03:00
Oğuzhan Koral 0ad4e15f0a Disable receive definition caching (#368) 2024-08-15 15:32:01 +03:00
Oğuzhan Koral a2ba6823a3 Trigger host app error correctly (#367) 2024-08-14 23:01:39 +03:00
Oğuzhan Koral 3713abd860 Fix(dui3): CNX-291 do not even try to delete "Layer0" (#366)
* Do not try to delete Untagged even if it is changed

* Fix removing active layer with correct condition
2024-08-14 21:00:00 +03:00
Alan Rynne 5ae78fb192 CXPLA-26 Update CI jobs to pass correct version to deploy step. (#365)
* Fix: Add info_version and pass it to deploy step

* fix: Rename build outputs to have clear meaning

* fix: Remove `deploy/` branch exception

* fix: Correct build job outputs + test deploy
2024-08-14 13:58:18 +02:00
Oğuzhan Koral 0b694833fb Implement card settings logic (#364) 2024-08-13 20:09:21 +03:00
Alan Rynne dfd82e6d40 feat: Add GitVersion to sketchup CI run (#363)
* feat: Add GitVersion to sketchup CI run

* fix: Temp trigger from my branch

* feat: Separate build and deploy jobs

* fix: Missing value field on build workflow

* fix: Job names

* fix: Remove on push for build job

* fix: Pull request trigger

* fix: Only trigger on dui3/alpha branch
2024-08-13 19:43:49 +03:00
Oğuzhan Koral 1694786177 Switch layer to Untagged if matches (#362) 2024-08-12 14:54:26 +03:00
Oğuzhan Koral ec72cc1454 Fix(dui3): stringfy application id and proxy object ids (#361)
* Use string for every piece of persistent_id

* Also convert collected persistent_ids to string too

* Correct the url with non-branch version
2024-08-12 11:35:28 +03:00
Oğuzhan Koral 37ba4d9a80 Fix layer type (#360) 2024-08-08 20:13:45 +03:00
Oğuzhan Koral 526e497037 Remove total childen count from everywhere (#359) 2024-08-08 15:42:37 +03:00
Oğuzhan Koral a81a723ba8 Skip definition deletion and do not remove active layer (#358) 2024-08-08 00:26:17 +03:00
Oğuzhan Koral 5976898f07 Feat(dui3): CNX-222 colors in sketchup (#357)
* Unpack color proxies

* Receive layer colors

* Fix applicationId issue on layers

* Consider proxies are null
2024-08-07 23:23:14 +03:00
Oğuzhan Koral 09fec26841 Fix(dui3): CNX-208 skp missing and borked geometry (#356)
* Do not try to assign material if root_render_material_proxies is nil

* Do not try to get color from layer, will need CNX-222

* Fix autocad polycurves and better reporting

* Fix flipped arcs
2024-08-07 18:11:31 +03:00
Oğuzhan Koral 165eb238b5 Face instances to Bilal (#355) 2024-08-07 15:24:48 +03:00
Oğuzhan Koral de305a3508 Force string type check fixes render material issue (#354) 2024-08-07 14:57:47 +03:00
Oğuzhan Koral d124478a92 Unify function name with .net (#353) 2024-08-05 17:56:02 +03:00
Oğuzhan Koral d9acc5b39d Reset dialog location to mid for focused window (#352) 2024-08-04 22:07:01 +03:00
Oğuzhan Koral 5b91a04e1e Feat(dui3): v3 installer build test (#351)
* Create v3-fake.yml

* Run on PR

* Disable patching iss

* Fix typoo

* Rename artifact to sketchup

* Add deployment steps

* Do not name artifact name

* Define which branch to build installer

* Align CI installers with new branch and files

* Get patch installer back

* Remove circleci again

* Rename file and enable tags

* Test enabling PR runs

* Test nested folders

* Test single path dll

* add all things

* try zipping manually first

* don't need sub dir

* rename zip

* Use version from env variables

* Test git version

* More test

* Test

* Revert

* install gitversion

* test

* Use hardcoded version

* Remove pull request trigger

---------

Co-authored-by: Adam Hathcock <adam@speckle.systems>
2024-08-02 15:08:20 +03:00
Oğuzhan Koral d145ce52f7 Sketchup DUI2-DUI3 side by side (#350)
* New sqlite3 libraries for new namespace

* Rename top level module to SpeckleConnector3

* Register extension as v3

* Add new sqlite3 libraries for mac

* Update bundle files for hybrid build mac/intel

* Rename speckle_connector_loader for v3

* Rename file and folder

* Rename loader
2024-07-31 19:26:38 +03:00
Oğuzhan Koral ec598bf4a8 Global toast from top level exception handler (#349)
* Set global notification on handling error

* Add notes
2024-07-27 15:41:06 +03:00
Oğuzhan Koral 568e679e3f Feat(dui3): render materials via proxy (#348)
* Send render materials

* Receive materials from proxy

* Do not set materials to instance and face
2024-07-25 23:52:01 +03:00
Oğuzhan Koral 85cc49f4d9 Temp hack for deep clean (#347) 2024-07-22 20:02:27 +03:00
oguzhankoral 65b07d44ab Tooling around skippy for 2024 debug 2024-07-22 14:40:53 +03:00
oguzhankoral f87bc98169 Doc for sorted_set 2024-07-22 14:03:46 +03:00
oguzhankoral 04270a8abc Upgrade debugger for Ruby 3.2 and SU 2024 2024-07-22 13:14:03 +03:00
Oğuzhan Koral 65fa47c9ac Upgrade xcode to 13.4.1 (#346) 2024-07-20 13:26:15 +03:00
Oğuzhan Koral 9367ea7e17 Layers and colors (#345) 2024-07-18 20:12:41 +02:00
Oğuzhan Koral b7ac9d1fee Receive skp groups as components (#344) 2024-07-18 15:51:29 +02:00
Oğuzhan Koral f20b26045c Align collections and proxies (#342) 2024-07-18 01:10:03 +02:00
Oğuzhan Koral 4bcbc5131b Fix missing objects in definition proxy (#341) 2024-07-16 16:21:47 +02:00
Oğuzhan Koral eeea2d7c8d Grouped meshes by layer and material (#339)
* POC grouped meshes for perf

* Better group mesh

* Add doc fix minor

* Consider back material for meshes

* Remove unused grouped meshes functions

* Remove unnecessary line
2024-07-16 12:57:19 +02:00
Oğuzhan Koral 68e9468294 Fix progress update and some performance (#338)
* Fix send conversion status

* Do not add non-orphan edges to flat atomic objects (~3x performance gain)
2024-07-10 18:55:18 +03:00
Oğuzhan Koral 694da73b77 Fix(blocks): update max depth of children (#337)
* Temp fix

* Update max depth of children
2024-07-09 13:59:16 +03:00
Oğuzhan Koral 15657bf0ec Feat (dui3): New blocks (#336)
* Add proxy objects

* Rename materials and definitions

* Add DefinitionManager

* Fix minor

* Tweak in converter

* Fix units

* Converter v2

* Split converter and converter_v2

* Sort arguments for converters

* Change inner terminology to FE2 for native converter v2

* Simplify arguments

* Remove one click send

* Convert definition proxies in advance then instance proxies

* Yey

* Accept uppercase Units too
2024-06-27 23:09:46 +03:00
oguzhankoral 78c6898ff0 Fix baked object ids 2024-06-05 21:47:09 +03:00
oguzhankoral 836e97a57c Flat stack trace from array to string 2024-06-05 15:11:33 +03:00
oguzhankoral 7f708108f2 Pass error message into hash to serialize 2024-06-05 15:02:53 +03:00
oguzhankoral 2d3e282150 Remove receive result -store only baked object ids 2024-06-05 14:44:01 +03:00
oguzhankoral 75b933ef0a Add conversion_result class and align for send receive 2024-06-05 14:33:16 +03:00
oguzhankoral c19799f081 Send conversion results 2024-06-03 10:37:03 +03:00
oguzhankoral 848ed922b5 Merge coplanar faces from after_get_objects 2024-05-22 11:13:37 +03:00
oguzhankoral 16e96b69a4 Set active path always to model before send 2024-05-13 18:24:36 +03:00
oguzhankoral fbdd7ee7b6 Include parents too onChangeEntity 2024-05-13 18:13:37 +03:00
oguzhankoral d3df5fceec Handle entity paths elegantly 2024-05-13 14:15:43 +03:00
oguzhankoral 948925d156 Ignore cache for grouped meshes since it breaks the object structure 2024-05-13 12:35:26 +03:00
oguzhankoral 7ab6b1b167 Do not group meshes with object referenced meshes 2024-05-13 12:23:25 +03:00
oguzhankoral ca3a109bc8 Fix non-selected update infinity json error 2024-05-13 11:11:22 +03:00
oguzhankoral ebe9551e72 Add 2024 support for DUI3 Mac 2024-05-10 10:46:31 +03:00
oguzhankoral d2836189c3 Add 2024 support for DUI3 2024-05-08 19:17:06 +03:00
oguzhankoral 3703963ec8 Merge branch 'oguzhan/worker-thread-poc' into oguzhan/dui3 2024-05-08 14:35:11 +03:00
oguzhankoral 02ca8e3a59 Do instance message send via worker 2024-05-08 02:34:32 +03:00
oguzhankoral 7c73658fa5 Remove TT dependency 2024-05-08 02:20:40 +03:00
oguzhankoral e03b448618 Enable progress bar while converting from selected entities 2024-05-08 01:33:19 +03:00
oguzhankoral c8d635752b Implement instant_message_sender over worker 2024-05-08 00:59:09 +03:00
oguzhankoral df9a5600d9 Fallback to ModelCollection always if collectionType not set 2024-05-07 23:35:17 +03:00
oguzhankoral dd730ea517 Fix typo 2024-05-07 23:18:46 +03:00
oguzhankoral 5bbfe1be59 Disable old UI button 2024-05-07 21:50:23 +03:00
oguzhankoral dd04bd8792 Rename DUI3 with New UI 2024-05-07 14:48:04 +03:00
oguzhankoral 20bc1f41f0 Fix typo on clearing cache on document switch 2024-05-07 14:25:24 +03:00
oguzhankoral 9e838734c0 Resize speckle icons
Co-authored-by: name <mucahitbgoker@gmail.com>
2024-05-07 14:18:01 +03:00
oguzhankoral 17aeca5e70 Clear cache on document switch 2024-05-07 13:46:02 +03:00
Mucahit Bilal GOKER 98852d8c22 Updated connector icons (#329) 2024-05-07 13:38:02 +03:00
oguzhankoral 674d9cd37d Track also session based ids
This was needed because of onElementRemoved only provides `entityID` instead `persistent_id`......
2024-05-07 12:55:55 +03:00
oguzhankoral c35f9c22ae Extract run expiration checks 2024-05-07 11:01:24 +03:00
oguzhankoral e5f861c0dc Invalidate sub elements of component if created 2024-05-07 10:39:22 +03:00
oguzhankoral 7a31143ef4 Highlight and remember received objects 2024-05-06 15:46:41 +03:00
oguzhankoral 9daa0e4059 Do not send if nothing selected 2024-05-06 12:40:54 +03:00
oguzhankoral eaedeb4f8b Implement getConnectorVersion 2024-05-06 12:13:10 +03:00
oguzhankoral bd2f1d2777 Add missing latest_version_id and has_dismissed_update_warning for receive card 2024-04-30 15:29:30 +03:00
oguzhankoral 52e43bac2d Make elements backward compatible 2024-04-29 10:11:23 +03:00
oguzhankoral daef503970 Wrap DUI3 receive into sketchup operation 2024-04-29 10:10:53 +03:00
oguzhankoral 4d0ba3cd13 Remove placeholder everything and tag send filters 2024-04-29 09:30:34 +03:00
oguzhankoral bd111a2106 Lowercase the app name 2024-04-22 11:40:39 +03:00
oguzhankoral 22daade5d2 Add EntityObserver for Edge and Vertex change detection 2024-03-25 14:24:17 +03:00
oguzhankoral a39d72a59d Change detections for entities 2024-03-25 14:24:06 +03:00
oguzhankoral 3a2e523589 Do not iterate closure table if nil 2024-03-22 12:26:25 +03:00
oguzhankoral 8bd27c6ad1 ObjectReference for previously converted objects 2024-03-21 20:13:11 +03:00
oguzhankoral 4c20cf67c8 Serializer adjustments for ObjectReference
- Previous state and sketchup entity pollution on serializer cleared up
2024-03-21 14:13:01 +03:00
oguzhankoral 337da3d523 Add option for netlify ui url 2024-03-19 14:55:41 +03:00
oguzhankoral 3050803e68 Send according to filter and expiration 2024-03-19 14:28:30 +03:00
oguzhankoral beb803fc63 Read/write cards correctly 2024-03-19 14:28:29 +03:00
oguzhankoral b056bcae2c Remove unnecessary sourceApplication 2024-03-19 14:28:29 +03:00
oguzhankoral 6a2c297640 Fix renames 2024-03-19 14:28:29 +03:00
oguzhankoral 8b3127eda4 Fix selection 2024-03-19 14:28:29 +03:00
oguzhankoral b63d51ab1d Align boilerplate config 2024-03-19 14:28:29 +03:00
oguzhankoral 3b2dfd27d6 Fix changed property for modelCardId 2024-03-19 14:28:29 +03:00
oguzhankoral b8753195df Solve post-conflicts with dev 2024-03-19 14:28:29 +03:00
oguzhankoral e76eb113a5 Zoom selection for highlight 2024-03-19 14:28:29 +03:00
oguzhankoral d285e10565 Update sqlite3 submodule hash ref 2024-03-19 14:28:29 +03:00
oguzhankoral 66803f9036 Highlight model for send cards 2024-03-19 14:28:29 +03:00
oguzhankoral d7cff48374 Remove cards from model 2024-03-19 14:28:29 +03:00
oguzhankoral d66ed0566b Add action name to error message for commands 2024-03-19 14:28:29 +03:00
oguzhankoral fde2b3bf0f First successful receive 2024-03-19 14:28:29 +03:00
oguzhankoral dcc8270cc6 Send progressbar with Sketchup.status_text
Thanks to library of thomthom
2024-03-19 14:28:29 +03:00
oguzhankoral 23b6ca7552 Send model card id via sendViaBrowserArgs 2024-03-19 14:28:29 +03:00
oguzhankoral 437173e6a4 Get source app version via base bindings 2024-03-19 14:28:29 +03:00
oguzhankoral 1274facbed Send message from sender card 2024-03-19 14:28:29 +03:00
oguzhankoral 180fc2ca59 Correct send data 2024-03-19 14:28:28 +03:00
oguzhankoral 187cf8f55e Real send via DUI3 2024-03-19 14:28:28 +03:00
oguzhankoral dd8868b2a2 Trigger sendersExpired when objects modified 2024-03-19 14:28:28 +03:00
oguzhankoral 921e8ea1a8 Use AddModel action for updateModel command 2024-03-19 14:28:28 +03:00
oguzhankoral 54e8782e12 Place binding classes into folder 2024-03-19 14:28:28 +03:00
oguzhankoral 9e690dd304 Enable error catch for command 2024-03-19 14:28:28 +03:00
oguzhankoral 373b42041b Update model card 2024-03-19 14:28:28 +03:00
oguzhankoral 30ead47fb9 Add type discriminator 2024-03-19 14:28:28 +03:00
oguzhankoral 2dc7206f17 Reorganize send bindings 2024-03-19 14:28:28 +03:00
oguzhankoral ab7594bd23 Split account binding from base 2024-03-19 14:28:28 +03:00
oguzhankoral 710a22aea7 Align with Dim's works 2024-03-19 14:28:28 +03:00
oguzhankoral 2c7c801efc WIP: selection bindings - add cards to model 2024-03-19 14:28:28 +03:00
oguzhankoral 41ebe058e7 WIP: filters 2024-03-19 14:28:28 +03:00
oguzhankoral 288c71b3e0 Convert tag colors to hex 2024-03-19 14:28:28 +03:00
oguzhankoral d7a8afcb31 Get default filters 2024-03-19 14:28:28 +03:00
oguzhankoral a1d120ee22 Get model state and send filter for sketchup 2024-03-19 14:28:28 +03:00
oguzhankoral b58d6d0d82 WIP: connector configs 2024-03-19 14:28:28 +03:00
oguzhankoral 5ab65391ca Implement config_binding 2024-03-19 14:28:28 +03:00
oguzhankoral 2b40d58996 Introduce ruby traverse_and_construct 2024-03-19 14:28:28 +03:00
oguzhankoral 96129388ec Send-Receive operations test via ruby 2024-03-19 14:28:25 +03:00
oguzhankoral 6f6058a2d1 Remove test bridge.js 2024-03-19 14:27:56 +03:00
oguzhankoral 063ef91613 Unit test for send operation 2024-03-19 14:27:56 +03:00
oguzhankoral 5233072099 Enable collect preferences 2024-03-19 14:27:56 +03:00
oguzhankoral cd063b0e8a Emit only documentChanged instead of passing data 2024-03-19 14:27:56 +03:00
oguzhankoral 719d796e20 Rename views to bindings 2024-03-19 14:27:56 +03:00
oguzhankoral 9767bf5261 Report errors to UI 2024-03-19 14:27:56 +03:00
oguzhankoral a9998bf12d Resolve triggerEvent 2024-03-19 14:27:56 +03:00
oguzhankoral 2b7dc899e1 Implement test bindings 2024-03-19 14:27:56 +03:00
oguzhankoral cbc3f51d4c Create random sketchup binding 2024-03-19 14:27:56 +03:00
oguzhankoral ff1c8c1bfe Implement on_document_changed action 2024-03-19 14:27:56 +03:00
oguzhankoral 6d4222bb1a Add command for get document info 2024-03-19 14:27:56 +03:00
oguzhankoral 7692a6f258 Remove get_commands action and command 2024-03-19 14:27:56 +03:00
oguzhankoral ed8e6b00c6 Apply same dialog-view relationship to legacy UI 2024-03-19 14:27:56 +03:00
oguzhankoral 85db9570dd Control views via dialog 2024-03-19 14:27:56 +03:00
oguzhankoral be174b9cce Remove unnecssary init from view 2024-03-19 14:27:56 +03:00
oguzhankoral f0099090b4 Collect command names from related view for SketchupBridge 2024-03-19 14:27:56 +03:00
oguzhankoral b72bdfcbf0 Pass view to commands 2024-03-19 14:27:53 +03:00
oguzhankoral e1f5addddc Send messages to correct view/binding 2024-03-19 14:23:06 +03:00
oguzhankoral 87d3145317 Solve missing resolve_id arguments for commands and actions 2024-03-19 14:23:06 +03:00
oguzhankoral 84fc89cbbe Run legacy and dui3 at the same time 2024-03-19 14:23:06 +03:00
oguzhankoral fbbddd8365 Pass resolve id from arguments 2024-03-19 14:23:06 +03:00
oguzhankoral dcfb787563 Replace Bridge with inhouse resolve_id solution 2024-03-19 14:23:06 +03:00
oguzhankoral 6822b2db9b Pass serialized accounts data as json object 2024-03-19 14:23:06 +03:00
oguzhankoral d89b3772de Pass correct request id for getAccounts 2024-03-19 14:23:06 +03:00
oguzhankoral e4a4eb0455 Request id temp hack 2024-03-19 14:23:06 +03:00
oguzhankoral 759efa7da3 Add get commands 2024-03-19 14:23:06 +03:00
oguzhankoral bf1c271784 Add bridge sample 2024-03-19 14:23:06 +03:00
oguzhankoral 9754893181 Check correct dui3 id ui controller 2024-03-19 14:23:06 +03:00
oguzhankoral 5b05182a1c Add new dialog for dui3 2024-03-19 14:23:00 +03:00
oguzhankoral 20528f72df Merge remote-tracking branch 'origin/development' 2024-03-19 13:32:03 +03:00
Alan Rynne 7de08d9f24 fix: Use correct digicert context + pass fingerprint to ISS compiler (#328)
* fix: Use correct digicert context + pass fingerprint to ISS compiler

* Update config.yml

* Update config.yml

---------

Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2024-03-14 11:27:45 +00:00
Oğuzhan Koral d047f5e6d2 Chore (URL): CNX-9018 Do not add model from multi model urls
- warn user and return
- otherwise add
2024-02-29 15:13:44 +03:00
Oğuzhan Koral 71071f817c Make FE2 terminology default (#326) 2024-02-27 17:09:38 +03:00
oguzhankoral ad9af9bb3d Merge remote-tracking branch 'origin/development' 2024-02-27 12:38:00 +03:00
Oğuzhan Koral c7c864b8c0 Feat (CI): Update CI for digicert (#325)
* Update CI for digicert

* Checkout speckle-sharp-ci-tools branch if exist
2024-02-27 12:36:25 +03:00
oguzhankoral 133308141b Merge branch 'development' 2023-11-28 13:57:11 +03:00
464 changed files with 4942 additions and 17398 deletions
Vendored
BIN
View File
Binary file not shown.
+9 -268
View File
@@ -1,276 +1,17 @@
version: 2.1
orbs:
# Using windows for builds
win: circleci/windows@2.4.0
# Upload artifacts to s3
aws-s3: circleci/aws-s3@2.0.0
# Define the jobs we want to run for this project
jobs:
build-ui:
build:
docker:
- image: "circleci/node:16"
- image: cimg/base:2023.03
steps:
- checkout
- run:
command: "npm install"
working_directory: "ui"
- run:
command: "npm run build"
working_directory: "ui"
- persist_to_workspace:
root: ./
paths:
- speckle_connector/vue_ui
build-connector: # Reusable job for basic connectors
executor:
name: win/default # comes with python 3.7.3
shell: cmd.exe
parameters:
slug:
type: string
default: ""
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: Create Innosetup signing cert
shell: powershell.exe
command: |
echo $env:PFX_B64 > "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt"
certutil -decode "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt" "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.pfx"
- run:
name: Set Environment Variable
shell: powershell.exe
command: |
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "2.0.999" } else { $env:CIRCLE_TAG }
$semver = if($tag.Contains('/')) {$tag.Split("/")[0] } else { $tag }
$ver = if($semver.Contains('-')) {$semver.Split("-")[0] } else { $semver }
$version = "$($ver).$($env:WORKFLOW_NUM)"
python patch_version.py $semver
environment:
WORKFLOW_NUM: << pipeline.number >>
- run:
name: Build Installer
command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss /Sbyparam=$p
shell: cmd.exe #does not work in powershell
#- run:
# name: Patch
# shell: powershell.exe
# command:
# | # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
# $tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.0" } else { $env:CIRCLE_TAG }
# $semver = if($tag.Contains('/')) {$tag.Split("/")[1] } else { $tag }
# $ver = if($semver.Contains('-')) {$semver.Split("-")[0] } else { $semver }
# $channel = if($semver.Contains('-')) {$semver.Split("-")[1] } else { "latest" }
# $version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
# New-Item -Force "speckle-sharp-ci-tools/Installers/sketchup/$channel.yml" -ItemType File -Value "version: $semver"
# echo $version
# python patch_version.py $semver
# speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers
build-connector-mac:
macos:
xcode: 12.5.1
parameters:
projname:
type: string
default: ""
slug:
type: string
default: ""
installer:
type: boolean
default: false
converter-files:
type: string
default: ""
installername:
type: string
default: ""
build-config:
type: string
default: Release
bundlename:
type: string
default: ""
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: Install dotnet
command: |
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel Current
$HOME/.dotnet/dotnet --version
$HOME/.dotnet/dotnet --list-runtimes
$HOME/.dotnet/dotnet --list-sdks
- run:
name: Create installer target dir
command: |
mkdir -p speckle-sharp-ci-tools/Installers/<< parameters.slug >>
- run:
name: Set Environment Variable
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "2.0.999"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
VER=$(echo "$SEMVER" | sed -e 's/-.*//')
VERSION=$(echo $VER.$WORKFLOW_NUM)
python3 patch_version.py $SEMVER
environment:
WORKFLOW_NUM: << pipeline.number >>
- run:
name: Zip Connector files
command: |
zip -r << parameters.slug >>-mac.zip "./speckle_connector" "./speckle_connector.rb"
# Copy installer files
- run:
name: Copy files to installer
command: |
mkdir -p speckle-sharp-ci-tools/Mac/<< parameters.installername >>/.installationFiles/
cp << parameters.slug >>-mac.zip speckle-sharp-ci-tools/Mac/<<parameters.installername>>/.installationFiles
# Create installer
- run:
name: Exit if External PR
command: if [ "$CIRCLE_PR_REPONAME" ]; then circleci-agent step halt; fi
- run:
name: Build Mac installer
command: ~/.dotnet/dotnet publish speckle-sharp-ci-tools/Mac/<<parameters.installername>>/<<parameters.installername>>.sln -r osx-x64 -c Release
- run:
name: Zip installer
command: |
cd speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/osx-x64/publish/
zip -r <<parameters.slug>>.zip ./
- store_artifacts:
path: speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/osx-x64/publish/<<parameters.slug>>.zip
- run:
name: Copy to installer location
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "2.0.999"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
VER=$(echo "$SEMVER" | sed -e 's/-.*//')
VERSION=$(echo $VER.$WORKFLOW_NUM)
cp speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/osx-x64/publish/<<parameters.slug>>.zip speckle-sharp-ci-tools/Installers/<< parameters.slug >>/<<parameters.slug>>-$SEMVER.zip
environment:
WORKFLOW_NUM: << pipeline.number >>
- when:
condition: << pipeline.git.tag >>
steps:
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers
get-ci-tools: # Clones our ci tools and persists them to the workspace
docker:
- image: cimg/base:2021.01
steps:
- add_ssh_keys:
fingerprints:
- "03:2e:ee:4f:14:67:2b:88:32:e8:cc:f0:cb:df:92:29"
- run:
name: I know Github as a host
command: |
mkdir ~/.ssh
ssh-keyscan github.com >> ~/.ssh/known_hosts
- run:
name: Clone
command: git clone git@github.com:specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools
deploy-manager2:
docker:
- image: mcr.microsoft.com/dotnet/sdk:6.0
parameters:
slug:
type: string
os:
type: string
extension:
type: string
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: Install Manager Feed CLI
command: dotnet tool install --global Speckle.Manager.Feed
- run:
name: Upload new version
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "0.0.0"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
/root/.dotnet/tools/Speckle.Manager.Feed deploy -s << parameters.slug >> -v ${SEMVER} -u https://releases.speckle.dev/installers/<< parameters.slug >>/<< parameters.slug >>-${SEMVER}.<< parameters.extension >> -o << parameters.os >> -f speckle-sharp-ci-tools/Installers/<< parameters.slug >>/<< parameters.slug >>-${SEMVER}.<< parameters.extension >>
- run: echo "so long and thanks for all the fish"
# Orchestrate our job run sequence
workflows:
build-and-deploy:
build_and_test:
when:
false
jobs:
- get-ci-tools:
filters:
tags:
only: /.*/
- build-ui:
filters:
tags:
only: /.*/
- build-connector:
slug: sketchup
requires:
- get-ci-tools
- build-ui
filters:
tags:
only: /.*/
context: innosetup
- build-connector-mac:
slug: sketchup
requires:
- get-ci-tools
- build-ui
filters:
tags:
only: /.*/
installername: SpeckleSketchUpInstall
- deploy-manager2:
context: do-spaces-speckle-releases
slug: sketchup
os: Win
extension: exe
requires:
- build-connector
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
- deploy-manager2:
context: do-spaces-speckle-releases
slug: sketchup
os: OSX
extension: zip
requires:
- build-connector-mac
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
- build
+64
View File
@@ -0,0 +1,64 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Build
on:
pull_request:
workflow_call:
outputs:
semver:
description: "The full SemVer 2.0 version of this build, e.g. '3.0.0-alpha.1234' (note: no 'v'-prefix)"
value: ${{ jobs.build.outputs.semver }}
file_version:
description: "The file info version, e.g. '3.0.0.1234'"
value: ${{ jobs.build.outputs.file_version }}
jobs:
build:
runs-on: ubuntu-latest
outputs:
semver: ${{ steps.set-version.outputs.semver }}
file_version: ${{ steps.set-version.outputs.file-version }}
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- id: set-version
name: Set version to output
shell: bash
run: |
TAG=${{ github.ref_name }}
if [[ "${{ github.ref }}" != refs/tags/* ]]; then
TAG="v3.0.99.${{ github.run_number }}"
fi
SEMVER="${TAG#v}"
FILE_VERSION=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
FILE_VERSION="$FILE_VERSION.${{ github.run_number }}"
echo "semver=$SEMVER" >> "$GITHUB_OUTPUT"
echo "file-version=$FILE_VERSION" >> "$GITHUB_OUTPUT"
echo $SEMVER
echo $FILE_VERSION
- name: Set connector version
run: |
python patch_version.py ${{steps.set-version.outputs.semver}}
- uses: montudor/action-zip@v1
with:
args: zip -q -r sketchup.zip vendor speckle_connector_3/ speckle_connector_3.rb
- name: ⬆️ Upload artifacts
uses: actions/upload-artifact@v4
with:
name: output-${{steps.set-version.outputs.semver}}
path: sketchup.zip
retention-days: 1
if-no-files-found: error
compression-level: 0 # no compression
-78
View File
@@ -1,78 +0,0 @@
name: Update issue Status
on:
issues:
types: [closed]
jobs:
update_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo "$PROJECT_ID"
echo "$STATUS_FIELD_ID"
echo 'DONE_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .settings | fromjson | .options[] | select(.name== "Done") | .id' project_data.json) >> $GITHUB_ENV
echo "$DONE_ID"
- name: Add Issue to project #it's already in the project, but we do this to get its node id!
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Update Status
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $status:ID!, $id:ID!, $value:String!) {
set_status: updateProjectNextItemField(
input: {
projectId: $project
itemId: $id
fieldId: $status
value: $value
}
) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f status=$STATUS_FIELD_ID -f id=$ITEM_ID -f value=${{ env.DONE_ID }}
+42
View File
@@ -0,0 +1,42 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Build and deploy
on:
push:
branches: ["main", "installer-test/**"]
tags: ["v3.*.*"] # Manual delivery on every 3.x tag
jobs:
build:
uses: ./.github/workflows/build.yml
deploy-installers:
runs-on: ubuntu-latest
needs: build
env:
IS_PUBLIC_RELEASE: ${{ github.ref_type == 'tag' }}
steps:
- name: 🔫 Trigger Build Installer(s)
uses: the-actions-org/workflow-dispatch@v4.0.0
with:
workflow: Build Installers
repo: specklesystems/connector-installers
token: ${{ secrets.CONNECTORS_GH_TOKEN }}
inputs: '{
"run_id": "${{ github.run_id }}",
"semver": "${{ needs.build.outputs.semver }}",
"file_version": "${{ needs.build.outputs.file_version }}",
"repo": "${{ github.repository }}",
"is_public_release": ${{ env.IS_PUBLIC_RELEASE }}
}'
ref: main
wait-for-completion: true
wait-for-completion-interval: 10s
wait-for-completion-timeout: 10m
display-workflow-run-url: true
display-workflow-run-url-interval: 10s
- uses: geekyeggo/delete-artifact@v5
with:
name: output-*
-50
View File
@@ -1,50 +0,0 @@
name: Move new issues into Project
on:
issues:
types: [opened]
jobs:
track_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
- name: Add Issue to project
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
-38
View File
@@ -1,38 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
name: Ruby
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.7']
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
# change this to (see https://github.com/ruby/setup-ruby#versioning):
# uses: ruby/setup-ruby@v1
uses: ruby/setup-ruby@0a29871fe2b0200a17a4497bae54fe5df0d973aa # v1.115.3
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Run tests
run: bundle exec rake
+2 -2
View File
@@ -10,8 +10,8 @@
settings.json
# vue app build dist folder
speckle_connector/vue_ui
speckle_connector/html
speckle_connector_3/vue_ui
speckle_connector_3/html
# speckle-sharp-ci-tools
/speckle-sharp-ci-tools
+1 -1
View File
@@ -18,7 +18,7 @@ AllCops:
- '_tools/su_attributes/**/*.rb'
- '_sqlite3/**/*.rb'
- 'ui/**/*'
- 'speckle_connector/src/ext/**/*.rb'
- 'speckle_connector_3/src/ext/**/*.rb'
- 'vendor/bundle/**/*'
- 'tests/**/*.rb'
SketchUp:
+1 -1
View File
@@ -1,6 +1,6 @@
require_paths:
- "C:/Program Files/SketchUp/SketchUp 2021/Tools"
- speckle_connector
- speckle_connector_3
require:
- sketchup-api-stubs
+3 -1
View File
@@ -24,6 +24,8 @@ group :development do
gem 'rubycritic', '~> 4.3', '>= 4.3.3', require: false
# Auto completions for SketchUp API.
gem 'sketchup-api-stubs'
# Runtime dependency of skippy for Ruby 3.2. Have it!
gem 'sorted_set', '~> 1.0'
# Aid with common SketchUp extension tasks.
gem 'skippy', '~> 0.4.1.a'
gem 'skippy', '~> 0.5.2.a'
end
+13 -5
View File
@@ -26,7 +26,7 @@ GEM
path_expander (~> 1.0)
ruby_parser (~> 3.1, > 3.1.0)
sexp_processor (~> 4.8)
git (1.18.0)
git (1.19.1)
addressable (~> 2.8)
rchardet (~> 1.8)
ice_nine (0.11.2)
@@ -51,6 +51,7 @@ GEM
public_suffix (5.0.1)
rainbow (3.1.1)
rake (13.0.6)
rbtree (0.4.6)
rchardet (1.8.0)
reek (6.1.1)
kwalify (~> 0.7.0)
@@ -89,6 +90,7 @@ GEM
simplecov (>= 0.17.0)
tty-which (~> 0.4.0)
virtus (~> 1.0)
set (1.1.0)
sexp_processor (4.16.1)
simplecov (0.21.2)
docile (~> 1.1)
@@ -97,11 +99,15 @@ GEM
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
sketchup-api-stubs (0.7.8)
skippy (0.4.3.a)
skippy (0.5.2.a)
git (~> 1.3)
naturally (~> 2.1)
thor (~> 0.19)
thor (0.20.3)
sorted_set (~> 1.0)
thor (>= 0.19, < 2.0)
sorted_set (1.0.3)
rbtree
set (~> 1.0)
thor (1.3.1)
thread_safe (0.3.6)
tty-which (0.4.2)
unicode-display_width (1.8.0)
@@ -112,6 +118,7 @@ GEM
equalizer (~> 0.0, >= 0.0.9)
PLATFORMS
x64-mingw-ucrt
x64-mingw32
x64-unknown
x86_64-linux
@@ -127,7 +134,8 @@ DEPENDENCIES
rubocop-sketchup
rubycritic (~> 4.3, >= 4.3.3)
sketchup-api-stubs
skippy (~> 0.4.1.a)
skippy (~> 0.5.2.a)
sorted_set (~> 1.0)
BUNDLED WITH
2.3.25
+11
View File
@@ -0,0 +1,11 @@
workflow: GitFlow/v1
next-version: 3.0.0
mode: ManualDeployment
branches:
main:
label: rc
develop:
regex: ^dui3/alpha$
label: beta
unknown:
increment: None
+18 -4
View File
@@ -49,9 +49,9 @@ This repo is split into three parts:
### 1. **Speckle Connector extension**
Includes the `ruby` source files to run extension on SketchUp environment. SketchUp Extensions are composed of
a **.rb** file as entry and **folder** that .rb file refers to. In our case entry file is `speckle_connector.rb`
a **.rb** file as entry and **folder** that .rb file refers to. In our case entry file is `speckle_connector_3.rb`
that responsible to register Speckle Connector extension to SketchUp and also it shows address to where extension
will start to read extension. Source folder is `speckle_connector`.
will start to read extension. Source folder is `speckle_connector_3`.
### 2. **User Interface**
@@ -64,7 +64,7 @@ This repo is split into three parts:
we use extensions as native part of the source `ruby` code.
After building `sqlite3.sln` file, compiled `sqlite3.so` (for Windows) and `sqlite3.bundle` (for OSX) dynamic library files are created
by solution to place them into source code into `speckle_connector/src/ext`. Building this project should be only
by solution to place them into source code into `speckle_connector_3/src/ext`. Building this project should be only
happen when SketchUp starts to support newer Ruby versions (currently it is `2.7`).
## Contribution Guide
@@ -115,9 +115,23 @@ You can now open up the repo in VS Code or you can use JetBrains' tools RubyMine
If you will use VS Code, make sure you've installed the Ruby extension for VS Code.
#### RubyMine
To debug:
- Add configuration as **'Ruby remote debug'**
- Remote host: localhost
- Remote port: 7000
- Remote root folder: <repo_path>
- Local port: 26162
- Local root folder: <repo_path>
- Run below script
bundle exec skippy sketchup:debug 2024
- When sketchup opened, click Debug button on RubyMine
### Loading the Speckle Connector Plugin
1. Find already prepared `speckle_connector_loader.rb` file on the `_tools`
1. Find already prepared `speckle_connector_3_loader.rb` file on the `_tools`
folder.
2. Copy this Ruby file into your SketchUp Plugins directory. You will likely find this at:
`C:\Users\{YOU}\AppData\Roaming\SketchUp\SketchUp 20XX\SketchUp\Plugins`
+3 -3
View File
@@ -32,12 +32,12 @@ end
# Glob pattern to match source files. Defaults to FileList['.'].
ruby_critic_paths = FileList[
'speckle_connector/**/*.rb',
'speckle_connector.rb',
'speckle_connector_3/**/*.rb',
'speckle_connector_3.rb',
'tests/**/*.rb'] -
FileList[
'_tools/**/*.rb',
'speckle_connector/src/ext/**/*.rb',
'speckle_connector_3/src/ext/**/*.rb',
]
# for local
+15
View File
@@ -0,0 +1,15 @@
# This is for automated pre-debugger configuration.
# We run skippy first, then activate debugger.
# The purpose of this file to wait till skp is live
# To establish a configuration
# 1. Create 'Run External Tool' before lunch step
# 2. Program -> C:\Ruby32-x64\bin\ruby.exe or whatever
# 3. Arguments -> C:\Users\KORAL\Documents\Git\Speckle\speckle-sketchup\_tools\debugger\bundle_exec_2024.rb or whatever
# 4. Working directory -> C:\Users\KORAL\Documents\Git\Speckle\speckle-sketchup or whatever
# Add a delay of 10 seconds, it is arbitrary, do not hesitate to change for what works best for you
sleep(10)
# Execute the original command
exec('bundle exec skippy sketchup:debug 2024')
+1 -1
View File
@@ -24,7 +24,7 @@ module JF_RubyToolbar
def self.load_toolbar
@last_dir = "#{$LOAD_PATH[0]}/"
@last_dir = @last_dir.gsub('/', '\\\\\\\\')
@last_dir = File.join($JF_RUBYTOOLBAR, 'speckle_connector')
@last_dir = File.join($JF_RUBYTOOLBAR, 'speckle_connector_3')
curdir = File.dirname __FILE__
# create toolbar
@@ -10,7 +10,7 @@
# Create a link to Plugins folder with this command
# rubocop:disable Layout/LineLength
# New-Item -ItemType SymbolicLink -Path '~\AppData\Roaming\SketchUp\SketchUp 2022\SketchUp\Plugins\speckle_connector_loader.rb' -Target ~\Git\Speckle\speckle-sketchup\_tools\speckle_connector_loader.rb
# New-Item -ItemType SymbolicLink -Path '~\AppData\Roaming\SketchUp\SketchUp 2022\SketchUp\Plugins\speckle_connector_3_loader.rb' -Target ~\Git\Speckle\speckle-sketchup\_tools\speckle_connector_3_loader.rb
# rubocop:enable Layout/LineLength
SKETCHUP_CONSOLE.show # if you want to show Ruby console on startup
@@ -32,7 +32,7 @@ $LOAD_PATH << File.join(speckle_path, '_tools')
$JF_RUBYTOOLBAR = speckle_path
# rubocop:enable Style/GlobalVars
files = %w[speckle_connector jf_RubyPanel su_attributes]
files = %w[speckle_connector_3 jf_RubyPanel su_attributes]
files.each do |ruby_file|
puts "Loading #{ruby_file}"
+8 -2
View File
@@ -4,7 +4,7 @@ import sys
def patch_connector(tag):
"""Patches the connector version within the connector file"""
rb_file = "speckle_connector.rb"
rb_file = "speckle_connector_3.rb"
with open(rb_file, "r") as file:
lines = file.readlines()
@@ -15,6 +15,12 @@ def patch_connector(tag):
print(f"Patched connector version number in {rb_file}")
break
for (index, line) in enumerate(lines):
if 'DEV_MODE = ' in line:
lines[index] = f' DEV_MODE = false\n'
print(f"Patched dev mode to false in {rb_file}")
break
with open(rb_file, "w") as file:
file.writelines(lines)
@@ -45,7 +51,7 @@ def main():
print(f"Patching version: {tag}")
patch_connector(tag)
patch_installer(tag)
# patch_installer(tag)
if __name__ == "__main__":
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

@@ -1,44 +0,0 @@
# frozen_string_literal: true
require 'JSON'
require_relative '../ext/sqlite3'
require_relative '../constants/path_constants'
module SpeckleConnector
# Accounts to communicate with models on user's account.
module Accounts
# Load accounts from user's app data.
def self.load_accounts
db_path = SPECKLE_ACCOUNTS_DB_PATH
unless File.exist?(db_path)
raise(
IOError,
"No Accounts db found. Please read the guide for different options for adding your account:\n
https://speckle.guide/user/manager.html#adding-accounts"
)
end
db = Sqlite3::Database.new(db_path)
rows = db.exec('SELECT * FROM objects')
db.close
rows.map { |row| JSON.parse(row[1]) }
end
def self.get_account_by_id(id)
accounts = load_accounts
accounts.select { |acc| acc['id'] == id }[0]
end
# Default account on the user computer.
def self.default_account
accounts = load_accounts
accounts.select { |acc| acc['isDefault'] }[0] || accounts[0]
end
# Try to get local server account for debug/test purposes.
def self.try_get_local_server_account
accounts = load_accounts
accounts.select { |acc| acc['serverInfo']['url'].include?('localhost') }[0] || nil
end
end
end
@@ -1,47 +0,0 @@
# frozen_string_literal: true
require_relative '../action'
require_relative '../../cards/send_card'
require_relative '../../cards/receive_card'
require_relative '../../filters/send/everything_filter'
require_relative '../../filters/send/selection_filter'
require_relative '../../filters/send_filters'
require_relative '../../sketchup_model/dictionary/model_card_dictionary_handler'
module SpeckleConnector
module Actions
# Action to add send card.
class AddModel < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, resolve_id, data)
if data['typeDiscriminator'] == 'ReceiverModelCard'
receive_card = Cards::ReceiveCard.new(data['id'], data['accountId'],
data['projectId'], data['projectName'],
data['modelId'], data['modelName'],
data['referencedObject'])
SketchupModel::Dictionary::ModelCardDictionaryHandler
.save_card_to_model(receive_card, state.sketchup_state.sketchup_model)
new_speckle_state = state.speckle_state.with_receive_card(receive_card)
state = state.with_speckle_state(new_speckle_state)
js_script = "baseBinding.receiveResponse('#{resolve_id}')"
return state.with_add_queue_js_command('addSendCard', js_script)
end
send_filter = Filters::SendFilters.get_filter_from_ui_data(data['sendFilter'])
# Init card and add to the state
send_card = Cards::SendCard.new(data['id'], data['accountId'], data['projectId'], data['modelId'],
send_filter, {})
SketchupModel::Dictionary::ModelCardDictionaryHandler
.save_card_to_model(send_card, state.sketchup_state.sketchup_model)
new_speckle_state = state.speckle_state.with_send_card(send_card)
state = state.with_speckle_state(new_speckle_state)
# Resolve promise
js_script = "baseBinding.receiveResponse('#{resolve_id}')"
state.with_add_queue_js_command('addSendCard', js_script)
end
end
end
end
@@ -1,30 +0,0 @@
# frozen_string_literal: true
require_relative '../action'
require_relative '../../cards/send_card'
require_relative '../../filters/send_filters'
require_relative '../../sketchup_model/dictionary/model_card_dictionary_handler'
module SpeckleConnector
module Actions
# Add model to document state.
class AddModelToDocumentState < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, resolve_id, model)
puts model.to_json
send_filter = Filters::SendFilters.get_filter_from_ui_data(model['sendFilter'])
send_card = Cards::SendCard.new(model['id'], model['accountId'], model['projectId'], model['modelId'], send_filter, {})
SketchupModel::Dictionary::ModelCardDictionaryHandler
.save_card_to_model(send_card, state.sketchup_state.sketchup_model)
new_speckle_state = state.speckle_state.with_send_card(send_card)
state = state.with_speckle_state(new_speckle_state)
js_script = "baseBinding.receiveResponse('#{resolve_id}')"
state.with_add_queue_js_command('addModelToDocumentState', js_script)
end
end
end
end
@@ -1,40 +0,0 @@
# frozen_string_literal: true
require_relative '../action'
require_relative '../../filters/send_filters'
require_relative '../../sketchup_model/dictionary/model_card_dictionary_handler'
module SpeckleConnector
module Actions
# Gets document state.
class GetDocumentState < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, resolve_id)
send_cards_hash = SketchupModel::Dictionary::ModelCardDictionaryHandler
.get_cards_from_dict(state.sketchup_state.sketchup_model)
send_cards = send_cards_hash.collect do |id, card|
filter = Filters::SendFilters.get_filter_from_document(card['sendFilter'])
send_card = Cards::SendCard.new(id, card['account_id'], card['project_id'], card['model_id'], filter, {})
new_speckle_state = state.speckle_state.with_send_card(send_card)
state = state.with_speckle_state(new_speckle_state)
{
id: send_card.id,
accountId: send_card.account_id,
projectId: send_card.project_id,
modelId: send_card.model_id,
sendFilter: send_card.send_filter,
typeDiscriminator: send_card.type_discriminator
}
end
model_state = { models: send_cards }
js_script = "baseBinding.receiveResponse('#{resolve_id}', #{model_state.to_json})"
state.with_add_queue_js_command('getDocumentState', js_script)
end
end
end
end
@@ -1,38 +0,0 @@
# frozen_string_literal: true
require_relative '../action'
require_relative '../../filters/send_filters'
require_relative '../../sketchup_model/dictionary/model_card_dictionary_handler'
module SpeckleConnector
module Actions
# Gets model state.
class GetModelState < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, resolve_id)
send_cards_hash = SketchupModel::Dictionary::ModelCardDictionaryHandler
.get_cards_from_dict(state.sketchup_state.sketchup_model)
send_cards = send_cards_hash.collect do |id, card|
filters = Filters::SendFilters.get_filters_from_model(card['filters'])
send_card = Cards::SendCard.new(id, card['account_id'], card['project_id'], card['model_id'], filters)
new_speckle_state = state.speckle_state.with_send_card(send_card)
state = state.with_speckle_state(new_speckle_state)
{
accountId: send_card.account_id,
projectId: send_card.project_id,
modelId: send_card.model_id,
filters: send_card.filters
}
end
model_state = { sendCards: send_cards }
js_script = "baseBinding.receiveResponse('#{resolve_id}', #{model_state.to_json})"
state.with_add_queue_js_command('getModelState', js_script)
end
end
end
end
@@ -1,45 +0,0 @@
# frozen_string_literal: true
require_relative '../action'
require_relative '../../sketchup_model/query/entity'
module SpeckleConnector
module Actions
# Action to add send card.
class HighlightModel < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, resolve_id, model_card_id)
# objects_to_highlight = if data['typeDiscriminator'] == 'ReceiverModelCard'
# # model_card = state.speckle_state.receive_cards[model_card_id]
# # TODO: return received objects
# []
# else
# model_card = state.speckle_state.send_cards[model_card_id]
# model_card.send_filter.selected_object_ids
# end
objects_to_highlight = state.speckle_state.send_cards[model_card_id].send_filter.selected_object_ids
state.sketchup_state.sketchup_model.selection.clear
# Flat entities to select entities on card
flat_entities = SketchupModel::Query::Entity.flat_entities(state.sketchup_state.sketchup_model.entities)
flat_entities.each do |entity|
next unless objects_to_highlight.include?(entity.persistent_id)
if entity.is_a?(Sketchup::ComponentDefinition)
state.sketchup_state.sketchup_model.selection.add(entity.instances)
end
state.sketchup_state.sketchup_model.selection.add(entity)
end
state.sketchup_state.sketchup_model.active_view.zoom(state.sketchup_state.sketchup_model.selection)
# Resolve promise
js_script = "baseBinding.receiveResponse('#{resolve_id}')"
state.with_add_queue_js_command('highlightModel', js_script)
end
end
end
end
@@ -1,89 +0,0 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../../actions/send_actions/send_card_expiration_check'
require_relative '../../sketchup_model/utils/face_utils'
require_relative '../../constants/dict_constants'
module SpeckleConnector
module Actions
module Events
# Event actions related to entities.
class EntitiesEventAction < EventAction
# Event action when element added.
class OnElementAdded
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, event_data)
modified_entities = event_data.to_a.collect { |e| e[1] }
# do not copy speckle base object specific attributes, because they are entity specific
modified_entities.each { |entity| entity.delete_attribute(SPECKLE_BASE_OBJECT) }
state
end
end
# Event action when element modified.
class OnElementModified
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, event_data)
speckle_state = state.speckle_state
modified_entity = event_data[0][1]
modified_entities = event_data.collect { |data| data[1] }
new_speckle_state = state.speckle_state.with_changed_object_ids(modified_entities.collect(&:persistent_id))
state = state.with_speckle_state(new_speckle_state)
state = Actions::SendCardExpirationCheck.update_state(state)
# if modified_entity.is_a?(Sketchup::Face)
# path = state.sketchup_state.sketchup_model.active_path
# modified_faces = SketchupModel::Utils::FaceUtils.near_faces(modified_entity.edges)
# path_objects = path.nil? ? [] : path + path.collect(&:definition)
# parent_ids = path_objects.collect(&:persistent_id)
# ids_to_invalidate = modified_faces.collect(&:persistent_id) + parent_ids
# entities_to_invalidate = speckle_entities_to_invalidate(speckle_state, ids_to_invalidate)
# new_speckle_state = invalidate_speckle_entities(speckle_state, entities_to_invalidate)
# # This is the place we can send information to UI for diffing check
# diffing = state.user_state.preferences[:user][:diffing]
# new_speckle_state = new_speckle_state.with_invalid_streams_queue if diffing
# return state.with_speckle_state(new_speckle_state)
# end
state
end
# @param speckle_state [States::SpeckleState] the current state of the Speckle
def self.speckle_entities_to_invalidate(speckle_state, ids)
speckle_state.speckle_entities.to_h.select { |id, _| ids.include?(id) }
end
# @param speckle_state [States::SpeckleState] the current state of the Speckle
def self.invalidate_speckle_entities(speckle_state, entities_to_invalidate)
speckle_entities = speckle_state.speckle_entities
entities_to_invalidate.each do |id, speckle_entity|
edited_speckle_entity = speckle_entity.with_invalid
speckle_entities = speckle_entities.put(id, edited_speckle_entity)
end
speckle_state.with_speckle_entities(speckle_entities)
end
end
# Event action when element removed.
class OnElementRemoved
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, _event_data)
# TODO: Do state updates when element removed
state
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onElementRemoved: OnElementRemoved,
onElementAdded: OnElementAdded,
onElementModified: OnElementModified
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -1,45 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
require_relative '../actions/create_stream'
require_relative '../actions/queue_send'
require_relative '../convertors/to_speckle'
module SpeckleConnector
module Actions
# Sends to speckle.
class OneClickSend < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state)
puts 'send to speckle'
default_account = Accounts.default_account
if default_account.nil?
puts 'No local account found. Please refer to speckle.guide for more information.'
return state
end
sketchup_model = state.sketchup_state.sketchup_model
to_convert = sketchup_model.selection.count > 0 ? sketchup_model.selection : sketchup_model.entities
first_saved_stream = first_saved_stream(sketchup_model)
action = if first_saved_stream.nil?
Actions::CreateStream.new
else
Actions::QueueSend.new(first_saved_stream, convert_to_speckle(sketchup_model, to_convert))
end
action.update_state(state)
end
def self.first_saved_stream(model)
(saved_streams = model.attribute_dictionary('speckle', true)['streams']) or []
saved_streams.nil? || saved_streams.empty? ? nil : saved_streams[0]
end
def self.convert_to_speckle(sketchup_model, to_convert)
converter = Converters::ToSpeckle.new(sketchup_model)
to_convert.map { |entity| converter.convert(entity) }
end
end
end
end
@@ -1,26 +0,0 @@
# frozen_string_literal: true
require_relative '../action'
require_relative '../../convertors/to_native'
require_relative '../../ext/TT_Lib2/progressbar'
module SpeckleConnector
module Actions
# Receive from server.
class AfterGetObjects < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, resolve_id, model_card_id, source_application, root_obj)
model_card = state.speckle_state.receive_cards[model_card_id]
converter = Converters::ToNative.new(state, model_card.model_id, model_card.project_name,
model_card.model_name, source_application)
# Have side effects on the sketchup model. It effects directly on the entities by adding new objects.
state = converter.receive_commit_object(root_obj)
resolve_js_script = "receiveBinding.receiveResponse('#{resolve_id}')"
state.with_add_queue_js_command('receive', resolve_js_script)
end
end
end
end
@@ -1,22 +0,0 @@
# frozen_string_literal: true
require_relative '../action'
require_relative '../../ui_data/sketchup/selection_info'
module SpeckleConnector
module Actions
# Action to get selection.
class GetSelection < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state)
selected_object_ids = state.sketchup_state.sketchup_model.selection.collect(&:persistent_id)
summary = "Selected #{selected_object_ids.length} objects."
selection_info = UiData::Sketchup::SelectionInfo.new(selected_object_ids, summary)
# js_script = "selectionBinding.receiveResponse('#{resolve_id}', #{selection_info.to_json})"
js_script = "selectionBinding.emit('setSelection', #{selection_info.to_json})"
state.with_add_queue_js_command('setSelection', js_script)
end
end
end
end
@@ -1,86 +0,0 @@
# frozen_string_literal: true
require_relative '../action'
require_relative '../../accounts/accounts'
require_relative '../../convertors/units'
require_relative '../../convertors/to_speckle'
require_relative '../../operations/send'
require_relative '../../ext/TT_Lib2/progressbar'
require_relative '../../ext/worker'
module SpeckleConnector
module Actions
# Send to server.
class Send < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, resolve_id, model_card_id)
model_card = state.speckle_state.send_cards[model_card_id]
account = Accounts.get_account_by_id(model_card.account_id)
converter = Converters::ToSpeckle.new(state, model_card_id, model_card.send_filter)
new_speckle_state, base = converter.convert_selection_to_base(state.user_state.preferences)
id, total_children_count, batches, new_speckle_state = converter.serialize(base, new_speckle_state,
state.user_state.preferences)
# update_test(state)
puts("converted #{base.count} objects for stream #{@stream_id}")
state = state.with_speckle_state(new_speckle_state)
resolve_js_script = "sendBinding.receiveResponse('#{resolve_id}')"
state = state.with_add_queue_js_command('send', resolve_js_script)
args = {
modelCardId: model_card_id,
projectId: model_card.project_id,
modelId: model_card.model_id,
token: account['token'],
serverUrl: account['serverInfo']['url'],
accountId: model_card.account_id,
message: model_card.message,
sendObject: {
id: id,
totalChildrenCount: total_children_count,
batches: batches
}
}
js_script = "sendBinding.emit('sendViaBrowser', #{args.to_json})"
state.with_add_queue_js_command('sendViaBrowser', js_script)
end
def self.update_test(state)
dialog = UI::HtmlDialog.new(
{
:dialog_title => 'Dialog Example',
:preferences_key => 'com.sample.plugin',
:scrollable => true,
:resizable => true,
:width => 600,
:height => 400,
:left => 10,
:top => 10,
:min_width => 50,
:min_height => 50,
:max_width =>1000,
:max_height => 1000,
:style => UI::HtmlDialog::STYLE_DIALOG
})
html = '<div id="hi"><b>Hello world!</b></div>'
dialog.set_html(html)
dialog.show
action = Proc.new do |status|
js_command = "document.getElementById('hi').innerHTML = '<b>#{status}</b>'"
log_js_command = "console.log('test')"
dialog.execute_script(js_command)
dialog.execute_script(log_js_command)
end
selected_object_ids = state.sketchup_state.sketchup_model.selection.collect(&:persistent_id)
state.worker.add_jobs(1000.times.to_a.map { |i| Job.new(i, &action) })
state.worker.do_work(Time.now.to_f, &action)
end
end
end
end
-39
View File
@@ -1,39 +0,0 @@
# frozen_string_literal: true
require_relative '../speckle_objects/other/color'
module SpeckleConnector
module Cards
# Card for sketchup connector to communicate speckle.
class Card < Hash
# @return [String] id of the card.
attr_reader :id
# @return [String] account id of the card.
attr_reader :account_id
# @return [String] project id of the card.
attr_reader :project_id
# @return [String] model id of the card.
attr_reader :model_id
# @return [Boolean] card is valid or not.
attr_reader :valid
def initialize(card_id, account_id, project_id, model_id)
super()
@id = card_id
@account_id = account_id
@project_id = project_id
@model_id = model_id
@valid = true
self[:id] = card_id
self[:account_id] = account_id
self[:project_id] = project_id
self[:model_id] = model_id
self[:valid] = @valid
end
end
end
end
@@ -1,36 +0,0 @@
# frozen_string_literal: true
require_relative 'card'
module SpeckleConnector
module Cards
# Receive card for sketchup connector to communicate speckle.
class ReceiveCard < Card
attr_reader :type_discriminator
# @return [String, NilClass] message to send
attr_reader :message
# @return [String] object id to receive
attr_reader :object_id
# @return [String] name of the project
attr_reader :project_name
# @return [String] name of the model
attr_reader :model_name
def initialize(card_id, account_id, project_id, project_name, model_id, model_name, object_id)
super(card_id, account_id, project_id, model_id)
@object_id = object_id
self[:object_id] = object_id
self[:model_name] = model_name
self[:project_name] = project_name
@model_name = model_name
@project_name = project_name
@type_discriminator = 'ReceiverModelCard'
self[:type_discriminator] = @type_discriminator
end
end
end
end
-31
View File
@@ -1,31 +0,0 @@
# frozen_string_literal: true
require_relative 'card'
module SpeckleConnector
module Cards
# Send card for sketchup connector to communicate speckle.
class SendCard < Card
# @return [Filters::Send::EverythingFilter | Filters::Send::SelectionFilter | Filters::Send::LayerFilter] filter of the card.
attr_reader :send_filter
# @return [Object] send settings of the card.
attr_reader :send_settings
attr_reader :type_discriminator
# @return [String, NilClass] message to send
attr_reader :message
def initialize(card_id, account_id, project_id, model_id, send_filter, send_settings)
super(card_id, account_id, project_id, model_id)
@send_filter = send_filter
@send_settings = send_settings
@type_discriminator = 'SenderModelCard'
self[:sendFilter] = send_filter
self[:sendSettings] = send_settings
self[:type_discriminator] = @type_discriminator
end
end
end
end
@@ -1,19 +0,0 @@
# frozen_string_literal: true
require_relative 'card'
module SpeckleConnector
module Cards
# Send card for sketchup connector to communicate speckle.
class SendCardMultipleFilters < Card
# @return [Hash{String=>Filter}] filters of the card.
attr_reader :filters
def initialize(card_id, account_id, project_id, model_id, filters)
super(card_id, account_id, project_id, model_id)
@filters = filters
self[:filters] = filters
end
end
end
end
@@ -1,8 +0,0 @@
# frozen_string_literal: true
module SpeckleConnector
APP_OBSERVER = 'SpeckleConnector::Observers::AppObserver'
ENTITIES_OBSERVER = 'SpeckleConnector::Observers::EntitiesObserver'
MODEL_OBSERVER = 'SpeckleConnector::Observers::ModelObserver'
SELECTION_OBSERVER = 'SpeckleConnector::Observers::SelectionObserver'
end
-23
View File
@@ -1,23 +0,0 @@
#-------------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
module SpeckleConnector
module TT
module Lib
### CONSTANTS ### ------------------------------------------------------------
# Plugin information
PLUGIN_ID = 'TT_Lib2'.freeze
PLUGIN_NAME = 'TT_Lib²'.freeze
PLUGIN_VERSION = '2.13.1'.freeze
end # module Lib
end # module TT
end
@@ -1,74 +0,0 @@
#-----------------------------------------------------------------------------
#
# CHANGELOG
# 2.5.0 - 18.10.2010
# * Upgraded JQuery to 1.4.4
# * Now bundles JQuery minimized
# * Added Win32Utils' Win32::API under TT::Win32::API
# * Added module: TT::Bezier
# * Added module: TT::Edge
# * Added module: TT::Edges
# * Added module: TT::Faces
# * Added module: TT::Materials
# * Added module: TT::Gizmo::Axis
# * Added module: TT::Win32
# * Added class: TT::Babelfish
# * Added class: TT::Dimension
# * Added class: TT::GUI::ToolWindow
# * Added method: TT::debug
# * Added method: TT::Point3d.douglas_peucker
# * Added method: TT::Point3d.simplify_curve
# * Added method: TT::Geom3d.interpolate_linear
# * Added method: TT::Geom3d.average_point
# * Added method: TT::GUI::Button.custom_properties
# * Added method: TT::GUI::Control.add_event
# * Added method: TT::GUI::Control.call_event
# * Added method: TT::GUI::Control.positioned?
# * Added method: TT::GUI::Control.properties
# * Added method: TT::GUI::ContainerElement.add_controls_to_webdialog
# * Added method: TT::GUI::ContainerElement.get_control_by_ui_id
# * Added methods: TT::GUI::Listbox
# * Added method: TT::GUI::Window.add_action_callback
# * Added method: TT::GUI::Window.add_control_to_webdialog
# * Added method: TT::GUI::Window.set_client_size
# * Added method: TT::System.is_windows?
# * Changed: TT::Gizmo::Manipulator
# * Changed: TT::GUI::Inputbox now inherits from TT::GUI::ToolWindow
# * Changed: TT::GUI::Inputbox.add_control accepts a +key+ argument a control id.
# * Changed: TT::GUI::Inputbox.prompt returns a Hash instead of Array.
# * Changed: TT::UV_Plane
# * Fixed: TT::JSON.to_s - Now handles Symbols as values.
# * Plus lots lots more - lots track of it all.
#
# 2.4.0 - 22.09.2010
# * Added module: TT::Binary
# * Added module: TT::GUI
# * Added class: TT::GUI::Window
# * Added class: TT::GUI::Inputbox
# * Added class: TT::JSON
# * Added module: TT::System
# * Added module: TT::Locale
# * Added module: TT::Cursor
#
# 2.3.0 - 08.09.2010
# * Added method: TT::Geom3d.spiral_sphere
# * Fixed: TT::Ray.test
#
# 2.2.0 - 06.09.2010
# * Added module: TT::Bounds
#
# 2.1.1 - 04.09.2010
# * Fixed bug: Ray.test (Workaround for SU8.0 bug)
#
# 2.1.0 - 03.09.2010
# * Added module: TT::Selection
# * Added module: TT::UVQ
# * Added class: TT::UV_Plane
# * Added class: TT::Gizmo::Manipulator
# * Added method: TT::Entities.bounds
# * Fixed: TT::Ray.test
#
# 2.0.0 - 01.09.2010
# * Initial Release
#
#-----------------------------------------------------------------------------
@@ -1,12 +0,0 @@
= TT_Lib2
* {SCF Thread}[http://forums.sketchucation.com/viewtopic.php?f=323&t=23307]
== Description
Library of common methods and classes for Google SketchUp Ruby plugins.
== Author
* Thomas Thomassen
* thomas[at]thomthom[dot]net
Binary file not shown.
-70
View File
@@ -1,70 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# Collection of Arc methods.
#
# @since 2.0.0
module SpeckleConnector
module TT::Arc
# Checks if a given +Curve+ is an +ArcCurve+ and not a polygon.
#
# Check for polygon requires SketchUp 7.1M1 or newer. Older SketchUp
# versions will not check for this property.
#
# @param [Sketchup::Curve] curve
# @since 2.0.0
def self.is?( curve )
return false if curve.respond_to?( :is_polygon? ) && curve.is_polygon?
curve.is_a?(Sketchup::ArcCurve)
end
# Checks if a given +Curve+ makes an circle and is not a polygon.
#
# Check for polygon requires SketchUp 7.1M1 or newer. Older SketchUp
# versions will not check for this property.
#
# SketchUp has a bug where an ArcCurve some times has 720 degrees angle
# instead of 360. This method handles this.
#
# @param [Sketchup::Curve] curve
# @since 2.0.0
def self.circle?( curve )
return false unless self.is?( curve )
return ((curve.end_angle - curve.start_angle).radians >= 360) ? true : false
end
# Based on Chris Fullmers "Exploded Arc Centerpoint Finder".
# Calculates the centre points of an arc given two edges.
#
# @param [Sketchup::Edge] e1
# @param [Sketchup::Edge] e2
#
# @return [Geom::Point3d, nil]
# @since 2.0.0
def self.exploded_center(e1, e2)
# Two edges representing an arc must have the same length and can not
# be parallel.
return nil if e1.length != e2.length
v1 = e1.line[1]
v2 = e2.line[1]
return nil if v1.parallel?( v2 )
# Get mid-point of edges from where intersecting lines will origin.
m1 = Geom.linear_combination( 0.5, e1.start.position, 0.5, e1.end.position )
m2 = Geom.linear_combination( 0.5, e2.start.position, 0.5, e2.end.position )
# Get the vectors for the intersecting lines
z_axis = v1 * v2
line1 = [m1, v1 * z_axis]
line2 = [m2, v2 * z_axis]
# Return the center
Geom.intersect_line_line(line1, line2)
end
end # module TT::Arc
end
@@ -1,63 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# Collection of AttributeDictionary methods.
#
# @since 2.5.0
module SpeckleConnector
module TT::Attributes
# Compare two +AttributeDictionaries+ objects.
#
# @param [Sketchup::AttributeDictionaries] dictionaries1
# @param [Sketchup::AttributeDictionaries] dictionaries2
#
# @return [Boolean]
# @since 2.5.0
def self.dictionaries_equal?( dictionaries1, dictionaries2 )
if dictionaries1.nil? || dictionaries2.nil?
if dictionaries1.nil? && dictionaries2.nil?
return true
else
return false
end
end
for dictionary in dictionaries1
return false unless d = dictionaries2[ dictionary.name ]
return false unless self.dictionary_equal?( dictionary, d, false )
end
return true
end
# Compare two +AttributeDictionary+ objects. By defaults their names must
# match, but one can set +compare_name+ to +false+ to only compare their
# content.
#
# @param [Sketchup::AttributeDictionary] dictionary1
# @param [Sketchup::AttributeDictionary] dictionary2
# @param [Boolean] compare_name
#
# @return [Boolean]
# @since 2.5.0
def self.dictionary_equal?( dictionary1, dictionary2, compare_name = true )
if compare_name
return false unless dictionary1.name == dictionary2.name
end
return false unless dictionary1.length == dictionary2.length
return false unless dictionary1.keys == dictionary2.keys
for key in dictionary1.keys
return false unless dictionary1[key] == dictionary2[key]
end
return true
end
end # module TT::Attributes
end
@@ -1,348 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# Translation dictionary. Pass strings through the +translate+ or +tr+ method
# to translate strings to the language given in to the last +load+ call.
#
# Silently eats errors and pass through the original string if a translation
# cannot be made.
#
# module MyPlugin
#
# # Assigning the instance to a constant makes it into
# # a easy to use shorthand that works in any sub-modules/classes.
# S = TT::Babelfish.new( l10n_path )
#
# def self.init
# S.load( 'fr' )
# end
#
# def self.foo
# puts S.tr( 'Hello World' )
# end
#
# end # module
#
#
# @since 2.5.0
module SpeckleConnector
class TT::Babelfish
# @since 2.5.0
attr_reader( :l10n, :default_l10n, :path, :file_ex )
# @param [String] l10n_path The path where the translation files are located.
# @param [String] default_l10n The source translation code.
# @param [String] file_ex The file extension of the translation files.
#
# @since 2.5.0
def initialize(l10n_path, default_l10n = 'en', file_ex = 'l10n')
@file_ex = file_ex.dup
@l10n = default_l10n.dup
@default_l10n = default_l10n.dup
@dictionary = {}
@metadata = {} # (!) Default data
path = File.expand_path( l10n_path )
unless File.exist?( path )
raise( ArgumentError, 'Path does not exist.' )
end
@path = path
end
# @return [String]
# @since 2.5.0
def inspect
if @metadata
name = @metadata['name']
size = @dictionary.size
"<#{self.class}:#{@l10n} - Speaks #{size} phrases in #{name}>"
else
"<#{self.class}:#{@l10n}>"
end
end
# @return [Hash] A copy of the current translation dictionary.
# @since 2.5.0
def dictionary
@dictionary.dup
end
# @return [Hash] A copy of the current translation dictionary.
# @since 2.5.0
def metadata
@metadata.dup
end
# Loads a translation dictionary.
#
# @param [String] l10n must represent the base name of a .l10n file in the
# language folder.
#
# @return [Boolean] +true+ on success, +false+ on failure.
# @since 2.5.0
def load(l10n)
# Special case for default language.
if l10n == @default_l10n
@l10n = @default_l10n
@metadata.clear # (!) Default data
@dictionary.clear
return true
end
# Work out the filename and ensure it exists.
filename = File.join(@path, "#{l10n}.#{@file_ex}")
unless File.exists?(filename)
puts "Babelfish - Failed to load l10n: #{l10n} (#{filename}). No such file."
return false
end
# Open the language file and parse the content. Unicode Regex ensures that
# UTF-8 files are parsed properly.
#
# Using a proxy hash prevents a failed appempt of loading a translation
# file from ruining the existing translation hash.
#
# Garbage data - data that doesn't match the expected format is ignored.
dictionary = {}
metadata = {}
File.open(filename, 'r') { |file|
metadata = read_metadata(file)
unless metadata
puts "Babelfish - Failed to load l10n: #{l10n} (#{filename}). Invalid data."
return false
end
file.each { |line|
next if line.match(/^\s*#/u) # Ignore comments
next if line.match(/^\s*$/u) # Ignore empty lines
if match = line.match(/^\s*"(.*)"\s*=\s*"(.*)"\s*$/u)
dictionary[ match[1] ] = match[2]
end
}
}
@l10n = l10n.dup
@metadata = metadata
@dictionary = dictionary
true
end
# Looks up the string and returns a translated string.
# If no translated string exists, the original is returned.
#
# Silently outputs any errors to the console and returns the original
# string.
#
# @param [String] string
# @param [Array] args
#
# @return [String]
# @since 2.5.0
def translate(string, *args)
translated = @dictionary[string] || string
sprintf(translated, *args)
rescue => e
p e.message
puts e.backtrace.join("\n")
sprintf(string, *args)
end
alias :tr :translate
# Used to handle singluar vs plural form.
#
# When +expression+ is a boolean, +true+ represent singular.
#
# @param [Numeric, Enumerable, Boolean] expression
# @param [String] singular
# @param [String] multiple
# @param [Array] args
#
# @return [String]
# @since 2.12.0
def plural(expression, singular, multiple, *args)
single = false
case expression
when Numeric
single = expression.to_i == 1
when Enumerable
[:size, :length, :count].each { |symbol|
single = expression.send(symbol) == 1 if expression.respond_to?(symbol)
}
else
single = !!expression
end
string = single ? singular : multiple
translate(string, *args)
end
alias :pl :plural
# Utility accessor to return a string without arguments.
#
# @param [String] string
#
# @return [String]
# @since 2.12.0
def [](string)
translate(string)
end
# Iterates the language folder for .l10n files and extracts the required
# display name in the first line of the file.
#
# Returns a hash of all the languages availible. The key is the translation
# code and the value is a hash with meta data.
#
# The meta data contains one required key: 'name' and optionally 'author'
# and/or 'contact'.
#
# {
# 'no' => {
# 'name' => '...'
# },
# 'fr' => {
# 'name' => '...',
# 'author' => '...',
# 'contact' => '...'
# }
# }
#
# How to get an array of availible language codes:
# codes = babelfish.translations.keys
#
# Silently eats errors and output any errors or warnings to the console.
#
# @return [Hash]
# @since 2.5.0
def translations
lang = {}
file_filter = File.join(@path, "*.#{@file_ex}")
Dir.glob( file_filter ) { |filename|
lang_code = File.basename(filename, ".#{@file_ex}")
File.open(filename, 'r') { |file|
# UTF-8 files might have a BOM mark, account for this.
metadata = read_metadata(file)
if metadata
lang[lang_code] = metadata
else
puts "WARNING: No @title in #{filename}"
end
}
}
lang # (?) This method appears to return false unless this is here...
rescue => e
p e.message
puts e.backtrace.join("\n")
ensure
lang
end
# Try to pick a language based on the Sketchup locale.
#
# 1. Tries exact matches.
# 2. Tries to find by language code, (if SU locale is en-GB) it tries to find
# "en.l10n".
# 3. Tries to find a similar dialect. (if SU locale is en-GB) it will consider
# "en-US.l10n" a match.
#
# (!)
# Norwegian is nn-NO and nb-NO, no
# This appear to be different from how English system works.
#
# http://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo%28VS.80%29.aspx
#
# @return [String]
# @since 2.5.0
def guess_l10n
su_locale = Sketchup.get_locale.downcase
# Extract list of languages availible
file_filter = File.join( @path, "*.#{@file_ex}" )
languages = Dir.glob( file_filter ).map { |filename|
File.basename(filename, ".#{@file_ex}").downcase
}
# First search for exact match
for lang_code in languages
return lang_code if lang_code == su_locale
end
# Search for partial match - language code
su_country = su_locale.split('-').first
for lang_code in languages
return lang_code if lang_code == su_country
end
# Search for partial match - language code and dialect
for lang_code in languages
country = lang_code.split('-').first
return lang_code if country == su_country
end
# Default
return @default_l10n
end
# Debug method that compares the availible translations against a spesified
# prototype. Outputs the result to the Console.
#
# @param [String] prototype_l10n The translation to compare against.
#
# @return [String]
# @since 2.5.0
def check(prototype_l10n)
puts "\nChecking language files for missing strings against #{prototype_l10n}..."
prototype = self.new( @path, @default_l10n )
prototype.load( prototype_l10n )
temp = self.new( @path, @default_l10n )
keys = prototype.dictionary.keys
prototype.translations.each { |code, data|
next if code == prototype_l10n
temp.load(code)
puts "\n=== #{data['name']} (#{code}) ==="
missing = keys - temp.dictionary.keys
puts "> Missing #{missing.length} keys:"
missing.each { |str| p str }
}
self.load(org)
"\nDone\n\n"
end
private
def read_metadata(file)
match = file.readline.match(/^(?:\xEF\xBB\xBF)?@title:\s*"(.+)"/u)
return nil if match.nil?
# Language data is read into a hash
lang_data = {
'author' => 'unknown',
'contact' => ''
}
# The Name is required and MUST be the first line in the file.
lang_data['name'] = match[1]
# The next following lines are optional and can be in any order,
# but with comments or skipped lines.
2.times {
match = file.readline.match(/@(\w+):\s*"(.+)"/u)
if match && ['author','contact'].include?(match[1])
lang_data[ match[1] ] = match[2]
end
}
lang_data
end
end # class TT::Babelfish
end
@@ -1,13 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
# This file exist only as a compatibility with older version of TT_Lib when
# the implementation was in pure Ruby. It also ensures the correct version for
# the platform is loaded.
require_relative 'core.rb'
require File.join( SpeckleConnector::TT::Lib::PATH_LIBS_CEXT, 'tt_lib2' )
@@ -1,38 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# ...
#
# @since 2.4.0
module SpeckleConnector
module TT::Binary
# Base64 encodes binary data.
#
# @param [Mixed] data
#
# @return [String]
# @since 2.4.0
def self.encode64(data)
return [data].pack('m')
end
# Decodes Base64 strings.
#
# @param [String] string
#
# @return [Mixed]
# @since 2.4.0
def self.decode64(string)
return string.unpack('m')[0]
end
end # module TT::Binary
end
@@ -1,55 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# @example
# class Foo
# extend TT::BooleanAttributes
# battr_accessor :bar
# end
#
# @since 2.7.0
module SpeckleConnector
module TT::BooleanAttributes
# @since 2.7.0
def battr( symbol, writable = false )
self.class_eval {
attr( symbol, writable )
question = "#{symbol}?".to_sym
alias_method( question, symbol )
remove_method( symbol )
}
end
# @since 2.7.0
def battr_accessor( *args )
self.class_eval {
attr_accessor( *args )
for attribute in args
question = "#{attribute}?".to_sym
alias_method( question, attribute )
remove_method( attribute )
end
}
end
# @since 2.7.0
def battr_reader( *args )
self.class_eval {
attr_reader( *args )
for attribute in args
question = "#{attribute}?".to_sym
alias_method( question, attribute )
remove_method( attribute )
end
}
end
end # class TT::BooleanAttributes
end
-112
View File
@@ -1,112 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# Collection of BoundingBox methods.
#
# @since 2.2.0
module SpeckleConnector
module TT::Bounds
# Returns a +Point3d+ from a standard position of the boundingbox.
#
# @param [Geom::BoundingBox] bounds
# @param [Integer] index
#
# @return [Geom::Point3d]
# @since 2.2.0
def self.point(bounds, index)
case index
when 0..7
pt = bounds.corner(index)
when TT::BB_CENTER_FRONT_BOTTOM
p1 = bounds.corner( TT::BB_LEFT_FRONT_BOTTOM )
p2 = bounds.corner( TT::BB_RIGHT_FRONT_BOTTOM )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_CENTER_BACK_BOTTOM
p1 = bounds.corner( TT::BB_LEFT_BACK_BOTTOM )
p2 = bounds.corner( TT::BB_RIGHT_BACK_BOTTOM )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_CENTER_FRONT_TOP
p1 = bounds.corner( TT::BB_LEFT_FRONT_TOP )
p2 = bounds.corner( TT::BB_RIGHT_FRONT_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_CENTER_BACK_TOP
p1 = bounds.corner( TT::BB_LEFT_BACK_TOP )
p2 = bounds.corner( TT::BB_RIGHT_BACK_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_LEFT_CENTER_BOTTOM
p1 = bounds.corner( TT::BB_LEFT_FRONT_BOTTOM )
p2 = bounds.corner( TT::BB_LEFT_BACK_BOTTOM )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_LEFT_CENTER_TOP
p1 = bounds.corner( TT::BB_LEFT_FRONT_TOP )
p2 = bounds.corner( TT::BB_LEFT_BACK_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_RIGHT_CENTER_BOTTOM
p1 = bounds.corner( TT::BB_RIGHT_FRONT_BOTTOM )
p2 = bounds.corner( TT::BB_RIGHT_BACK_BOTTOM )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_RIGHT_CENTER_TOP
p1 = bounds.corner( TT::BB_RIGHT_FRONT_TOP )
p2 = bounds.corner( TT::BB_RIGHT_BACK_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_LEFT_FRONT_CENTER
p1 = bounds.corner( TT::BB_LEFT_FRONT_BOTTOM )
p2 = bounds.corner( TT::BB_LEFT_FRONT_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_RIGHT_FRONT_CENTER
p1 = bounds.corner( TT::BB_RIGHT_FRONT_BOTTOM )
p2 = bounds.corner( TT::BB_RIGHT_FRONT_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_LEFT_BACK_CENTER
p1 = bounds.corner( TT::BB_LEFT_BACK_BOTTOM )
p2 = bounds.corner( TT::BB_LEFT_BACK_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_RIGHT_BACK_CENTER
p1 = bounds.corner( TT::BB_RIGHT_BACK_BOTTOM )
p2 = bounds.corner( TT::BB_RIGHT_BACK_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_LEFT_CENTER_CENTER
p1 = bounds.corner( TT::BB_LEFT_FRONT_BOTTOM )
p2 = bounds.corner( TT::BB_LEFT_BACK_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_RIGHT_CENTER_CENTER
p1 = bounds.corner( TT::BB_RIGHT_FRONT_BOTTOM )
p2 = bounds.corner( TT::BB_RIGHT_BACK_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_CENTER_FRONT_CENTER
p1 = bounds.corner( TT::BB_LEFT_FRONT_BOTTOM )
p2 = bounds.corner( TT::BB_RIGHT_FRONT_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_CENTER_BACK_CENTER
p1 = bounds.corner( TT::BB_LEFT_BACK_BOTTOM )
p2 = bounds.corner( TT::BB_RIGHT_BACK_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_CENTER_CENTER_TOP
p1 = bounds.corner( TT::BB_LEFT_FRONT_TOP )
p2 = bounds.corner( TT::BB_RIGHT_BACK_TOP )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_CENTER_CENTER_BOTTOM
p1 = bounds.corner( TT::BB_LEFT_FRONT_BOTTOM )
p2 = bounds.corner( TT::BB_RIGHT_BACK_BOTTOM )
pt = Geom.linear_combination( 0.5, p1, 0.5, p2 )
when TT::BB_CENTER_CENTER_CENTER
pt = bounds.center
end
pt
end
end # module TT::Bounds
end
@@ -1,241 +0,0 @@
#-------------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-------------------------------------------------------------------------------
module SpeckleConnector
module TT
# Loads the appropriate C Extension loader after ensuring the appropriate
# version has been copied from the staging area.
#
# @since 2.9.0
class CExtensionManager
class IncompatibleVersion < RuntimeError; end
VERSION_PATTERN = /\d+\.\d+\.\d+$/
# The `path` argument should point to the path where a 'stage' folder is
# located with the following folder structure:
#
# + `path`
# +-+ stage
# +-+ 1.8
# | +-+ HelloWorld.so
# | + HelloWorld.bundle
# +-+ 2.0
# +-+ HelloWorld.so
# + HelloWorld.bundle
#
# The appropriate file will be copied on demand to a folder structure like:
# `path`/<EXTENSION_VERSION>/<RUBY_VERSION>/HelloWorld.so
#
# When a new version is deployed the files will be copied again from the
# staging area to a new folder named with the new extension version.
#
# The old versions are cleaned up if possible. This attempt is done upon
# each time #prepare_path is called.
#
# This way the C extensions can be updated because they are never loaded
# from the staging folder directly.
#
# @param [String] path The location where the C Extensions are located.
# @since 2.9.0
def initialize( path, version )
# ENV, __FILE__, $LOAD_PATH, $LOADED_FEATURE and more might return an
# encoding different from UTF-8. It's often ASCII-US or ASCII-8BIT.
# If the developer has derived from these strings the encoding sticks with
# it and will often lead to errors further down the road when trying to
# load the files. To work around this the path is attempted to be
# relabeled as UTF-8 if we can produce a valid UTF-8 string.
# I'm forcing an encoding instead of converting because the encoding label
# of the strings seem to be consistently mislabeled - the data is in
# fact UTF-8.
if path.respond_to?(:encoding)
test_path = path.dup.force_encoding("UTF-8")
path = test_path if test_path.valid_encoding?
end
unless version =~ VERSION_PATTERN
raise ArgumentError, 'Version must be in "X.Y.Z" format'
end
unless File.directory?( path )
raise IOError, "Stage path not found: #{path}"
end
@version = version
@path = path
@stage = File.join( path, 'stage' )
@target = File.join( path, version )
@log = []
# See method comments for more info.
#require_file_utils()
end
# Copies the necessary C Extension libraries to a version dependent folder
# from where they can be loaded. This will allow the SketchUp RBZ installer
# to update the extension without running into errors when trying to
# overwrite files from previous installation.
#
# @return [String] The path where the extensions are located.
# @since 2.9.0
def prepare_path
log("prepare_path")
pointer_size = ['a'].pack('P').size * 8 # 32 or 64
ruby = RUBY_VERSION.split('.')[0..1].join('.') # Get Major.Minor string.
platform = ( TT::System::PLATFORM_IS_OSX ) ? 'osx' : 'win'
platform = "#{platform}#{pointer_size}"
stage_path = File.join( @stage, ruby, platform )
target_path = File.join( @target, ruby, platform )
fallback = false
log("> stage_path: #{stage_path}")
log("> target_path: #{target_path}")
begin
# Copy files if target doesn't exist.
unless File.directory?(stage_path)
raise IncompatibleVersion, "Staging directory not found: #{stage_path}"
end
unless File.directory?( target_path )
log("MKDIR: #{target_path}")
require_file_utils() # See method comments for more info.
log(FileUtils.mkdir_p( target_path ))
end
stage_content = Dir.entries( stage_path )
target_content = Dir.entries( target_path )
log("> stage_content: #{stage_content}")
log("> target_content: #{target_content}")
unless (stage_content - target_content).empty?
log("COPY: #{stage_path} => #{target_path}")
require_file_utils() # See method comments for more info.
log(FileUtils.copy_entry( stage_path, target_path ))
end
# Clean up old versions.
version_pattern = /\d+\.\d+\.\d+$/
filter = File.join( @path, '*' )
log("> cleanup: #{filter}")
Dir.glob( filter ).each { |entry|
log(">>> entry: #{entry}")
next unless File.directory?( entry )
log(">>> @target: #{@target} (#{entry.downcase == @target.downcase})")
log(">>> @stage: #{@stage} (#{entry.downcase == @stage.downcase})")
log(">>>>> @target vs entry")
log(">>>>> #{entry.class.name}: #{entry.bytes}") if entry.respond_to?(:bytes)
log(">>>>> #{@target.class.name}: #{@target.bytes}") if @target.respond_to?(:bytes)
next if entry.downcase == @stage.downcase || entry.downcase == @target.downcase
log(">>> match: #{entry =~ version_pattern}")
next unless entry =~ version_pattern
begin
log("REMOVE: #{entry}")
require_file_utils # See method comments for more info.
log(FileUtils.rm_r( entry ))
rescue
log_warn("#{TT::Lib::PLUGIN_NAME} - Unable to clean up: #{entry}")
end
}
rescue Errno::EACCES
if fallback
UI.messagebox(
"Failed to load #{TT::Lib::PLUGIN_NAME}. Missing permissions to " <<
"Plugins and temp folder."
)
raise
else
# Even though the temp folder contains the username, it appear to be
# returned in DOS 8.3 format which Ruby 1.8 can open. Fall back to
# using the temp folder for these kind of systems.
log_warn("#{TT::Lib::PLUGIN_NAME} - Unable to access: #{target_path}")
temp_tt_lib_path = File.join( temp_path, TT::Lib::PLUGIN_ID )
target_path = File.join( temp_tt_lib_path, @version, ruby, platform )
log_warn("#{TT::Lib::PLUGIN_NAME} - Falling back to: #{target_path}")
fallback = true
retry
end
end
target_path
end
# @return [String]
# @since 2.9.0
def to_s
object_hex_id = "0x%x" % (self.object_id << 1)
"<##{self.class}::#{object_hex_id}>"
end
alias :inspect :to_s
# @since 2.10.7
def print_log
puts @log.join("\n")
end
private
# @since 2.10.7
def log(value)
string = value.is_a?(String) ? value : value.inspect
@log << string
string
end
# @since 2.10.7
def log_warn(value)
puts log(value)
end
# Return the system temp path from the environment variables.
#
# @since 2.10.7
def temp_path
File.expand_path( ENV['TMPDIR'] || ENV['TMP'] || ENV['TEMP'] )
end
# Attempt to load the Standard Library FileUtils module. Fall back to
# bundled 1.8 copy.
#
# @since 2.9.0
def require_file_utils
if RUBY_VERSION.to_i == 1
path = File.dirname( __FILE__ ) # rubocop:disable SketchupSuggestions/FileEncoding
require File.join( path, 'thirdparty', 'fileutils.rb' )
else
begin
require 'fileutils'
rescue LoadError
# A bug in SketchUp 2014 M0 caused the drive letter for the Ruby
# Standard Library to be incorrect if SketchUp was started by
# clicking a SKP file on a drive (network drive?) different from
# where SketchUp was installed.
#
# This cause the fileutils to fail to load. To work around this the
# file is required right before it is needed. That should make the
# file needed only the first time after installing a new version.
# Makes the code awkward and ugly, but alas. :(
std_lib_path = Sketchup.find_support_file( 'Tools/RubyStdLib' ) # rubocop:disable SketchupSuggestions/SketchupFindSupportFile
unless $LOAD_PATH.include?( std_lib_path )
UI.messagebox(
'Due to a bug in SketchUp 2014 M0 the standard library was ' <<
'not loaded. Please start SketchUp from a link on the drive ' <<
'it was installed to instead of from clicking an SKP on a ' <<
'different drive.'
) unless @load_error_displayed
@load_error_displayed = true
end
puts $LOAD_PATH.join("\n")
raise
end
end # if RUBY_VERSION
end
end # class
end # module
end
@@ -1,28 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# Collection of Color methods.
#
# @since 2.5.0
module SpeckleConnector
module TT::Color
# Safely clones a Sketchup::Color object. Sketchup::Color.clone appear to
# be bugged and prone to crash SU.
#
# @param [Sketchup::Color] color
#
# @return [Sketchup::Color]
# @since 2.5.0
def self.clone(color)
Sketchup::Color.new( *color.to_a )
end
end # module TT::Instance
end
-537
View File
@@ -1,537 +0,0 @@
#-------------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-------------------------------------------------------------------------------
Sketchup::require 'sketchup.rb'
# Sketchup::require '../TT_Lib2.rb'
require_relative 'system.rb'
require_relative 'c_extension_manager.rb'
#-------------------------------------------------------------------------------
# Root namespace for Thomas Thomassen (ThomThom, TT)
#
# Do not modify or extend!
#
# @since 2.0.0
module SpeckleConnector
module TT
### CONSTANTS ### ------------------------------------------------------------
# BoundingBox Constants
# @since 2.0.0
BB_LEFT_FRONT_BOTTOM = 0
BB_RIGHT_FRONT_BOTTOM = 1
BB_LEFT_BACK_BOTTOM = 2
BB_RIGHT_BACK_BOTTOM = 3
BB_LEFT_FRONT_TOP = 4
BB_RIGHT_FRONT_TOP = 5
BB_LEFT_BACK_TOP = 6
BB_RIGHT_BACK_TOP = 7
BB_CENTER_FRONT_BOTTOM = 8
BB_CENTER_BACK_BOTTOM = 9
BB_CENTER_FRONT_TOP = 10
BB_CENTER_BACK_TOP = 11
BB_LEFT_CENTER_BOTTOM = 12
BB_LEFT_CENTER_TOP = 13
BB_RIGHT_CENTER_BOTTOM = 14
BB_RIGHT_CENTER_TOP = 15
BB_LEFT_FRONT_CENTER = 16
BB_RIGHT_FRONT_CENTER = 17
BB_LEFT_BACK_CENTER = 18
BB_RIGHT_BACK_CENTER = 19
BB_LEFT_CENTER_CENTER = 20
BB_RIGHT_CENTER_CENTER = 21
BB_CENTER_FRONT_CENTER = 22
BB_CENTER_BACK_CENTER = 23
BB_CENTER_CENTER_TOP = 24
BB_CENTER_CENTER_BOTTOM = 25
BB_CENTER_CENTER_CENTER = 26
BB_CENTER = 26
# UI.messagebox Constants
# @since 2.4.0
MB_ICONHAND = 0x00000010
MB_ICONSTOP = 0x00000010
MB_ICONERROR = 0x00000010
MB_ICONQUESTION = 0x00000020
MB_ICONEXCLAMATION = 0x00000030
MB_ICONWARNING = 0x00000030
MB_ICONASTERISK = 0x00000040
MB_ICONINFORMATION = 0x00000040
MB_ICON_NONE = 80
MB_DEFBUTTON1 = 0x00000000
MB_DEFBUTTON2 = 0x00000100
MB_DEFBUTTON3 = 0x00000200
MB_DEFBUTTON4 = 0x00000300
# PolygonMesh
# @since 2.5.0
MESH_SHARP = 0
MESH_SOFT = 4
MESH_SMOOTH = 8
MESH_SOFT_SMOOTH = 12
# view.draw_points
# @since 2.5.0
POINT_OPEN_SQUARE = 1
POINT_FILLED_SQUARE = 2
POINT_CROSS = 3
POINT_X = 4
POINT_STAR = 5
POINT_OPEN_TRIANGLE = 6
POINT_FILLED_TRIANGLE = 7
# Handle to error message window.
# @since 2.7.0
@lib2_update = nil
def self.lib2_update; @lib2_update; end
def self.lib2_update=(window); @lib2_update = window; end
# Defers execution of the given block.
#
# @param [Numeric] time
#
# @return [Nil]
# @since 2.5.0
def self.defer( time = 0 )
done = false
timer = UI.start_timer( time, false ) {
# (i) Unless the timer is stopped before the messagebox it will
# continue to trigger until the frist messagebox is closed.
unless done
done = true
yield
end
}
nil
end
### LIBRARY ### --------------------------------------------------------------
# TT_Lib related methods.
#
# @since 2.0.0
module Lib
# Library version number.
# @since 2.0.0
VERSION = PLUGIN_VERSION
# Library preference key.
# @since 2.5.0
PREF_KEY = 'TT_Lib2'.freeze
# @since 2.8.0
file = File.expand_path( __FILE__ ) # rubocop:disable SketchupSuggestions/FileEncoding
file.force_encoding( "UTF-8" ) if file.respond_to?( :force_encoding )
PATH = File.dirname( file ).freeze
PATH_LIBS = File.join( PATH, 'libraries' ).freeze
# TT::Lib.cext_manager
def self.cext_manager; @cext_manager; end
begin
@cext_manager = CExtensionManager.new( PATH_LIBS, PLUGIN_VERSION )
PATH_LIBS_CEXT = @cext_manager.prepare_path.freeze
rescue CExtensionManager::IncompatibleVersion => error
unless @compatibility_alert
# Avoid this message being called for every extension that rely on TT_Lib.
@compatibility_alert = true
message = "%{extension_name} version %{version} is not "\
"compatible with this version of SketchUp and could not be loaded. "\
"Please check for updates to the extension."
message %= { extension_name: PLUGIN_NAME, version: PLUGIN_VERSION }
TT.defer(1.0) {
UI.messagebox(message)
}
end
end
# Call this method to check if the installed +TT_Lib+ version is the same
# or newer than +version+. If it's not then a messagebox will appear
# informing the user that a newer +TT_Lib+ is required.
#
# @param [String] version a string with the minimun version required in
# the format 'x.x.x'.
# @param [String] plugin_name a string describing to the user which plugin
# require a newer TT_Lib version.
#
# @return [Boolean]
# @since 2.0.0
def self.compatible?(version, plugin_name = 'A plugin installed')
major, minor, revision = TT::Lib::VERSION.split('.').map { |s| s.to_i }
min_major, min_minor, min_revision = version.split('.').map { |s| s.to_i }
return true if major > min_major
return true if major == min_major && minor > min_minor
return true if major == min_major && minor == min_minor && revision >= min_revision
#UI.messagebox("#{plugin_name} requires a newer version, #{version}, of TT_Lib.")
if TT.lib2_update.nil?
url = 'http://www.thomthom.net/software/sketchup/tt_lib2/errors/outdated'
options = {
:dialog_title => 'TT_Lib² Outdated',
:scrollable => false, :resizable => false, :left => 200, :top => 200
}
w = UI::WebDialog.new( options )
w.set_size( 500, 300 )
arguments = "plugin=#{plugin_name}"
arguments << "&version=#{TT::Lib::VERSION}"
arguments << "&minimum=#{version}"
w.set_url( "#{url}?#{arguments}" )
w.show
TT.lib2_update = w
end
return false
end
# Compiles a list of the current .rb and .rbs files in the library. This is
# used to verify the integirty of the installation upon first run.
#
# @private
# @return [Nil]
# @since 2.5.0
def self.compile_integrity_list
result = UI.messagebox( <<MSG, MB_OKCANCEL )
This method is only intended for development purposes. Don't mess about with it!
Press Cancel.
MSG
#'# Silly Sublime Text doesn't handle HereDoc arguments properly.
return if result == 2 # CANCEL
files = Dir.glob( File.join(self.path, '*.{rb,rbs}') ).map! { |file|
File.basename( file )
}
File.open( self.integrity_check_file, 'w' ) { |output|
output.puts( files )
}
nil
end
# @private
# @since 2.9.5
class IntegrityCheck
attr_reader :unexpected_files, :missing_files
def initialize( file_with_list_of_expected_files )
integrity_file = file_with_list_of_expected_files
@expected_files = IO.readlines( integrity_file ).map! { |file|
file.strip
}
@missing_files = @expected_files.select { |file|
filename = File.join( PATH, file )
!File.exist?( filename )
}
filter = File.join(PATH, '*.{rb,rbs}' )
@existing_files = Dir.glob( filter ).map! { |file|
File.basename( file )
}
@unexpected_files = @existing_files - @expected_files
end
def ok?
@missing_files.empty? && @unexpected_files.empty?
end
def missing_files?
!@missing_files.empty?
end
def unexpected_files?
!@unexpected_files.empty?
end
end # class IntegrityCheck
# Called upon startup. If it's the first time this library is loaded a file
# integrity check is run to ensure all requires files are present, and that
# there are no old remains in case of an update.
#
# @private
# @return [Boolean]
# @since 2.5.0
def self.integrity_check
version = "VerifiedIntegrity-#{self::VERSION}"
verified = ::Sketchup.read_default( PREF_KEY, version )
return true if verified
integrity = IntegrityCheck.new( self.integrity_check_file )
if integrity.ok?
::Sketchup.write_default( PREF_KEY, version, true )
true
else
message = <<MSG
TT_Lib² appear to be incorrectly installed. Please remove TT_Lib² and then
install it again. If this error message persist, contant the author for
assistance.
MSG
message.gsub!( /\s+/, ' ' ) # Collapse whitespace.
if integrity.missing_files?
missing_files = integrity.missing_files.join("\n")
message << "\n\nMissing files:\n" << missing_files
end
if integrity.unexpected_files?
unexpected_files = integrity.unexpected_files.join("\n")
message << "\n\nUnexpected files found:\n" << unexpected_files
end
# Defer execution to allow remaining plugins to load.
TT.defer { UI.messagebox( message, MB_OK ) }
false
end
end
# Checks for VirtualStore conflict.
#
# @private
# @return [Nil]
# @since 2.9.0
def self.virtualstore_check
return nil unless TT::System.is_windows?
require 'TT_Lib2/win32.rb'
plugins_folder = Sketchup.find_support_file( 'Plugins' ) # rubocop:disable SketchupSuggestions/SketchupFindSupportFile
virtual_folder = TT::System.get_virtual_file( plugins_folder )
return nil if virtual_folder.nil?
return nil if plugins_folder == virtual_folder
return nil unless File.exist?( virtual_folder )
if Dir.entries( virtual_folder ).to_a.size > 2
message = <<MSG
TT_Lib² detected that some of the files in SketchUp's plugin folder has ended up
in Window's Virtual Store. It happens because of insufficint permissions for the
plugin folder. Please move the files into the real Plugins folder.
MSG
message.gsub!( /\s+/, ' ' ) # Collapse whitespace.
# Defer execution to allow remaining plugins to load.
TT.defer {
result = UI.messagebox( message, MB_OKCANCEL )
if result == IDOK
UI.openURL( virtual_folder )
end
}
end
nil
end
# Returns the full path to the integrity file.
#
# @private
# @return [Nil]
# @since 2.5.0
def self.integrity_check_file
File.join( self.path, 'integrity_list.dat' )
end
# @return [String] The file path where the library is installed.
# @since 2.0.0
def self.path
PATH.dup
end
# Debug method to reload the library modules.
#
# @param [Boolean] return_files Determines if the method should return
# the number of files reloaded or an array of the files reloaded.
#
# @return [Integer, Array]
# @since 2.0.0
def self.reload( return_files=false )
original_verbose = $VERBOSE
$VERBOSE = nil # Mute warnings caused by constant redefining.
x = Dir.glob( File.join(self.path, '*.{rb,rbs}') ).each { |file|
load file
}
(return_files) ? x : x.length
ensure
$VERBOSE = original_verbose
end
end # module TT::Lib
### MENUS ### ----------------------------------------------------------------
# If TT's Menu is installed this method will return a custom Menu item
# instead of the requested root menu.
#
# @param [String] name The prefered root menu in Sketchup.
#
# @return [Sketchup::Menu]
# @since 2.0.0
def self.menu( name )
if global_variables.include?( :$tt_menu ) && $tt_menu
$tt_menu
else
UI.menu( name )
end
end
### NAMESPACE ### ------------------------------------------------------------
# Namespace for plugins to be wrapped into.
# Example:
#
# require 'TT_Lib2/core.rb'
# module TT::Plugins::FooBar
# ...
# end
#
# Reserved for Thomas Thomassen.
#
# @since 2.0.0
module Plugins; end
### MACROS ### ---------------------------------------------------------------
# @param [Integer] number
#
# @return [Boolean]
# @since 2.5.0
def self.even?(number)
number % 2 == 0
end
# @param [Integer] number
#
# @return [Boolean]
# @since 2.5.0
def self.odd?(number)
number % 2 > 0
end
# Format the given +time+ into a human readable string.
#
# @param [Numeric] time
#
# @return [String]
# @since 2.5.0
def self.format_time( time )
time = (time.finite?) ? time : 0.0
hours = (time / 3600).to_i
minutes = (time/60 - hours * 60).to_i
seconds = (time - (minutes * 60 + hours * 3600)).to_i
if hours > 0 && minutes > 0
"#{hours}h #{minutes}m #{seconds}s"
elsif minutes > 0
"#{minutes}m #{seconds}s"
else
"#{seconds}s"
end
end
# Returns the given square meters +area_meters+ in square inches.
#
# @param [Numeric] area_meters
#
# @return [Numeric]
# @since 2.5.0
def self.m2( area_meters )
ratio = 1.m ** 2
area_meters * ratio
end
# Returns the given square inches +area_inches+ in square meters.
#
# @param [Numeric] area_inches
#
# @return [Numeric]
# @since 2.5.0
def self.to_m2( area_inches )
ratio = 1.0 / self.m2(1)
area_inches * ratio
end
# @param [Object] object
#
# @return [String]
# @since 2.6.0
def self.object_id_hex( object )
"0x%x" % (object.object_id << 1)
end
# @param [Array] array
#
# @return [Hash]
# @since 2.6.0
def self.array_to_hash( array )
h = {}
for key, value in array
h[ key ] = value
end
h
end
### ENVIRONMENT ###-----------------------------------------------------------
# Set up load paths.
#path = File.join( TT::Lib.path, 'libraries' )
#$LOAD_PATH << path unless $LOAD_PATH.include?( path )
end # module TT
end
if SpeckleConnector::TT::System.platform_supported?
# Check the integrity of the library.
SpeckleConnector::TT::Lib.integrity_check
#TT::Lib.virtualstore_check() # Disabled until a better guide can be made.
# Require remaining modules.
Dir.glob( File.join(SpeckleConnector::TT::Lib.path, '*.{rb}') ).each { |filename|
file = __FILE__.dup
file.force_encoding( "UTF-8" ) if file.respond_to?(:force_encoding)
unless File.basename( filename ) == File.basename( file )
relative_file = File.join( SpeckleConnector::TT::Lib.path, File.basename( filename ) )
require( relative_file )
end
}
else
# Disable the extension if it's not supported by the platform. This is done to
# avoid potential crashes when loading the binaries.
#if Sketchup.respond_to?(:extensions)
# extension = Sketchup.extensions[TT::Lib::PLUGIN_NAME]
# extension.uncheck
#end
# Alert the user that the extension is not compatible with the running system.
message = "#{SpeckleConnector::TT::Lib::PLUGIN_NAME} is not supported for this platform."
UI.messagebox(message)
end # if TT::System.platform_supported?
-269
View File
@@ -1,269 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# @since 2.4.0
module SpeckleConnector
module TT::Cursor
# Path to the cursor resources.
PATH = File.join( TT::Lib.path, 'cursors')
# Definitions of cursor resources.
# :symbol_id => ['filename.png', x, y]
@cursors = {
:default => 0,
:invalid => 663,
:hand => 671,
:hand_invalid => 918,
:link => 670,
:erase => 645,
:pencil => 632,
:freehand => 655,
:arc_1 => 629,
:arc_2 => 631,
:arc_3 => 630,
:man => 612,
:position_camera => 653,
:position_camera_3d => 902,
:walk => 420,
:walk_3d => 904,
:look_around => 418,
:look_around_3d => 903,
:orbit => 419,
:orbit_3d => 900,
:pan => 1003,
:pan_2d => 901,
:zoom => 421,
:zoom_region => 422,
:zoom_3d => 905,
:zoom_2d => 907,
:zoom_2d_region => 906,
:offset => 646,
:offset_invalid => 679,
:dropper => 651,
:dropper_texture => 652,
:dropper_invalid => ['dropper_invalid.png', 2, 29],
:paint => 681, # 647
:paint_same => 650,
:paint_object => 649,
:paint_connected => 648,
:paint_invalid => 680,
:text => 678,
:follow_me => 640,
:follow_me_invalid => 678,
:pushpull => 639,
:pushpull_add => 755,
:pushpull_invalid => 707,
:tape => 638,
:tape_add => 731,
:select => 633,
:select_add => 634,
:select_remove => 636,
:select_toggle => 635,
:select_step_1 => 924,
:select_step_2 => 925,
:select_invalid => 926,
:vertex => ['Vertex.png', 12, 19],
:vertex_add => ['Vertex_Add.png', 12, 19],
:vertex_remove => ['Vertex_Remove.png', 12, 19],
:vertex_toggle => ['Vertex_Toggle.png', 12, 19],
:rectangle => 637,
:move => 641,
:move_copy => 642,
:move_fold => 672,
:move_invalid => 673,
:position => 658,
:position_invalid => 673,
:scale => 736,
:scale_invalid => 730,
:scale_n_s => 659,
:scale_n_ne => 666,
:scale_ne => 661,
:scale_ne_e => 667,
:scale_w_e => 660,
:scale_n_nw => 665,
:scale_nw => 662,
:scale_nw_w => 664,
:rotate => 643,
:rotate_copy => 644,
:rotate_invalid => 713
}
# Creates cursor ids for the requested cursor +id+. Cursors are created on demand and
# reused to save resources.
#
# Valid +id+ arguments
# * +:default+
# * +:invalid+ (2.7.0)
# * +:hand+ (2.7.0)
# * +:hand_invalid+ (2.7.0)
# * +:link+ (2.7.0)
# * +:erase+ (2.7.0)
# * +:pencil+ (2.7.0)
# * +:freehand+ (2.7.0)
# * +:arc_1+ (2.7.0)
# * +:arc_2+ (2.7.0)
# * +:arc_3+ (2.7.0)
# * +:man+ (2.7.0)
# * +:position_camera_3d+ (2.7.0)
# * +:orbit+ (2.7.0)
# * +:orbit_3d+ (2.7.0)
# * +:pan_2d+ (2.7.0)
# * +:pan+ (2.7.0)
# * +:walk+ (2.7.0)
# * +:walk_3d+ (2.7.0)
# * +:look_around+ (2.7.0)
# * +:look_around_903+ (2.7.0)
# * +:zoom+ (2.7.0)
# * +:zoom_region+ (2.7.0)
# * +:zoom_3d+ (2.7.0)
# * +:zoom_2d+ (2.7.0)
# * +:zoom_2d_region+ (2.7.0)
# * +:offset+
# * +:offset_invalid+
# * +:dropper+
# * +:dropper_texture+ (2.7.0)
# * +:dropper_invalid+
# * +:paint+ (2.7.0)
# * +:paint_same+ (2.7.0)
# * +:paint_object+ (2.7.0)
# * +:paint_connected+ (2.7.0)
# * +:paint_invalid+ (2.7.0)
# * +:text+ (2.7.0)
# * +:follow+ (2.7.0)
# * +:follow_me+ (2.7.0)
# * +:pushpull+ (2.7.0)
# * +:pushpull_add+ (2.7.0)
# * +:pushpull_invalid+ (2.7.0)
# * +:tape+ (2.7.0)
# * +:tape_add+ (2.7.0)
# * +:select+
# * +:select_add+
# * +:select_remove+
# * +:select_toggle+
# * +:select_step_1+ (2.7.0)
# * +:select_step_2+ (2.7.0)
# * +:select_invalid+ (2.7.0)
# * +:vertex+ (2.5.0)
# * +:vertex_add+ (2.5.0)
# * +:vertex_remove+ (2.5.0)
# * +:vertex_toggle+ (2.5.0)
# * +:rectangle+ (2.6.0)
# * +:move+ (2.6.0)
# * +:move_copy+ (2.7.0)
# * +:move_fold+ (2.7.0)
# * +:move_invalid+ (2.7.0)
# * +:position+ (2.7.0)
# * +:position_invalid+ (2.7.0)
# * +:rotate+ (2.6.0)
# * +:rotate_copy+ (2.6.0)
# * +:rotate_invalid+ (2.7.0)
# * +:scale+ (2.6.0)
# * +:scale_invalid+ (2.7.0)
# * +:scale_n_s+ (2.7.0)
# * +:scale_n_ne+ (2.7.0)
# * +:scale_ne+ (2.7.0)
# * +:scale_ne_e+ (2.7.0)
# * +:scale_w_e+ (2.7.0)
# * +:scale_n_nw+ (2.7.0)
# * +:scale_nw+ (2.7.0)
# * +:scale_nw_w+ (2.7.0)
#
# @param [Symbol] id
#
# @return [Integer, nil] +Integer+ of a cursor resource uon success, +nil+ upon failure.
# @since 2.4.0
def self.get_id(id)
return nil unless @cursors.key?(id)
# Load cursors on demand
if @cursors[id].is_a?(Array)
cursor_file, x, y = @cursors[id]
filename = File.join( TT::Cursor::PATH, cursor_file )
@cursors[id] = UI.create_cursor( filename, x, y )
end
return @cursors[id]
end
# Returns a cursor ID to a scaling direction cursor based on a 2D vector in
# screen space.
#
# @param [Geom::Vector3d] screen_vector
# @param [Sketchup::View] view
#
# @return [Integer, nil] +Integer+ of a cursor resource uon success, +nil+ upon failure.
# @since 2.7.0
def self.get_vector2d_cursor( screen_vector, view )
cursors = self.scale_handles
cursor_id = nil
nearest_angle = nil
for vector, cursor in cursors
a1 = vector.angle_between( screen_vector ).abs
a2 = vector.angle_between( screen_vector.reverse ).abs
angle = [ a1, a2 ].min
if nearest_angle.nil? || angle < nearest_angle
nearest_angle = angle
cursor_id = cursor
end
end
cursor_id
end
# Returns a cursor ID to a scaling direction cursor based on a 3D vector in
# model space.
#
# @param [Geom::Vector3d] vector
# @param [Sketchup::View] view
#
# @return [Integer, nil] +Integer+ of a cursor resource uon success, +nil+ upon failure.
# @since 2.7.0
def self.get_vector3d_cursor( vector, view )
pt1 = ORIGIN
pt2 = ORIGIN.offset( vector )
spt1 = view.screen_coords( pt1 )
spt2 = view.screen_coords( pt2 )
spt1.z = 0
spt2.z = 0
screen_vector = spt1.vector_to( spt2 )
self.get_vector2d_cursor( screen_vector, view )
end
# @return [Hash]
# @since 2.7.0
def self.scale_handles
@scale_handles ||= self.compute_scale_handles
@scale_handles
end
# @private
#
# @return [Hash]
# @since 2.7.0
def self.compute_scale_handles
cursor_ids = [
TT::Cursor.get_id( :scale_nw_w ),
TT::Cursor.get_id( :scale_nw ),
TT::Cursor.get_id( :scale_n_nw ),
TT::Cursor.get_id( :scale_n_s ),
TT::Cursor.get_id( :scale_n_ne ),
TT::Cursor.get_id( :scale_ne ),
TT::Cursor.get_id( :scale_ne_e ),
TT::Cursor.get_id( :scale_w_e )
].reverse
cursors = {}
angle = ( 180.0 / cursor_ids.size ).degrees
cursor_ids.each_with_index { |id, index|
tr = Geom::Transformation.rotation( ORIGIN, Z_AXIS, -angle * index )
vector = X_AXIS.transform( tr )
cursors[ vector ] = id
}
cursors
end
end # module TT::Cursor
end
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

@@ -1,73 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'system.rb'
require_relative 'win32.rb'
module SpeckleConnector
module TT
# Outputs debug data.
#
# Under Windows the data is sent to OutputDebugString and
# requires a utility like DebugView to see the data. Without it the call
# is muted.
#
# Under other platforms the data is sent to the console.
#
# @param [Mixed] data
#
# @return [Nil]
# @since 2.5.0
def self.debug(data)
if data.is_a?( String )
str = data
else
str = data.inspect
end
if TT::System.is_windows?
if TT::Win32.respond_to?(:debug_output)
TT::Win32.debug_output(str)
else
TT::Win32::OutputDebugString.call( "#{str}\n\0" )
end
else
puts data
end
nil
end
# @since 2.7.0
class Debug
# @param [String] object
#
# @return [Array]
# @since 2.7.0
def self.map_methods( object, ignore = [Kernel, Object] )
klass = ( object.class == Class || object.class == Module ) ? object : object.class
methods = klass.instance_methods
klasses = {}
ancestors = klass.ancestors
puts "#{klass} - (#{klass.class})"
puts "> Ancestors: #{ancestors.inspect}"
for k in ancestors
puts " > #{k} - ( #{k.class})"
if ignore.include?( k )
puts " (Ignored)"
else
puts " #{k.instance_methods(false).sort.join( "\n " )}"
end
end
nil
end
end # class Debug
end # module TT
end
@@ -1,47 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# Special Proc like object that limits the frequency it's executed. Designed to
# be used with +change+ events for TT::GUI::Textbox.
#
# @since 2.7.0
module SpeckleConnector
class TT::DeferredEvent
attr_accessor( :suppress_event_if_value_not_changed )
# @param [Float] delay Maximum frequency the event can be executed.
# @param [Proc] block
#
# @since 2.7.0
def initialize( delay = 0.2, &block )
@proc = block
@delay = delay
@last_value = nil
@timer = nil
@suppress_event_if_value_not_changed = true
end
# @param [Mixed] value Must be different from last call in order to trigger.
#
# @return [Boolean] True is the event was executed.
# @since 2.7.0
def call( value )
return false if @suppress_event_if_value_not_changed && value == @last_value
UI.stop_timer( @timer ) if @timer
@timer = UI.start_timer( @delay, false ) {
UI.stop_timer( @timer ) # Ensure it only runs once.
@proc.call( value )
}
true
end
end # class DeferredEvent
end
@@ -1,111 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# Collection of Face methods.
#
# @since 2.0.0
module SpeckleConnector
module TT::Definition
# Sets the origin of the +ComponentDefinition+ to a given 3d point.
#
# @param [Sketchup::ComponentDefinition] definition
# @param [Geom::Point3d] origin
#
# @return [Boolean]
# @since 2.0.0
def self.set_origin(definition, origin)
return false if definition.image?
# Set the origin - move the entities and counter-adjust the instances.
t = Geom::Transformation.new( origin )
definition.entities.transform_entities( t.inverse, definition.entities.to_a )
definition.instances.each { |i|
i.transformation = i.transformation * t
}
return true
end
# Sets the origin of the +ComponentDefinition+ to a given point on its bounds.
#
# +origin+ can be an integer of the following values:
#
# BB_LEFT_FRONT_BOTTOM = 0
# BB_RIGHT_FRONT_BOTTOM = 1
# BB_LEFT_BACK_BOTTOM = 2
# BB_RIGHT_BACK_BOTTOM = 3
# BB_LEFT_FRONT_TOP = 4
# BB_RIGHT_FRONT_TOP = 5
# BB_LEFT_BACK_TOP = 6
# BB_RIGHT_BACK_TOP = 7
# BB_BOTTOM_CENTER = 8
# BB_TOP_CENTER = 9
# BB_LEFT_CENTER = 10
# BB_RIGHT_CENTER = 11
# BB_FRONT_CENTER = 12
# BB_BACK_CENTER = 13
# BB_CENTER = 14
#
# All these constants are defined under +TT+.
#
# @param [Sketchup::ComponentDefinition] definition
# @param [Integer] origin
#
# @return [Boolean]
# @since 2.0.0
def self.set_origin_by_bounds(definition, origin)
return false if definition.image?
bb = definition.bounds
# Compute the origin
if origin.is_a?(Numeric)
case origin
when (0..7)
new_origin = bb.corner(origin)
when BB_CENTER
new_origin = bb.center
when BB_BOTTOM_CENTER
p1 = bb.corner(BB_LEFT_FRONT_BOTTOM)
p2 = bb.corner(BB_RIGHT_BACK_BOTTOM)
new_origin = Geom::Point3d.linear_combination(0.5, p1, 0.5, p2)
when BB_TOP_CENTER
p1 = bb.corner(BB_LEFT_FRONT_TOP)
p2 = bb.corner(BB_RIGHT_BACK_TOP)
new_origin = Geom::Point3d.linear_combination(0.5, p1, 0.5, p2)
when BB_LEFT_CENTER
p1 = bb.corner(BB_LEFT_FRONT_BOTTOM)
p2 = bb.corner(BB_LEFT_BACK_TOP)
new_origin = Geom::Point3d.linear_combination(0.5, p1, 0.5, p2)
when BB_RIGHT_CENTER
p1 = bb.corner(BB_RIGHT_FRONT_BOTTOM)
p2 = bb.corner(BB_RIGHT_BACK_TOP)
new_origin = Geom::Point3d.linear_combination(0.5, p1, 0.5, p2)
when BB_FRONT_CENTER
p1 = bb.corner(BB_LEFT_FRONT_BOTTOM)
p2 = bb.corner(BB_RIGHT_FRONT_TOP)
new_origin = Geom::Point3d.linear_combination(0.5, p1, 0.5, p2)
when BB_BACK_CENTER
p1 = bb.corner(BB_LEFT_BACK_BOTTOM)
p2 = bb.corner(BB_RIGHT_BACK_TOP)
new_origin = Geom::Point3d.linear_combination(0.5, p1, 0.5, p2)
else
raise ArgumentError
end
elsif origin.is_a?(Geom::Point3d) || (origin.is_a?(Array) && origin.size = 3)
new_origin = origin
else
raise ArgumentError
end
self.set_origin(definition, bb.center)
end
end # module TT::Definition
end
@@ -1,279 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# A 2d matrix Array that can be iterated in rows, columns or like a regular +Array+.
#
# *Note*: it does not behave exactly like a regular +Array+. Use the +.to_a+ method
# to get a regular array.
#
# @since 2.5.0
module SpeckleConnector
class TT::Dimension
include Enumerable
# @since 2.5.0
attr(:width)
# @since 2.5.0
attr(:height)
# @overload new(array, width, height, obj = nil)
# @param [Array] array creates an Dimension from an array
# @param [Integer] width
# @param [Integer] height
# @param [Object] obj default value
# @overload new(width, height, obj = nil)
# @param [Integer] width
# @param [Integer] height
# @param [Object] obj default value
#
# @since 2.5.0
def initialize(*args)
# Check if the first aruments is an +Array+ - use that to populate the dataset.
arr = args.first.is_a?(Array) ? args.shift : nil
# Validate arguments
if args.length < 2
raise ArgumentError, 'Missing arguments. Requires width and height.'
end
# Extract remaining arguments
@width, @height, obj = args
# Create 2-dimensional array
@d = Array.new(@width * @height, obj)
# Populate with the given data, if any.
unless arr.nil?
unless arr.length == @width * @height
raise ArgumentError, 'Array length does not match width and height.'
end
arr.each_index { |i| @d[i] = arr[i] }
end
end
# @example
# dim = Dimension.new([6,7,8,9], 2, 2)
# puts dim[2]
# -> 8
# puts dim[0,1]
# -> 8
#
# @overload [](index)
# @param [Integer] index an Integer between 0 and self.length-1
# @overload [](row, column)
# @param [Integer] row an Integer between 0 and self.width-1
# @param [Integer] column an Integer between 0 and self.height-1
#
# @return [Object] value at the given index
#
# @since 2.5.0
def [](*args)
case args.length
when 1
return @d[ args.first ]
when 2
column, row = args
return @d[ (row * @width) + column ]
end
end
# @overload []=(index)
# @param [Integer] index an Integer between 0 and self.length-1
# @overload []=(row, column)
# @param [Integer] row an Integer between 0 and self.width-1
# @param [Integer] column an Integer between 0 and self.height-1
#
# @since 2.5.0
def []=(*args)
value = args.pop
case args.length
when 1
@d[args.first] = value
when 2
column, row = args
@d[ (row * @width) + column ] = value
end
end
# @param [Integer] index an Integer between +0+ and +self.height-1+
#
# @return [Array] row at index
#
# @since 2.5.0
def row(index)
return @d[index * @width, @width]
end
# Sets the row at the given index.
#
# @param [Integer] index an Integer between +0+ and +self.height-1+
# @param [Array] new_row an +Array+ of the size +self.width+
#
# @return [Array] the new row
#
# @since 2.5.0
def set_row(index, new_row)
return @d[index * @width, @width] = new_row
end
# @return [Array<Array>] an array containing all the rows
#
# @since 2.5.0
def rows
arr = []
0.step(@d.length - 1, @width) { |i|
arr << @d[i, @width]
}
return arr
end
# @param [Integer] index an Integer between +0+ and +self.width-1+
#
# @return [Array] column at index
#
# @since 2.5.0
def column(index)
arr = []
0.upto(@height - 1) { |j|
arr << @d[index + (j * @width)]
}
return arr
end
# Sets the column at the given index.
#
# @param [Integer] index an Integer between +0+ and +self.width-1+
# @param [Array] new_column an +Array+ of the size +self.height+
#
# @return [Array] the new column
#
# @since 2.5.0
def set_column(index, new_column)
0.upto(@height - 1) { |j|
@d[index + (j * @width)] = new_column[j]
}
end
# @return [Array<Array>] an +Array+ containing all the columns
#
# @since 2.5.0
def columns
arr = []
0.upto(@width - 1) { |i|
column = []
0.upto(@height - 1) { |j|
column << @d[i + (j * @width)]
}
arr << column
}
return arr
end
# @yield [array, index] an +Array+ representing either a row or a column
# depening on +column+
# @yieldparam [Array] array
# @yieldparam [optional, Integer] index
#
# @param [Boolean] column iterates columns when +true+ or rows
# when +false+
#
# @since 2.5.0
def each(column = false, &block)
if column
0.upto(@width - 1) { |i|
0.upto(@height - 1) { |j|
index = i + (j * @width)
if block.arity > 1
yield( @d[index], index )
else
yield( @d[index] )
end
}
}
else
@d.each { |i| yield(i) }
end
end
# @yield [row, index]
# @yieldparam [Array] row
# @yieldparam [optional, Integer] index
#
# @since 2.5.0
def each_row(&block)
0.step(@d.length - 1, @width) { |i|
if block.arity > 1
yield( @d[i, @width], (i / @width) )
else
yield( @d[i, @width] )
end
}
end
# @yield [column, index]
# @yieldparam [Array] column
# @yieldparam [optional, Integer] index
#
# @since 2.5.0
def each_column(&block)
0.upto(@width - 1) { |i|
column = []
0.upto(@height - 1) { |j|
column << @d[i + (j * @width)]
}
if block.arity > 1
yield(column, i)
else
yield(column)
end
}
end
# @return [Integer] the size of the +Dimension+
#
# @since 2.5.0
def length
return @width * @height
end
alias :size :length
# @return [Array] converts the Dimension to an array.
#
# @since 2.5.0
def to_a(orient_by_columns = false)
arr = []
self.each(orient_by_columns) { |value|
arr << value
}
return arr
end
# @return [String]
# @since 2.5.0
def inspect
return "#<#{self.class}(#{@width}x#{@height})>"
end
# @return [TT::Dimension]
# @since 2.5.0
def map
new_dim = TT::Dimension.new( self.width, self.height )
self.each_with_index { |value, index|
new_dim[index] = yield( value )
}
new_dim
end
# @return [TT::Dimension]
# @since 2.5.0
def map!
@d.map! { |value| yield(value) }
self
end
end # class TT::Dimension
end
@@ -1,120 +0,0 @@
#-------------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-------------------------------------------------------------------------------
require_relative 'core.rb'
# Caches drawing instructions so complex calculations for generating the
# GL data can be reused.
#
# Redirect all Skethcup::View commands to a DrawCache object and call
# #render in a Tool's #draw event.
#
# @example
# class Example
# def initialize( model )
# @draw_cache = TT::DrawCache.new( model.active_view )
# end
# def deactivate( view )
# @draw_cache.clear
# end
# def resume( view )
# view.invalidate
# end
# def draw( view )
# @draw_cache.render
# end
# def onLButtonUp( flags, x, y, view )
# point = Geom::Point3d.new( x, y, 0 )
# view.draw_points( point, 10, 1, 'red' )
# view.invalidate
# end
# end
#
# @since 2.8.0
module SpeckleConnector
class TT::DrawCache
# @param [Sketchup::View] view
#
# @since 2.8.0
def initialize( view )
@view = view
@commands = []
end
# Clears the cache. All drawing instructions are removed.
#
# @return [Nil]
# @since 2.8.0
def clear
@commands.clear
nil
end
# Draws the cached drawing instructions.
#
# @return [Sketchup::View]
# @since 2.8.0
def render
view = @view
for command in @commands
view.send( *command )
end
view
end
# Cache drawing commands and data. These methods received the finsihed
# processed drawing data that will be executed when #render is called.
[
:draw,
:draw2d,
:draw_line,
:draw_lines,
:draw_points,
:draw_polyline,
:draw_text,
:drawing_color=,
:line_stipple=,
:line_width=,
:set_color_from_line
].each { |symbol|
define_method( symbol ) { |*args|
@commands << args.unshift( this_method )
@commands.size
}
}
# Pass through methods to Sketchup::View so that the drawing cache object
# can easily replace Sketchup::View objects in existing codes.
#
# @since 2.8.0
def method_missing( *args )
view = @view
method = args.first
if view.respond_to?( method )
view.send(*args)
else
raise NoMethodError, "undefined method `#{method}' for #{self.class.name}"
end
end
# @return [String]
# @since 2.8.0
def inspect
hex_id = TT.object_id_hex( self )
"#<#{self.class.name}:#{hex_id} Commands:#{@commands.size}>"
end
private
# http://www.ruby-forum.com/topic/75258#895569
def this_method
( caller[0] =~ /`([^']*)'/ and $1 ).intern
end
end # class TT::DrawCache
end
-516
View File
@@ -1,516 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'point3d.rb'
require_relative 'progressbar.rb'
# Collection of Edge methods.
#
# @since 2.5.0
module SpeckleConnector
module TT::Edge
# @param [Array<Geom::Point3d, Geom::Vector3d>, Array<Geom::Point3d, Geom::Point3d>] line
# @param [Sketchup::Edge] edge
#
# @return [Geom::Point3d|Nil]
# @since 2.5.0
def self.intersect_line_edge(line, edge)
point = Geom.intersect_line_line(line, edge.line)
return nil if point.nil?
return point if self.point_on_edge?(point, edge)
return nil
end
# @param [Geom::Point3d] point
# @param [Sketchup::Edge] edge
#
# @return [Boolean]
# @since 2.5.0
def self.point_on_edge?(point, edge)
a = edge.start.position
b = edge.end.position
TT::Point3d.between?(a, b, point, true)
end
end # module TT::Edge
# Collection of methods to manipulate sets of edges.
#
# @since 2.5.0
module TT::Edges
# @param [Sketchup::Edge] edge1
# @param [Sketchup::Edge] edge2
#
# @return [Sketchup::Vertex|Nil]
# @since 2.5.0
def self.common_vertex(edge1, edge2)
for v1 in edge1.vertices
for v2 in edge2.vertices
return v1 if v1 == v2
end
end
nil
end
# Find continous sets of edges not part of faces. The result is an array of
# more arrays. Each sub-array contains a set of sorted edges.
#
# Sub-arrays may contains arrays of only one edge.
#
# @param [Array<Sketchup::Entity>] entities
#
# @return [Array<Array<Sketchup::Edge>>] An +Array+ of Arrays.
# @since 2.5.0
def self.find_curves( entities, progress_in_ui=false )
cache = {} # Use Hash for quick lookups
entities.each { |e|
next unless e.is_a?( Sketchup::Edge ) && e.faces.empty?
cache[e] = e
}
if progress_in_ui && progress_in_ui.is_a?( TT::Progressbar )
progress = progress_in_ui
else
progress = TT::Progressbar.new( cache, 'Finding curves' )
end
curves = []
until cache.empty?
curve = {} # Use Hash for quick lookups
stack = [ cache.keys.first ]
until stack.empty?
# Fetch the next edges in the stack and add to the curve
edge = stack.shift
curve[edge] = edge
cache.delete(edge)
progress.next if progress_in_ui
# Find next edges
for v in edge.vertices
vert_edges = v.edges
next if vert_edges.size != 2
for e in vert_edges
stack << e unless curve.key?(e)
end
end
end # until stack.empty?
curves << self.sort( curve )
end # until cache.empty?
curves
end
# Finds all sets of edges forming a curve not connected to any other geometry.
# Returns an array of sorted curves. (Curve = array of edges)
#
# @param [Array<Sketchup::Entity>] entities
#
# @return [Array<Array<Sketchup::Edge>>] An +Array+ of Arrays.
# @since 2.5.0
def self.find_isolated_curves(entities)
source = entities.to_a
curves = []
until source.empty?
entity = source.shift
next unless entity.is_a?( Sketchup::Edge )
connected = entity.all_connected
source -= connected
next unless connected.all? { |e| e.is_a?(Sketchup::Edge) }
sorted_edges = self.sort( connected )
curves << sorted_edges unless sorted_edges.nil?
end
return curves
end
# Attempts to merge colinear edges into one edge. Makes use of SketchUp's own
# healing feature.
# Based on repair_broken_lines.rb by Carlo Roosen 2004
#
# If +progress_in_ui+ is true then a +TT::Progressbar+ object is used to
# give UI feedback to the user about the process.
#
# If +progress_in_ui+ is a +TT::Progressbar+ object then that is used instead
# and +.next+ is called for each entity.
#
# @param [Array<Sketchup::Entity>] entities
# @param [Boolean|TT::Progressbar] progress_in_ui
#
# @return [Integer] Number of splits repaired.
# @since 2.5.0
def self.repair_splits( entities, progress_in_ui=false )
temp_edges = []
return 0 if entities.length == 0
parent = entities[0].parent.entities
if progress_in_ui && progress_in_ui.is_a?( TT::Progressbar )
progress = progress_in_ui
else
progress = TT::Progressbar.new( entities, 'Repairing split edges' )
end
for e in entities.to_a
next unless e.is_a?(Sketchup::Edge)
progress.next if progress_in_ui
for vertex in e.vertices
next unless vertex.edges.length == 2
# (?) Like coplanar faces - can one compare vectors like this? Or do one
# have to check if all vertices lie on the same line?
v1 = vertex.edges[0].line[1]
v2 = vertex.edges[1].line[1]
next unless v1.parallel?(v2)
# To repair a broken edge a temporary edge is placed at their shared vertex.
# This temporary edge is then later erased which causes the two edges to
# merge.
pt1 = vertex.position
pt2 = pt1.clone
pt2.x += rand(1000) / 100.0
pt2.y += rand(1000) / 100.0
pt2.z += rand(1000) / 100.0
temp_edge = parent.add_line( pt1, pt2 )
temp_edges << temp_edge unless temp_edge.nil?
end
end
parent.erase_entities(temp_edges) unless temp_edges.empty?
temp_edges.size
end
# Sorts the given set of edges from start to end. If the edges form a loop
# an arbitrary start is picked.
#
# @todo Comment source
#
# @param [Array<Sketchup::Edge>] edges
#
# @return [Array<Sketchup::Edge>] Sorted set of edges.
# @since 2.5.0
def self.sort( edges )
if edges.is_a?( Hash )
self.sort_from_hash( edges )
elsif edges.is_a?( Enumerable )
lookup = {}
for edge in edges
lookup[edge] = edge
end
self.sort_from_hash( lookup )
else
raise ArgumentError, '"edges" argument must be a collection of edges.'
end
end
# Sorts the given set of edges from start to end. If the edges form a loop
# an arbitrary start is picked.
#
# @param [Hash] edges Sketchup::Edge as keys
#
# @return [Array<Sketchup::Edge>] Sorted set of edges.
# @since 2.5.0
def self.sort_from_hash( edges )
# Get starting edge - then trace the connected edges from either end.
start_edge = edges.keys.first
# Find the next left and right edge
vertices = start_edge.vertices
left = []
for e in vertices.first.edges
left << e if e != start_edge && edges[e]
end
right = []
for e in vertices.last.edges
right << e if e != start_edge && edges[e]
end
return nil if left.size > 1 || right.size > 1 # Check for forks
left = left.first
right = right.first
# Sort edges from start to end
sorted = [start_edge]
# Right
edge = right
until edge.nil?
sorted << edge
connected = []
for v in edge.vertices
for e in v.edges
connected << e if edges[e] && !sorted.include?(e)
end
end
return nil if connected.size > 1 # Check for forks
edge = connected.first
end
# Left
unless sorted.include?( left ) # Fix: 2.6.0
edge = left
until edge.nil?
sorted.unshift( edge )
connected = []
for v in edge.vertices
for e in v.edges
connected << e if edges[e] && !sorted.include?(e)
end
end
return nil if connected.size > 1 # Check for forks
edge = connected.first
end
end
sorted
end
# @note The first vertex will also appear last if the curve forms a loop.
#
# Takes a sorted set of edges and returns a sorted set of vertices. Use
# +TT::Edges.sort+ to sort a set of edges.
#
# @param [Array<Sketchup::Edge>] curve Set of sorted edge.
#
# @return [Array<Sketchup::Vertex>] Sorted set of vertices.
# @since 2.5.0
def self.sort_vertices(curve)
return curve[0].vertices if curve.size <= 1
vertices = []
# Find the first vertex.
common = self.common_vertex( curve[0], curve[1] ) # (?) Errorcheck?
vertices << curve[0].other_vertex( common )
# Now the rest can be added.
curve.each { |edge|
vertices << edge.other_vertex(vertices.last) # (?) Errorcheck?
}
return vertices
end
end # module TT::Edges
# Collection of methods to find and repair small gaps and stray edges.
#
# @since 2.5.0
module TT::Edges::Gaps
# Pair with the results of +TT::Edges::Gaps.find+.
#
# @param [Sketchup::Entities] entities +Entities+ collection where the vertex belong to.
# @param [Sketchup::Vertex] vertex
# @param [Hash] result The returned hash from +TT::Edges::Gaps.find+.
# @param [Length] epsilon The max distance for which the gap can be closed.
#
# @return [Boolean] Returns +true+ if the gap was closed.
# @since 2.5.0
def self.close( entities, vertex, result, epsilon )
# 1. Closest projected open end
data = result[:vertex_projected]
if data[:dist] && data[:dist][0] + data[:dist][1] < epsilon
pt1 = data[:point]
pt2 = data[:point2]
entities.add_line( vertex.position, pt1 )
entities.add_line( pt1, pt2 )
return true
end
# 2. Closest edge
data = result[:edge]
if data[:dist] && data[:dist] < epsilon
entities.add_line( vertex.position, data[:point] )
return true
end
# 3. Closest open vertex
data = result[:vertex]
if data[:dist] && data[:dist] < epsilon
entities.add_line( vertex.position, data[:point] )
return true
end
false
end
# Tries to connect all open ended edges to other edges if the distance is less
# than +epsilon+. If an open-ended edge can't be connected it'll erase it if
# its length is less than +epsilon+.
#
# If +progress_in_ui+ is true then a +TT::Progressbar+ object is used to
# give UI feedback to the user about the process.
#
# If +progress_in_ui+ is a +TT::Progressbar+ object then that is used instead
# and +.next+ is called for each entity.
#
# @param [Sketchup::Entities|Array<Sketchup::Entity>] entities Entities to process.
# @param [Length] epsilon The max distance for which the gap can be closed.
# @param [Boolean] erase_small_edges If +true+ it will erase all stray edges shorter than +epsilon+.
# @param [Boolean|TT::Progressbar] progress_in_ui
#
# @return [Integer] Returns the number of fixes done.
# @since 2.5.0
def self.close_all( entities, epsilon, erase_small_edges=false, progress_in_ui=false )
fixes = 0
return fixes if entities.length == 0
context = entities[0].parent.entities
edges = entities.select { |e| e.is_a?( Sketchup::Edge ) }
small_edges = []
end_vertices = self.find_end_vertices( edges )
if progress_in_ui && progress_in_ui.is_a?( TT::Progressbar )
progress = progress_in_ui
else
progress = TT::Progressbar.new( end_vertices, 'Closing open ends' )
end
for v in end_vertices
progress.next if progress_in_ui
result = self.find(v, end_vertices, edges)
closed = self.close( context, v, result, epsilon )
fixes += 1 if closed
if !closed && erase_small_edges
edge = v.edges.first
if edge.length < epsilon
small_edges << edge
fixes += 1
end
end
end # for
Sketchup.status_text = 'Erasing small edges...' if progress_in_ui
if erase_small_edges && !small_edges.empty?
context.erase_entities( small_edges )
end
fixes
end
# Finds possible connections for +vertex+ within the set of +open_ends+ and
# +edges+.
#
# The returned hash contains three keys:
# * +:vertex_projected+ - the closest connection by extending two edges towards each other.
# * +:vertex+ - the closest open-end vertex
# * +:edge+ - the closest edge
#
# Each value contains another hash with the following keys:
# * +:dist+ the distance from +vertex+ to +:point+
# * +:point+ the point which +vertex+ can be extended to.
# * +:point2+ Only availible to +:vertex_projected+. The origin of the end vertex
# of the other edge. In this case +:point+ is the intersecting point where
# the two edges meet.
#
# @param [Sketchup::Vertex] vertex The open-end vertex to find connections for.
# @param [Array<Sketchup::Vertex>] open_ends Set of availible open-end vertices.
# @param [Array<Sketchup::Edge>] edges Set of edges to connect to.
#
# @return [Hash] Returns a hash with possible connection options.
# @since 2.5.0
def self.find( vertex, open_ends, edges )
origin = vertex.position
edge = vertex.edges.first
other = edge.other_vertex( vertex ).position
vector = origin.vector_to( other )
line = edge.line
ends = open_ends - [ vertex, edge.other_vertex( vertex ) ]
end_edges = ends.map { |v| v.edges.first }
distances = {}
ends.each { |v|
distances[v] = origin.distance( v.position )
}
# 1. Closest projected open end
projected_vertices = {}
projected_vertices_pt = {}
for v in ends
e = v.edges.first
pt = Geom::intersect_line_line( line, e.line )
next if pt.nil?
direction = origin.vector_to(pt)
next unless direction.valid?
next if direction.samedirection?(vector)
projected_vertices[v] = [
origin.distance( pt ),
pt.distance( v.position )
]
projected_vertices_pt[v] = pt
end
closest_projected_vertex = projected_vertices.keys.min { |a,b|
va = projected_vertices[a]
vb = projected_vertices[b]
if va[0] == vb[0]
va[1] <=> vb[1]
else
va[0] <=> vb[0]
end
}
pt = projected_vertices_pt[closest_projected_vertex]
tmp = projected_vertices[closest_projected_vertex]
dist = (tmp.nil?) ? nil : tmp[0]
result = {}
result[:vertex_projected] = { :dist => tmp, :point => pt, :point2 => closest_projected_vertex }
# 3. Closest open vertex
closest_open_vertex = ends.min { |a,b|
distances[a] <=> distances[b]
}
pt = (closest_open_vertex) ? closest_open_vertex.position : nil
dist = distances[closest_open_vertex]
result[:vertex] = { :dist => dist, :point => pt }
# 2. Closest edge
edge_distances = {}
edge_distances_pt = {}
for e in edges
next if e == edge
pt = Geom::intersect_line_line( line, e.line )
next if pt.nil?
#next if pt == other
pt1, pt2 = e.vertices.map { |v| v.position }
next unless TT::Point3d.between?( pt1, pt2, pt, true )
v = origin.vector_to(pt)
next unless v.valid?
next if v.samedirection?(vector)
edge_distances[e] = origin.distance( pt )
edge_distances_pt[e] = pt
end
closest_edge = edge_distances.keys.min { |a,b|
edge_distances[a] <=> edge_distances[b]
}
pt = edge_distances_pt[closest_edge]
dist = edge_distances[closest_edge]
result[:edge] = { :dist => dist, :point => pt }
result
end # def
# Finds all open-ended edges in +entities+ and returns an array of vertices
# for each open end.
#
# @param [Sketchup::Entities|Array<Sketchup::Entity>] entities
#
# @return [Array<Sketchup::Vertex>]
# @since 2.5.0
def self.find_end_vertices( entities )
vertices = []
for e in entities
next unless e.is_a?( Sketchup::Edge )
open_ends = e.vertices.select { |v| v.edges.length == 1 }
vertices.concat( open_ends )
end
vertices
end
end # module TT::Edges::Gaps
end
@@ -1,164 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'instance.rb'
# Collection of Entities methods.
#
# @since 2.0.0
module SpeckleConnector
module TT::Entities
# Returns a boundingbox for all the given entities.
#
# @param [Enumerable] entities
#
# @return [Geom::BoundingBox]
# @since 2.1.0
def self.bounds(entities)
bb = Geom::BoundingBox.new
for e in entities
next unless e.respond_to?(:bounds)
bb.add( e.bounds )
end
return bb
end
# Counts all unique +Sketchup::Entities+ collections in the given context,
# including sub-entities.
#
# @param [Enumerable] context
# @param [Hash] options
#
# @return [Integer]
# @since 2.5.0
def self.count_unique_entities( context, options={} )
c = 0
entities = nil # Init variables for speed
self.each_entities( context, options={} ) { |entities| c = c.next }
c
end
# Counts all unique entities in the given collection, including sub-entities.
#
# @param [Enumerable] context
# @param [Hash] options
#
# @return [Integer]
# @since 2.5.0
def self.count_unique_entity( context, options={} )
c = context.length
entities = nil # Init variables for speed
self.each_entities( context, options={} ) { |entities|
c += entities.length
}
c
end
# Yields each unique Entities collection recursivly.
#
# TT::Entities.each_entities { |entities|
# processEntities( entities )
# }
#
# If a number is returned to the processing block it will be used to add up a
# total when +each_entities+ returns.
#
# TT::Entities.each_entities { |entities|
# c = 0
# for e in entities
# c += 1 if e.is_a?( SketchUp::Edge )
# end
# c
# }
#
# This example will return the total number of edges processed. Use to keep
# statistic for the iteration.
#
# @param [Enumerable] context
# @param [Hash] processed_definitions Hash index of processed entities.
# @param [Hash] options
#
# @yield [entities]
# @yieldparam [Enumerable|Sketchup::Entities] entities
#
# @return [Integer] Returns
# @since 2.5.0
def self.each_entities( context, processed_definitions={}, options={}, &block )
skip_locked = options[:locked] && options[:locked].is_a?( Hash )
c = 0
result = yield( context )
c += result if result.is_a?( Numeric )
# Process Groups and ComponentInstances
for e in context.to_a
next unless e.valid? && TT::Instance.is?( e )
d = TT::Instance.definition( e )
if processed_definitions[d].nil?
processed_definitions[d] = true
next if skip_locked && options[:locked].key?(d)
result = self.each_entities( d.entities, processed_definitions, options, &block )
c += result if result.is_a?( Numeric )
end
end
c
end
# Yields each entity recursivly.
def self.each_entity( entities, processed_definitions={}, options={}, &block )
skip_locked = options[:locked] && options[:locked].is_a?( Hash )
c = 0
for e in entities.to_a
next if skip_locked && e.respond_to?( :locked? ) && e.locked?
c = c.next if yield( e )
# Process Groups and ComponentInstances
next unless e.valid? && TT::Instance.is?( e )
d = TT::Instance.definition( e )
if processed_definitions[d].nil?
processed_definitions[d] = true
next if skip_locked && options[:locked].key?(d)
c += self.each_entity( d.entities, processed_definitions, options, &block )
end
end # for
c
end
# Collects the 3d positions of the vertices in the given entities collection.
# Processes child Groups and Components recursivly.
#
# @param [Enumerable] entities
# @param [Geom::Transformation] parent_transformation
#
# @return [Array<Geom::Point3d>]
# @since 2.0.0
def self.positions(entities, parent_transformation = Geom::Transformation.new)
pts = []
vertices = []
for e in entities
if e.respond_to?(:vertices)
vertices << e.vertices
elsif TT::Instance.is?( e )
d = TT::Instance.definition(e)
t = parent_transformation * e.transformation
sub_pts = self.positions(d.entities, t)
pts.concat( sub_pts )
end
end # for
vertices.flatten!
vertices.uniq!
pts.concat( vertices.map { |v| v.position.transform( parent_transformation ) } )
pts
end
end # module TT::Entities
end
@@ -1,2 +0,0 @@
ID=c03a2b93-3365-4ef1-95f4-f35158757622
VERSION_ID=0c8388c8-299b-44aa-9584-877446f65403
-130
View File
@@ -1,130 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'geom3d.rb'
require_relative 'uvq.rb'
# Collection of Face methods.
#
# @since 2.0.0
module SpeckleConnector
module TT::Face
# Returns all vertices in the face's outer loop that isn't connecting colinear
# edges.
#
# @param [Sketchup::Face] face
#
# @return [Array<Sketchup::Vertex>]
# @since 2.5.0
def self.corners(face)
corners = []
# We only check the outer loop, ignoring interior lines.
face.outer_loop.edgeuses.each { |eu|
# Ignore vertices that's between co-linear edges.
v1 = eu.edge.line[1]
v2 = eu.next.edge.line[1]
if v1.valid? && v2.valid? && !v1.parallel?( v2 )
#unless v1.parallel?( v2 )
#unless eu.edge.line[1].parallel?( eu.next.edge.line[1] )
# Find which vertex is shared between the two edges.
# (?) TT::Edges.common_vertex( eu.edge, eu.next.edge )
if eu.edge.start.used_by?(eu.next.edge)
corners << eu.edge.start
else
corners << eu.edge.end
end
end
}
return corners
end
# @param [Sketchup::Face] face
#
# @return [Boolean] Returns true if the face has four corners.
# @since 2.5.0
def self.is_quad?( face )
face.is_a?( Sketchup::Face ) &&
self.corners( face ).length == 4
end
# @param [Sketchup::Face] face
# @param [Sketchup::TextureWriter] texture_writer
# @param [Boolean] front_to_back
#
# @return [Boolean] Returns true if the face has four corners.
# @since 2.5.0
def self.mirror_material(face, texture_writer, front_to_back=true)
# Get the material to mirror
material = (front_to_back) ? face.material : face.back_material
# Plain colour and Default is simply mirrored
if material.nil? || material.materialType < 1
if front_to_back
face.back_material = material
else
face.material = material
end
return
end
# Get four Point3d samples from the face's plane.
# We take one point from the face and offset that in X and Y
# on the face's plane. This ensures we get enough data.
# Previously I sampled data from vertices, which lead to problems
# for triangles with distorted textures. Distorted textures require
# four UV points.
samples = []
samples << face.vertices[0].position # 0,0 | Origin
samples << samples[0].offset(face.normal.axes.x) # 1,0 | Offset Origin in X
samples << samples[0].offset(face.normal.axes.y) # 0,1 | Offset Origin in Y
samples << samples[1].offset(face.normal.axes.y) # 1,1 | Offset X in Y
# Arrays containing 3D and UV points.
xyz = []
uv = []
uvh = face.get_UVHelper(true, true, texture_writer)
samples.each { |position|
# XYZ 3D coordinates
xyz << position
# UV 2D coordinates
if front_to_back
uvq = uvh.get_front_UVQ(position)
else
uvq = uvh.get_back_UVQ(position)
end
uv << TT::UVQ.normalize(uvq)
}
# Position texture.
pts = []
(0..3).each { |i|
pts << xyz[i]
pts << uv[i]
}
face.position_material(material, pts, !front_to_back)
nil
end
end # module TT::Face
# Collection of methods for sets of faces.
#
# @since 2.5.0
module TT::Faces
# @param [Sketchup::Face] face1
# @param [Sketchup::Face] face2
#
# @return [Boolean]
# @since 2.5.0
def self.coplanar?(face1, face2)
TT::Geom3d.planar_points?( face1.vertices + face2.vertices )
end
end # module TT::Faces
end
-211
View File
@@ -1,211 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'point3d.rb'
# @since 2.0.0
module SpeckleConnector
module TT::Geom3d
# Returns +plane+ in the format +[ point3d, vector3d ]+.
#
# @param [Array<Geom::Point3d, Geom::Vector3d>, Array<Number, Number, Number, Number>] plane
#
# @return [Array<Geom::Point3d, Geom::Vector3d>]
# @since 2.0.0
def self.normalize_plane(plane)
return plane if plane.length == 2
a, b, c, d = plane
v = Geom::Vector3d.new(a,b,c)
p = ORIGIN.offset(v.reverse, d)
return [p, v]
end
# Check if all points in the array are on the same plane.
#
# @todo Ensure that the points are not co-linear. (Edge case)
#
# @param [Array<Geom::Point3d|Sketchup::Vertex>] points
#
# @since 2.0.0
def self.planar_points?(points)
points = TT::Point3d.extend_all( points )
points.uniq!
return false if points.size < 3
plane = Geom.fit_plane_to_points( points )
points.all? { |pt| pt.on_plane?( plane ) }
end
# Creates a set of +Geom::Point3d+ objects for an arc.
#
# @param [Geom::Point3d] center
# @param [Geom::Vector3d] xaxis
# @param [Geom::Vector3d] normal
# @param [Number] radius
# @param [Float] start_angle in radians
# @param [Float] end_angle in radians
# @param [Integer] num_segments
#
# @return [Array<Geom::Point3d>]
# @since 2.0.0
def self.arc(center, xaxis, normal, radius, start_angle, end_angle, num_segments = 12)
# Generate the first point.
t = Geom::Transformation.rotation(center, normal, start_angle )
points = []
points << center.offset(xaxis, radius).transform(t)
# Prepare a transformation we can repeat on the last entry in point to complete the arc.
t = Geom::Transformation.rotation(center, normal, (end_angle - start_angle) / num_segments )
1.upto(num_segments) { |i|
points << points.last.transform(t)
}
return points
end
# @see http://en.wikipedia.org/wiki/Circle
# @see http://en.wikipedia.org/wiki/Unit_Circle
#
# @param [Geom::Point3d] center
# @param [Geom::Vector3d] xaxis
# @param [Numeric] radius
# @param [Numeric] start_angle
# @param [Numeric] end_angle
# @param [Integer] segments
#
# @return [Array<Geom::Point3d>]
# @since 2.7.0
def self.arc2d( center, xaxis, radius, start_angle, end_angle, segments = 24 )
full_angle = end_angle - start_angle
segment_angle = full_angle / segments
t = Geom::Transformation.axes( center, xaxis, xaxis * Z_AXIS, Z_AXIS )
arc = []
(0..segments).each { |i|
angle = start_angle + (segment_angle * i)
x = radius * Math.cos(angle)
y = radius * Math.sin(angle)
arc << Geom::Point3d.new(x,y,0).transform!(t)
}
arc
end
# Creates a set of +Geom::Point3d+ objects for an circle.
#
# @param [Geom::Point3d] center
# @param [Geom::Vector3d] normal
# @param [Number] radius
# @param [Integer] num_segments
#
# @return [Array<Geom::Point3d>]
# @since 2.0.0
def self.circle(center, normal, radius, num_segments)
points = self.arc(center, normal.axes.x, normal, radius, 0.0, Math::PI * 2, num_segments)
points.pop
return points
end
# @param [Geom::Point3d] center
# @param [Geom::Vector3d] xaxis
# @param [Numeric] radius
# @param [Integer] segments
#
# @return [Array<Geom::Point3d>]
# @since 2.7.0
def self.circle2d( center, xaxis, radius, segments = 24 )
segments = segments.to_i
angle = 360.degrees - ( 360.degrees / segments )
self.arc2d( center, xaxis, radius, 0, angle, segments - 1 )
end
# Calculates the number of segments in an arc given the segments of a full circle. This
# will give a close visual quality of the arcs and circles.
#
# @param [Float] angle in radians
# @param [Integer] full_circle_segments
# @param [Boolean] force_even useful to ensure the segmented arc's
# apex hits the apex of the real arc
#
# @return [Integer]
# @since 2.0.0
def self.arc_segments(angle, full_circle_segments, force_even = false)
segments = (full_circle_segments * (angle.abs / (Math::PI * 2))).to_i
segments += 1 if force_even && segments % 2 > 0 # if odd
segments = 1 if segments < 1
return segments
end
# Evenly distribute a fixed number of points on a sphere.
# http://www.cgafaq.info/wiki/Evenly_distributed_points_on_sphere
#
# @param [Integer] number_of_points
# @param [Geom::Point3d] origin
#
# @return [Array<Geom::Point3d>]
# @since 2.3.0
def self.spiral_sphere( number_of_points, origin=ORIGIN )
t = Geom::Transformation.new( origin )
n = number_of_points
node = Array.new( n )
dlong = Math::PI * (3-Math.sqrt(5))
dz = 2.0/n
long = 0
z = 1 - dz/2
(0...n).each { |k|
r = Math.sqrt( 1-z*z )
pt = Geom::Point3d.new( Math.cos(long)*r, Math.sin(long)*r, z )
node[k] = pt.transform( t )
z = z - dz
long = long + dlong
}
node
end
# @param [Geom::Point3d] point1
# @param [Geom::Point3d] point2
# @param [Integer] subdivs The number of resulting segments
#
# @return [Array<Geom::Point3d>]
# @since 2.5.0
def self.interpolate_linear(point1, point2, subdivs)
step = 1.0 / subdivs
pts = []
(0..subdivs).each { |i|
r = step * i
pts << Geom::linear_combination( r, point1, 1.0 - r, point2 )
}
pts
end
# @param [Array<Geom::Point3d>] points
#
# @return [Geom::Point3d]
# @since 2.5.0
def self.average_point(points)
average = Geom::Point3d.new(0,0,0)
return average if points.empty?
number_of_points = points.length
for pt in points
average.x += pt.x
average.y += pt.y
average.z += pt.z
end
average.x = average.x / number_of_points
average.y = average.y / number_of_points
average.z = average.z / number_of_points
average
end
end # module TT::Geom3D
end
File diff suppressed because it is too large Load Diff
@@ -1,561 +0,0 @@
#-------------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-------------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'bezier.rb'
require_relative 'geom3d.rb'
# @note Alpha stage. Very likely to be subject to change!
#
# Read .gli file into an array of drawing instructions.
# The instructions is an array of method name and arguments.
#
# POLYGON [10,0],[14,4],[6,4]
#
# becomes
#
# [ :draw2d, [ Point3d(10,0,0), Point3d(14,4,0), Point3d(6,4,0) ] ]
#
# This translate to arguments that is sent to the View object by using the
# .send method.
#
# = GL Image Format
#
# == Data Types
#
# <color>
# * red <int>
# * green <int>
# * blue <int>
# * alpha <int> (Default = 255)
#
# <point>
# * x <float>
# * y <float>
#
# == Instructions
#
# COLOR
# * color <color>
#
# WIDTH
# * width <int>
#
# STIPPLE
# * width <string>
#
# POINTS
# * type <string>
# TRIANGLE_OPEN
# TRIANGLE_FILLED
# SQUARE_OPEN
# SQUARE_FILLED
# CIRCLE, O
# DISC, *
# PLUS, +
# CROSS, X
# * size <int>
# * points <point>+
#
# LINES
# * points <point>+
#
# POLYLINE
# * points <point>+
#
# BEZIER
# * points <point>+
#
# LOOP
# * points <point>+
#
# ARC
# * center <point>
# * radius <float>
# * degrees <float>
# * segments <int>
# * start_angle <float> (Default = 0.0)
#
# CIRCLE
# * center <point>
# * radius <float>
# * segments <int>
#
# RECT
# * left <float>
# * top <float>
# * width <float>
# * height <float>
# * corner_radius <float> (Default = 0.0)
#
# POLYGON
# * points <point>+
#
# PIE
# * center <point>
# * radius <float>
# * degrees <float>
# * segments <int>
# * start_angle <float> (Default = 0.0)
#
# DISC
# * center <point>
# * radius <float>
# * segments <int>
#
# GRADIENT
# * left <float>
# * top <float>
# * width <float>
# * height <float>
# * steps <int>
# * horizontal <bool>
# * start_color <color>
# * end_color <color>
#
# @since 2.7.0
module SpeckleConnector
class TT::GL_Image
# http://www.songho.ca/opengl/index.html
# http://www.songho.ca/opengl/gl_tessellation.html
# rescue TT::GL_Image::ParseError => e
#
# @since 2.7.0
class ParseError < StandardError; end
# @param [String] gl_image_file
#
# @since 2.7.0
def initialize( gl_image_file )
unless File.exist?( gl_image_file )
raise( ArgumentError, "GL Image '#{gl_image_file}' not found." )
end
@file = gl_image_file
@instructions = read_file( gl_image_file )
end
# @param [Sketchup::View] view
# @param [Integer] x
# @param [Integer] y
#
# @since 2.7.0
def draw( view, x, y )
# Reset viewport
view.line_width = 1
view.line_stipple = ''
view.drawing_color = [0,0,0]
# Process drawing instructions. Offset drawing by given coordinates.
offset = Geom::Vector3d.new( x, y, 0 )
for instruction in @instructions
command, arguments = instruction
positioned_arguments = arguments.map { |argument|
if argument.is_a?( Geom::Point3d )
argument.transform( offset )
else
argument
end
}
begin
view.send( command, *positioned_arguments )
rescue => e
p instruction
raise( e )
end
end
end
# @return [String]
# @since 2.7.0
def inspect
hex_id = TT.object_id_hex( self )
"#<#{self.class.name}:#{hex_id}>"
end
private
# @param [String] gl_image_file
#
# @return [Array]
# @since 2.7.0
def read_file( gl_image_file )
offset = Geom::Point3d.new( 0, 0, 0 )
odd_width = true # Default width = 1.0
line_width = 1
instructions = []
File.open( gl_image_file, 'r' ) { |file|
file.each_line { |line|
#next if line[0,1] == '#'
next if line[0] == 35 # Is it faster to check for single char like this?
data = decode_line( line )
command = data.shift
case command
when nil
# Skip
when 'OFFSET'
x, y = data
offset = Geom::Point3d.new( x, y, 0 )
when 'COLOR'
add_instruction( instructions, :drawing_color=, data )
when 'WIDTH'
line_width = data[0]
odd_width = data[0] % 2 == 1
add_instruction( instructions, :line_width=, data )
when 'STIPPLE'
add_instruction( instructions, :line_stipple=, data )
when 'POINTS'
style = data.shift
size = data.shift
r = size / 2
points = []
case style
when 'SQUARE_OPEN'
for point in data
x1, y1 = point.to_a.map { |i| i - r }
x2 = x1 + size
y2 = y1 + size
points << Geom::Point3d.new( x1, y1, 0 )
points << Geom::Point3d.new( x2, y1, 0 )
points << points.last
points << Geom::Point3d.new( x2, y2, 0 )
points << points.last
points << Geom::Point3d.new( x1, y2, 0 )
points << points.last
points << points[-7]
end
adjust_points!( points, odd_width, offset )
add_instruction( instructions, :draw2d, points, GL_LINES )
when 'SQUARE_FILLED'
for point in data
x1, y1 = point.to_a.map { |i| i - r }
x2 = x1 + size
y2 = y1 + size
points << Geom::Point3d.new( x1, y1, 0 )
points << Geom::Point3d.new( x2, y1, 0 )
points << Geom::Point3d.new( x2, y2, 0 )
points << Geom::Point3d.new( x1, y2, 0 )
end
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_QUADS )
when 'TRIANGLE_OPEN'
for point in data
x, y = point.to_a
points << Geom::Point3d.new( x, y - r, 0 )
points << Geom::Point3d.new( x - r, y + r, 0 )
points << points.last
points << Geom::Point3d.new( x + r, y + r, 0 )
points << points.last
points << points[-5]
end
adjust_points!( points, odd_width, offset )
add_instruction( instructions, :draw2d, points, GL_LINES )
when 'TRIANGLE_FILLED'
for point in data
x, y = point.to_a
points << Geom::Point3d.new( x, y - r, 0 )
points << Geom::Point3d.new( x - r, y + r, 0 )
points << Geom::Point3d.new( x + r, y + r, 0 )
end
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_TRIANGLES )
when 'PLUS', '+'
for point in data
x, y = point.to_a
points << Geom::Point3d.new( x, y - r, 0 )
points << Geom::Point3d.new( x, y + r, 0 )
points << Geom::Point3d.new( x - r, y, 0 )
points << Geom::Point3d.new( x + r, y, 0 )
end
adjust_points!( points, odd_width, offset )
add_instruction( instructions, :draw2d, points, GL_LINES )
when 'CROSS', 'X'
for point in data
x, y = point.to_a
points << Geom::Point3d.new( x - r, y - r, 0 )
points << Geom::Point3d.new( x + r, y + r, 0 )
points << Geom::Point3d.new( x + r, y - r, 0 )
points << Geom::Point3d.new( x - r, y + r, 0 )
end
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_LINES )
when 'DISC', '*'
for point in data
points = TT::Geom3d.circle( point, Z_AXIS, r, 24 )
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_POLYGON )
end
when 'CIRCLE', 'O'
for point in data
points = TT::Geom3d.circle( point, Z_AXIS, r, 24 )
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_LINE_LOOP )
end
else
raise( ParseError, "Invalid point style. (#{style})" )
end
when 'LINES'
points = data
adjust_points!( points, odd_width, offset )
add_instruction( instructions, :draw2d, points, GL_LINES )
when 'POLYLINE'
points = data
adjust_points!( points, odd_width, offset )
add_instruction( instructions, :draw2d, points, GL_LINE_STRIP )
when 'LOOP'
points = data
adjust_points!( points, odd_width, offset )
add_instruction( instructions, :draw2d, points, GL_LINE_LOOP )
when 'FRAME'
x, y, w, h, r = data # X, Y, Width, Height, Radius
o = line_width / 2.0 # Because of this - don't use off_width.
points = []
if r
x_axis = X_AXIS.reverse
z = Z_AXIS
# Account for line width.
ro = r - o
# Top Left
c = Geom::Point3d.new( x + r, y + r, 0 )
arc = TT::Geom3d.arc( c, x_axis, z, ro, 0, 90.degrees, 8 )
points.concat( arc )
# Top Right
c = Geom::Point3d.new( x + w - r, y + r, 0 )
arc = TT::Geom3d.arc( c, x_axis, z, ro, 90.degrees, 180.degrees, 8 )
points.concat( arc )
# Bottom Right
c = Geom::Point3d.new( x + w - r, y + h - r, 0 )
arc = TT::Geom3d.arc( c, x_axis, z, ro, 180.degrees, 270.degrees, 8 )
points.concat( arc )
# Bottom Right
c = Geom::Point3d.new( x + r, y + h - r, 0 )
arc = TT::Geom3d.arc( c, x_axis, z, ro, 270.degrees, 360.degrees, 8 )
points.concat( arc )
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_LINE_LOOP )
else
points << Geom::Point3d.new( x, y + o, 0 )
points << Geom::Point3d.new( x + w, y + o, 0 )
points << Geom::Point3d.new( x + w - o, y, 0 )
points << Geom::Point3d.new( x + w - o, y + h, 0 )
points << Geom::Point3d.new( x + w, y + h - o, 0 )
points << Geom::Point3d.new( x, y + h - o, 0 )
points << Geom::Point3d.new( x + o, y + h, 0 )
points << Geom::Point3d.new( x + o, y, 0 )
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_LINES )
end
when 'BEZIER'
points = TT::Geom3d::Bezier.points( data, 8 * data.size )
adjust_points!( points, odd_width, offset )
add_instruction( instructions, :draw2d, points, GL_LINE_STRIP )
when 'ARC'
center, radius, degrees, segments, start_angle = data
start_angle = (start_angle) ? start_angle.degrees : 0.0
end_angle = start_angle + degrees.degrees
points = TT::Geom3d.arc( center, X_AXIS.reverse, Z_AXIS, radius, start_angle, end_angle, segments )
adjust_points!( points, odd_width, offset )
add_instruction( instructions, :draw2d, points, GL_LINE_STRIP )
when 'CIRCLE'
center, radius, segments = data
points = TT::Geom3d.circle( center, Z_AXIS, radius, segments )
adjust_points!( points, odd_width, offset )
add_instruction( instructions, :draw2d, points, GL_LINE_LOOP )
when 'RECT'
x,y,w,h,r = data
points = []
if r
x_axis = X_AXIS.reverse
z = Z_AXIS
# Top Left
c = Geom::Point3d.new( x + r, y + r, 0 )
points.concat( TT::Geom3d.arc( c, x_axis, z, r, 0, 90.degrees, 8 ) )
# Top Right
c = Geom::Point3d.new( x + w - r, y + r, 0 )
points.concat( TT::Geom3d.arc( c, x_axis, z, r, 90.degrees, 180.degrees, 8 ) )
# Bottom Right
c = Geom::Point3d.new( x + w - r, y + h - r, 0 )
points.concat( TT::Geom3d.arc( c, x_axis, z, r, 180.degrees, 270.degrees, 8 ) )
# Bottom Right
c = Geom::Point3d.new( x + r, y + h - r, 0 )
points.concat( TT::Geom3d.arc( c, x_axis, z, r, 270.degrees, 360.degrees, 8 ) )
#
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_POLYGON )
else
points << Geom::Point3d.new( x, y, 0 )
points << Geom::Point3d.new( x + w, y, 0 )
points << Geom::Point3d.new( x + w, y + h, 0 )
points << Geom::Point3d.new( x, y + h, 0 )
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_QUADS )
end
when 'POLYGON'
points = data
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_POLYGON )
when 'PIE'
center, radius, degrees, segments, start_angle = data
start_angle = (start_angle) ? start_angle.degrees : 0.0
end_angle = start_angle + degrees.degrees
points = TT::Geom3d.arc( center, X_AXIS.reverse, Z_AXIS, radius, start_angle, end_angle, segments )
points.unshift( center )
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_TRIANGLE_FAN )
when 'DISC'
center, radius, segments = data
points = TT::Geom3d.circle( center, Z_AXIS, radius, segments )
adjust_points!( points, false, offset )
add_instruction( instructions, :draw2d, points, GL_POLYGON )
when 'GRADIENT'
x, y, w, h, steps, vertical, color1, color2 = data
n = ( vertical ) ? w / steps : h / steps
r = 1.0 / steps
for i in ( 0...steps )
points = []
if vertical
x1 = x + (n * i)
y1 = y
w1 = n
h1 = h
else
x1 = x
y1 = y + (n * i)
w1 = w
h1 = n
end
points << Geom::Point3d.new( x1, y1, 0 )
points << Geom::Point3d.new( x1 + w1, y1, 0 )
points << Geom::Point3d.new( x1 + w1, y1 + h1, 0 )
points << Geom::Point3d.new( x1, y1 + h1, 0 )
adjust_points!( points, false, offset )
c = color2.blend( color1, r * i )
add_instruction( instructions, :drawing_color=, [c] )
add_instruction( instructions, :draw2d, points, GL_QUADS )
end
else
raise( ParseError, "Unknown drawing instruction. (#{command})\n\t#{line.inspect}" )
end
}
}
instructions
end
# On nVidia cards an odd width line must have the co-ordinates in the center
# of the pixel - otherwise the line will be aliased between the pixels on
# either side.
#
# @param [Array<Geom::Point3d>] points
# @param [Boolean] odd_width
# @param [Geom::Vector3d] offset
#
# @return [Array]
# @since 2.7.0
def adjust_points!( points, odd_width, offset )
total_offset = offset.clone
if odd_width
pixel_grid = Geom::Vector3d.new( 0.5, 0.5, 0.0 )
total_offset = total_offset + pixel_grid
end
tr = Geom::Transformation.new( total_offset )
for point in points
point.transform!( tr )
end
nil
end
# @param [Array] instructions
# @param [Symbol] command
# @param [Array] arguments
# @param [Integer] draw_operation
#
# @return [Array]
# @since 2.7.0
def add_instruction( instructions, command, arguments, draw_operation = nil )
last_instruction = instructions.last
# Prepend any drawing operation.
if draw_operation
arguments.unshift( draw_operation )
end
# If there where no previous command or the last command was different
# there is no optimization to be made.
if last_instruction.nil? || last_instruction[0] != command
instructions << [ command, arguments ]
return nil
end
# Merge commands
case command
when :drawing_color=, :line_width=
# Remove command that override each other.
instructions.pop
instructions << [ command, arguments ]
when :draw2d
# Merge operations when possible.
current_operation = arguments[0]
case current_operation
when GL_POINTS, GL_LINES, GL_TRIANGLES, GL_QUADS
# These operations can safely be merged if they appear in sequence.
last_operation = last_instruction[1][0]
if current_operation == last_operation
arguments.shift
last_instruction[1].concat( arguments )
else
instructions << [ command, arguments ]
end
else
# All other operations cannot be merged.
instructions << [ command, arguments ]
end
end
instructions
end
# @param [String] line
#
# @return [Array]
# @since 2.7.0
def decode_line( line )
parts = line.split(/\s/)
args = []
for part in parts
next if part.empty?
if r = part.match( /^\<(\d+),(\d+),(\d+)(?:,(\d+))?\>$/ )
# Colour - <255,64,0> - <255,64,0,64>
color = r.captures.compact.map { |str| str.to_i }
args << Sketchup::Color.new( color )
elsif r = part.match( /^\[(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)\]$/ )
# Point2d - [-20,30] - [20.5,-30.5]
x, y = r.captures.map! { |str| str.to_f }
args << Geom::Point3d.new( x, y, 0 )
elsif part.match( /^(-?\d+(?:\.\d+)?)$/ )
# Number
args << part.to_f
elsif part.match( /^true$/i )
args << true
elsif part.match( /^false$/i )
args << false
else
args << part
end
end
args
end
end # class TT::GL_Image
end
-874
View File
@@ -1,874 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'debug.rb'
require_relative 'json.rb'
module SpeckleConnector
# Wrapper library for creating and manipulating WebDialogs and HTML content
# via Ruby.
#
# @note Very likely to be subject to change!
module TT::GUI
# http://juixe.com/techknow/index.php/2007/01/22/ruby-class-tutorial/
# http://stackoverflow.com/questions/1645398/ruby-include-question
# http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/
#
# @since 2.6.0
module ControlEvents
# @since 2.6.0
def self.included( base )
# Hash table of valid events for the class.
# > Key: (Symbol)
# > Value: (Symbol)
base.instance_variable_set( '@control_events', {} )
base.extend( ControlEventDefinitions )
end
# @since 2.6.0
module ControlEventDefinitions
# @since 2.6.0
def inherited( subclass )
instance_var = '@control_events'
parent_events = instance_variable_get( instance_var ).dup
subclass.instance_variable_set( instance_var, parent_events )
end
end # module ControlEventDefinitions
end # module ControlEvents
# All GUI elements inherit this class.
#
# @abstract
# @since 2.4.0
class Control
include ControlEvents
attr_accessor( :ui_id ) # @since 2.4.0
attr_accessor( :name ) # @since 2.7.0
attr_accessor( :left, :right, :top, :bottom, :height, :width ) # @since 2.4.0
attr_accessor( :parent, :window ) # @since 2.4.0
attr_accessor( :tooltip ) # @since 2.5.0
attr_accessor( :font_name, :font_size, :font_bold, :font_italic ) # @since 2.7.0
# (!) Tab Index
# Base Events:
# * click, double-click (?)
# * keydown, keypress, keyup (?)
# * mousein, mouseout, mousemove
# * mousebuttondown, mousebuttonup
# * focus, blur
# @since 2.4.0
def initialize( *args )
# Unique identifier used in the WebDialog's HTML.
@ui_id = 'UI_' + self.object_id.to_s
# Hash with Symbols for keys idenitfying the event.
# Each event is an array of Proc's.
@events = {}
@disabled = false
end
# @return [Array<Symbol>]
# @since 2.6.0
def self.events
@control_events.keys
end
# @return [Array<Symbol>]
# @since 2.6.0
def self.has_event?( event )
@control_events.key?( event )
end
# Adds an event to the stack.
#
# @param [Symbol] event
# @param [Proc] block
#
# @return [nil]
# @since 2.5.0
def add_event_handler( event, &block )
unless self.class.has_event?( event )
raise( ArgumentError, "Event #{event} not defined for #{self.class}" )
end
@events[event] ||= []
@events[event] << block
nil
end
# Triggers the given event. All attached procs for that event will be called.
#
# @param [Symbol] event
# @param [Array] args Set of arguments to be passed to the called procs.
#
# @return [Boolean]
# @since 2.5.0
def call_event( event, args = nil )
TT::debug "call_event(#{event.to_s})"
TT::debug args.inspect
if @events.key?( event )
@events[event].each { |proc|
next if proc.nil? # In case Button control where made without a block.
# Add self to argument list so the called event can get the handle for
# the control triggering it.
if args.nil?
proc.call( self )
else
args.unshift( self )
proc.call( *args )
end
}
true
else
# (?) Raise error?
false
end
end
# @return [Boolean]
# @since 2.7.0
def disabled?
@disabled == true
end
# @param [Boolean] value
#
# @return [Boolean]
# @since 2.7.0
def disabled=( value )
@disabled = value
update_html_element( { :disabled => value } )
value
end
# @param [Boolean] value
#
# @return [Boolean]
# @since 2.7.0
def enabled=( value )
self.disabled = !value
end
# @return [Boolean]
# @since 2.7.0
def enabled?
!@disabled
end
# @param [Numeric] value
#
# @return [Numeric]
# @since 2.6.0
def top=( value )
@top = value
update_html_element( { :top => value } )
value
end
# @param [Numeric] value
#
# @return [Numeric]
# @since 2.6.0
def left=( value )
@left = value
update_html_element( { :left => value } )
value
end
# @param [Numeric] value
#
# @return [Numeric]
# @since 2.6.0
def bottom=( value )
@bottom = value
update_html_element( { :bottom => value } )
value
end
# @param [Numeric] value
#
# @return [Numeric]
# @since 2.6.0
def right=( value )
@right = value
update_html_element( { :right => value } )
value
end
# @param [Numeric] value
#
# @return [Numeric]
# @since 2.6.0
def width=( value )
@width = value
update_html_element( { :width => value } )
value
end
# @param [Numeric] value
#
# @return [Numeric]
# @since 2.6.0
def height=( value )
@height = value
update_html_element( { :height => value } )
value
end
# @param [Numeric] left
# @param [Numeric] top
#
# @return [Array<Numeric,Numeric>]
# @since 2.4.0
def move( left, top )
@left = left
@top = top
update_html_element( { :left => left, :top => top } )
[ left, top ]
end
# @param [Numeric] width
# @param [Numeric] height
#
# @return [Array<Numeric,Numeric>]
# @since 2.4.0
def size( width, height )
@width = width
@height = height
update_html_element( { :width => width, :height => height } )
[ width, height ]
end
# @param [Numeric] top
# @param [Numeric] right
# @param [Numeric] bottom
# @param [Numeric] left
#
# @return [Array<Numeric,Numeric,Numeric,Numeric>]
# @since 2.4.0
def position( top, right, bottom, left )
@top = top
@right = right
@bottom = bottom
@left = left
properties = {
:top => top,
:right => right,
:bottom => bottom,
:left => left
}
update_html_element( properties )
[ top, right, bottom, left ]
end
# (!) Need explicit :position property.
# @return [Boolean]
# @since 2.5.0
def positioned?
( @left || @right || @top || @bottom ) ? true : false
end
# @param [String] value
#
# @return [String]
# @since 2.7.0
def font_name=( value )
@font_name = value
update_html_element( { :font_name => value } )
value
end
# @param [Numeric] value
#
# @return [Numeric]
# @since 2.7.0
def font_size=( value )
@font_size = value
update_html_element( { :font_size => value } )
value
end
# @return [TT::JSON]
# @since 2.5.0
def properties
options = TT::JSON.new
options['id'] = @ui_id
options['parent'] = @parent.ui_id if @parent.is_a?( Control )
options['type'] = self.class.name
if positioned?
options['top'] = @top if @top
options['bottom'] = @bottom if @bottom
options['left'] = @left if @left
options['right'] = @right if @right
end
options['width'] = @width if @width
options['height'] = @height if @height
options['font_name'] = @font_name if @font_name
options['font_size'] = @font_size if @font_size
options['disabled'] = @disabled if @disabled
if self.respond_to?( :custom_properties )
options.merge!( custom_properties() )
end
options
end
# @return [String]
# @since 2.6.0
def inspect
"<#{self.class}:#{TT.object_id_hex(self)}>"
end
# Release all references to other objects. Setting them to nil. So that
# the GC can collect them.
#
# @return [Nil]
# @since 2.6.0
def release!
@parent = nil
@window = nil
@events.clear
nil
end
private
# @param [Hash] properties
#
# @return [Boolean]
# @since 2.6.0
def update_html_element( properties )
if self.window && self.window.visible?
properties['type'] = self.class.name # Required by JS UI.update_properties
self.window.call_script( 'UI.update_properties', self.ui_id, properties )
else
false
end
end
# Defines an event for the control. If an event is not defines it cannot be
# called.
#
# @overload set( event, ... )
# @param [Symbol] event
#
# @return [Nil]
# @since 2.6.0
def self.define_event( *args )
for event in args
raise( ArgumentError, 'Expected a Symbol' ) unless event.is_a?( Symbol )
@control_events[event] = event
end
nil
end
end # class Control
# @abstract +Container+ and +Window+ implements this.
# @since 2.4.0
module ContainerElement
# @since 2.4.0
attr( :controls )
# @since 2.4.0
def initialize( *args )
super( *args )
@controls = []
end
# @param [Control] control
#
# @return [Boolean] +True+ if the webdialog was open and the control added.
# @since 2.4.0
def add_control( control )
raise( ArgumentError, 'Expected Control' ) unless control.is_a?( Control )
# Add to Ruby DOM tree
@controls << control
control.parent = self
control.window = self.window
# Add to Webdialog
if self.window && self.window.visible?
self.window.add_control_to_webdialog( control )
return true
end
false
end
# @param [Control] control
#
# @return [Boolean] +True+ if the webdialog was open and the control removed.
# @since 2.9.0
def remove_control( control )
raise( ArgumentError, 'Expected Control' ) unless control.is_a?( Control )
raise( IndexError, 'Control not found' ) unless controls.include?( control )
@controls.delete( control )
control_ui_id = control.ui_id
control.release!
if self.window && self.window.visible?
self.window.call_script( 'UI.remove_control', control_ui_id )
return true
end
false
end
# While #add_control add the control to the Ruby class's internal list, this
# method adds the control to the webdialog.
#
# @private
# @return [Nil]
# @since 2.5.0
def add_controls_to_webdialog
for control in @controls
@window.add_control_to_webdialog( control )
if control.is_a?( ContainerElement )
control.add_controls_to_webdialog
end
end
nil
end
# @param [String] ui_id
#
# @return [Control,Nil]
# @since 2.5.0
def get_control_by_ui_id( ui_id )
for control in @controls
return control if control.ui_id == ui_id
if control.is_a?( ContainerElement )
result = control.get_control_by_ui_id( ui_id )
return result if result
end
end
nil
end
# @param [Symbol] name
#
# @return [Control,Nil]
# @since 2.5.0
def get_control_by_name( name )
for control in @controls
return control if control.name == name
if control.is_a?( ContainerElement )
result = control.get_control_by_name( name )
return result if result
end
end
nil
end
alias :[] :get_control_by_name
# @see Control#release!
# @return [Nil]
# @since 2.6.0
def release!
for control in @controls
control.release!
end
@controls.clear
super
end
end # module ContainerElement
# @since 2.4.0
class Container < Control
include ContainerElement
# (!) Background color. (Style)
end # class Container
# @since 2.7.0
class Groupbox < Container
# @since 2.7.0
attr_reader( :label )
# @param [String] label
#
# @since 2.7.0
def initialize( label = '' )
super
@label = label
end
# @param [String] value
#
# @return [String]
# @since 2.7.0
def label=( value )
@label = value
update_html_element( { :label => value } )
value
end
# @return [TT::JSON]
# @since 2.7.0
def custom_properties
prop = TT::JSON.new
prop['label'] = @label
prop
end
end # Groupbox
# = Events
# * +:click+
#
# @since 2.4.0
class Button < Control
# @since 2.4.0
attr_accessor( :caption )
# @since 2.6.0
define_event( :click )
# @param [String] caption
# @param [Proc] on_click
#
# @since 2.4.0
def initialize( caption, &on_click )
super
# Defaults
# http://msdn.microsoft.com/en-us/library/aa511279.aspx#controlsizing
# http://msdn.microsoft.com/en-us/library/aa511453.aspx#sizing
# Actual: 75x23
# Visible: 73x21
@width = 73
@height = 21
# User Properties
@caption = caption
add_event_handler( :click, &on_click )
end
# @return [TT::JSON]
# @since 2.5.0
def custom_properties
prop = TT::JSON.new
prop['caption'] = @caption
prop
end
end # class Button
# @since 2.6.0
class ToolbarButton < Button
# @since 2.6.0
attr_accessor( :icon )
# @param [String] caption
# @param [Proc] on_click
#
# @since 2.6.0
def initialize( caption, &on_click )
super
# Defaults
@width = 28
@height = 28
end
# @return [TT::JSON]
# @since 2.6.0
def custom_properties
# (!) Improve custom properties.
prop = super
#prop['caption'] = @caption
prop['icon'] = @icon if @icon
prop
end
end # class ToolbarButton
# @since 2.7.0
class Checkbox < Control
# @since 2.7.0
attr_reader( :label )
# @since 2.7.0
define_event( :change )
define_event( :click )
# @param [String] label
#
# @since 2.7.0
def initialize( label, checked = false )
super
@label = label
@checked = checked
end
# @return [Boolean]
# @since 2.7.0
def check!
checked = true
end
# @return [Boolean]
# @since 2.7.0
def uncheck!
checked = false
end
# @return [Boolean]
# @since 2.7.0
def toggle!
checked = !checked
end
# @return [Boolean]
# @since 2.7.0
def checked
@checked = self.window.get_checkbox_state( self.ui_id )
end
alias checked? checked
# @param [Boolean] value
#
# @return [Boolean]
# @since 2.7.0
def checked=( value )
@checked = value
update_html_element( { :checked => value } )
value
end
# @param [String] value
#
# @return [String]
# @since 2.7.0
def label=( value )
@label = value
update_html_element( { :label => value } )
value
end
# @return [TT::JSON]
# @since 2.7.0
def custom_properties
prop = TT::JSON.new
prop['label'] = @label
prop['checked'] = @checked
prop
end
end # class Checkbox
# = Events
# * +:change+
#
# @since 2.4.0
class Listbox < Control
# @since 2.5.0
attr_reader( :value, :items, :size, :multiple )
# @since 2.6.0
define_event( :change )
# @param [Array<String>] list
#
# @since 2.5.0
def initialize( list = nil )
super
@value = nil
@items = ( list.is_a?(Array) ) ? list : [] # (?) Hash instead?
@size = nil
@multiple = false
end
# @return [TT::JSON]
# @since 2.5.0
def custom_properties
prop = TT::JSON.new
prop['value'] = @value
prop['items'] = @items
prop['size'] = @size if @size
prop['multiple'] = @multiple if @multiple
prop
end
# @overload add_item(string)
# @param [String] string
#
# @overload add_item(string, ...)
# @param [String] string
#
# @return [String]
# @since 2.5.0
def add_item( *args )
args = args[0] if args.size == 1 && args[0].is_a?( Array )
if args.size == 1
@items << args[0]
self.window.call_script( 'UI.add_list_item', self.ui_id, args[0] )
else
@items.concat( args )
self.window.call_script( 'UI.add_list_item', self.ui_id, args )
end
end
# @return [String]
# @since 2.7.0
def value
@value = self.window.get_control_value( self.ui_id )
end
# @param [String] string
#
# @return [String]
# @since 2.7.0
def value=( string )
unless @items.include?( string )
raise ArgumentError, "'#{string}' not a valid value in list."
end
@value = string
update_html_element( { :value => string } )
string
end
end # class Listbox
# = Events
# * +:change+
# * +:keydown+
# * +:keypress+
# * +:keyup+
# * +:focus+
# * +:blur+
#
# @since 2.4.0
class Textbox < Control
# @since 2.4.0
attr_accessor( :value, :multiline )
alias :multiline? :multiline
# @since 2.6.0
define_event( :change )
define_event( :textchange )
define_event( :keydown, :keypress, :keyup )
define_event( :focus, :blur )
define_event( :copy, :cut, :paste )
# @param [String] value
#
# @since 2.4.0
def initialize( value )
super
@value = value
end
# @return [String]
# @since 2.6.0
def value
@value = self.window.get_control_value( self.ui_id )
end
# @param [String] string
#
# @return [String]
# @since 2.6.0
def value=( string )
@value = string
update_html_element( { :value => string } )
string
end
# @return [TT::JSON]
# @since 2.6.0
def custom_properties
prop = TT::JSON.new
prop['value'] = @value
prop['multiline'] = @multiline
prop
end
end # class Textbox
# @since 2.4.0
class Label < Control
# @since 2.4.0
attr_accessor( :caption )
# @since 2.7.0
attr_accessor( :url )
# @since 2.7.0
define_event( :open_url )
# @param [String] caption
# @param [Control] control Control which receives focus when the Label is activated.
#
# @since 2.4.0
def initialize( caption, control=nil )
super
@caption = caption
@control = control
@url = nil
add_event_handler( :open_url ) {
UI.openURL( param )
}
# (!) Align
end
# @param [String] string
#
# @return [String]
# @since 2.6.0
def caption=( string )
@caption = string
update_html_element( { :caption => string } )
string
end
# @param [String] string
#
# @return [String]
# @since 2.7.0
def url=( string )
@url = string
update_html_element( { :url => string } )
string
end
# @return [TT::JSON]
# @since 2.6.0
def custom_properties
prop = TT::JSON.new
prop['caption'] = @caption
prop['control'] = @control.ui_id if @control
prop['url'] = @url if @url
prop
end
end # class Label
end # module TT::GUI
end
@@ -1,31 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'instance.rb'
# Collection of Face methods.
#
# @since 2.0.0
module SpeckleConnector
module TT::Image
# Returns the material for the given +Image+.
#
# @param [Sketchup::Image] image
#
# @return [Sketchup::Material]
# @since 2.0.0
def self.material(image)
definition = TT::Instance.definition(image)
face = definition.entities.grep(Sketchup::Face).first
face.material
end
end # module TT::Image
end
@@ -1,315 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'gui.rb'
require_relative 'json.rb'
require_relative 'locale.rb'
require_relative 'modal_wrapper.rb'
require_relative 'toolwindow.rb'
require_relative 'settings.rb'
# (i) Alpha stage. Very likely to be subject to change!
#
# @example
# i = TT::GUI::Inputbox.new
# i.add_control( {
# :label => 'FooBar',
# :value => 'Hello'
# } )
# i.prompt { |results|
# p results
# }
#
# @since 2.4.0
module SpeckleConnector
class TT::GUI::Inputbox < TT::GUI::ToolWindow
CT_LIST = 1
CT_RADIOBOX = 2
# @since 2.5.5
#attr_accessor( :controls )
def controls; @ib_controls; end;
# Creates a new Inputbox instance.
#
# @todo Escape HTML data (?) Ok on Windows IE.
# @todo Descriptions (Info, HTML)
# @todo Slider
# @todo PickList
# @todo Events
#
# @param [Hash] options
# @option options [String] :title ("Inputbox")
# @option options [String] :pref_key If present the inputbox will remember
# it's values and properties between sessions.
# @option options [Boolean] :save_values (true) Set to false if you
# do not want the values to be remembered between sessions, but want the
# window size etc to be remembered. Requires +:pref_key+ to be +true+
# @option options [Boolean] :resizable (true)
# @option options [Integer] :width
# @option options [Integer] :height
# @option options [String] :accept_label ("Ok") The caption of the
# accept button.
# @option options [String] :cancel_label ("Cancel") The caption of the
# cancel button.
# @option options [Boolean] :modal (false) Set to true to prevent
# the user from interacting with the model while the window is open. It also
# prevents other modal windows (From TT_Lib) from opening.
# @option options [Boolean] :true_modal (false) *Deprecated* Under
# Windows the window can be made into a true modal window, but since it
# can't under OSX this key is deprecated.
#
# @since 2.4.0
def initialize(options={})
raise ArgumentError unless options.is_a?( Hash )
# Window options (Sent to Window < Webdialog instance)
wnd_options = {
:dialog_title => 'Inputbox',
:scrollable => false,
:resizable => true
}
wnd_options[:dialog_title] = options[:title] if options.key?(:title)
wnd_options[:preferences_key] = options[:pref_key] if options.key?(:pref_key)
wnd_options[:resizable] = options[:resizable] if options.key?(:resizable)
wnd_options[:width] = options[:width] if options.key?(:width)
wnd_options[:height] = options[:height] if options.key?(:height)
wnd_options[:left] = options[:left] if options.key?(:left)
wnd_options[:top] = options[:top] if options.key?(:top)
wnd_options[:min_width] = 200
wnd_options[:min_height] = 12
# Window UI options (Sent to Javascript)
@html_options = {
:accept_label => 'Ok',
:cancel_label => 'Cancel'
}
@html_options[:save_values] = options[:save_values] if options.key?(:save_values)
@html_options.merge!(options)
# Internal flags.
@save_values = ( options.key?(:save_values) ) ? options[:save_values] : true
@true_modal = ( options.key?(:true_modal) ) ? options[:true_modal] : false
@modal = ( options.key?(:modal) ) ? options[:modal] : false
# Array of the controls and their values.
@ib_controls = []
@values = nil
# Flag indicating if the window is closing.
@closing = false
# Initate the ModalWrapper
if !@true_modal && @modal
@modal_window = TT::GUI::ModalWrapper.new( self )
end
# Control's label is the section key for settings
if options.key?(:pref_key) && @save_values
@defaults = TT::Settings.new( options[:pref_key] )
else
@defaults = nil
end
# Initialize the parent Window class
super( wnd_options )
# Using relative paths, relying on the BASE element seem to fail on some
# computers running various versions of Windows. Not sure why - maybe
# different security settings..?
wpath = File.join( TT::Lib.path, 'webdialog' )
add_script( local_path( File.join(wpath, 'js', 'inputbox.js') ) )
add_style( local_path( File.join(wpath, 'css', 'inputbox.css') ) )
add_action_callback( 'Inputbox_ready', &method(:event_inputbox_ready) )
add_action_callback( 'Inputbox_accept', &method(:event_inputbox_accept) )
add_action_callback( 'Inputbox_cancel', &method(:event_inputbox_cancel) )
set_on_close( &method(:event_inputbox_close) )
end
# When the webdialog reports it's ready, call function to start building the
# UI with the given user options.
def event_inputbox_ready( window, params )
TT.debug '>> Input Ready'
# Window settings
o = TT::JSON.new( @html_options )
window.execute_script("inputbox.init_html(#{o});")
# Add controls
@ib_controls.each { |control|
c = control.clone
if c[:value].is_a?( Float ) && !c[:value].is_a?( Length )
c[:value] = TT::Locale.float_to_string( c[:value] )
elsif c[:value].is_a?( Length )
c[:value] = c[:value].to_s
end
window.execute_script("inputbox.add_control(#{c});")
}
end
# When the user accepts the values in the Inputbox, collect the data and
# convert them back from string into their input types.
# (!) Build Hash with keys and values.
def event_inputbox_accept( window, params )
TT.debug '>> Input Accept'
@values = {}
@ib_controls.each { |control|
value = window.send( :get_value, control[:id] )
type = ( control[:options] && control[:multiple] ) ? Array : control[:value]
value = TT::Locale.cast_string(value, type, '||')
@defaults[ control[:label] ] = value if @save_values
@values[ control[:key] ] = value
control[:value] = value
}
window.close
end
# User cancels the inputbox, either by using the Cancel button, the Close
# button or the Right-Click menu.
def event_inputbox_cancel( window, params )
TT.debug '>> Input Cancel'
window.close
end
# Flag the window as closing to avoid the Modal_Wrapper from also calling
# close - something which will lead to multiple triggering of this event.
def event_inputbox_close
TT.debug '>> Window Closed'
@closing = true
@modal_window.close if @modal_window
begin
@block.call(@values) # (?) Delay with timer to ensure window closes?
rescue => e
puts e.message
puts e.backtrace.join("\n")
UI.messagebox("#{e.message}\n\n#{e.backtrace.join("\n")}", MB_MULTILINE)
end
end
# Flag indicating if the webdialog is closing.
#
# @since 2.4.0
def closing?
@closing
end
# Adds a new input control.
#
# @example Normal Textbox
# i = TT::GUI::Inputbox.new( options )
# i.add_control( {
# :label => 'Hello',
# :value => 'World'
# } )
#
# @example Natrually Ordered Multi Select List
# i = TT::GUI::Inputbox.new( options )
# i.add_control( {
# :label => 'My List',
# :description => 'Lorem Ipsum Dolor Sit Amet.',
# :value => ['Hello', 'World'],
# :options => ['Foo', 'Hello', 'Bar', 'World', 'FooBar'],
# :multiple => true,
# :order => 1,
# :natrual_order => true,
# :size => 5
# } )
#
# @param [Hash] options
# @option options [String] :label *Required* Should be unique in order for
# persistant values to function.
# @option options [String|Array] :value *Required* Default value.
# @option options [Symbol] :key Identifying key used in the result hash when
# the dialog closes. This was introdused in 2.5.0. Before that the results
# where Arrays.
# @option options [String] :description Explainatory text accosiated to the
# control.
# @option options [Array] :options Array of valued, used for lists and
# radiobox options.
# @option options [Integer] :order -1, 0 or 1 - -1 means decending order, 0 no
# order, 1 accending order.
# @option options [Boolean] :natrual_order If the list is ordered, set this
# +true+ for a natrual ordering.
# @option options [Integer] :size Set this property for a scrollable list
# instead of a drop down list.
# @option options [Boolean] :multiple Allows mulitple items to be selected.
# @option options [Integer] :type Possible constants:
# * +TT::GUI::Inputbox::CT_LIST+
# * +TT::GUI::Inputbox::CT_RADIOBOX+
# @option options [Boolean] :no_save Prevents the value from being saved if
# set to +true+. Added version 2.5.0.
#
# @since 2.4.0
def add_control(options)
options = ( options.is_a?( Hash ) ) ? TT::JSON.new( options ) : options.dup
raise ArgumentError unless options.is_a?( TT::JSON )
# Process and prepare the options before sending it to the Webdialog.
options[:id] = "Inputbox_control#{@ib_controls.size}"
# Ensure the options are JSON object which can be sent to the Webdialog.
# Hashes will aumatically be converted to JSON objects.
if options.key?( :key )
# Ensure key is unique.
key = options[:key]
@ib_controls.each { |control|
raise ArgumentError, 'options[:key] must be uniqe' if control[:key] == key
}
else
options[:key] = options[:id]
end
# Default value.
if @defaults && !options[:no_save]
options[:value] = @defaults[ options[:label], options[:value] ]
end
@ib_controls << options
# If controls are added while the inputbox is open it needs to be notified.
if self.visible?
self.execute_script("inputbox.add_control(#{options});")
end
end
# *Note* As of 2.5.0 the returned argument of the block is a Hash instead of
# an Array.
#
# @param [&block] block the callback that receives the resulting values as a
# +Hash+ when the inputbox closes. Only used if the inputbox is not true modal.
#
# @return [Hash|nil] Hash of resulting values if Inputbox is true modal, nil otherwise.
# @since 2.4.0
def prompt(&block)
@values = nil
@block = block
if @true_modal
show_window(@true_modal)
@values
elsif @modal
@modal_window.show
nil
else
self.show_window
nil
end
end
private
# Returns the value of the given element identified by its index in +@controls+.
#
# @param [String] id
#
# @return [String]
# @since 2.4.0
def get_value(id)
self.execute_script("inputbox.get_value('#{id}');")
self.get_element_value('RUBY_Inputbox_get_value');
end
end # module TT::GUI::Inputbox
end
@@ -1,69 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# Collection of Group, ComponentInstance and Image methods.
#
# @since 2.0.0
module SpeckleConnector
module TT::Instance
# Returns the definition for a +Group+, +ComponentInstance+ or +Image+
#
# @param [:definition, Sketchup::Group, Sketchup::Image] instance
#
# @return [Sketchup::ComponentDefinition,Mixed]
# @since 2.0.0
def self.definition(instance)
if instance.respond_to?(:definition)
begin
return instance.definition
rescue
# Previously this was the first check, but too many extensions modify
# Sketchup::Group.definition with a method which is bugged so to avoid
# all the complaints about extensions not working due to this the call
# is trapped is a rescue block and any errors will make it fall back to
# using the old way of finding the group definition.
end
end
if instance.is_a?(Sketchup::Group)
# (i) group.entities.parent should return the definition of a group.
# But because of a SketchUp bug we must verify that group.entities.parent
# returns the correct definition. If the returned definition doesn't
# include our group instance then we must search through all the
# definitions to locate it.
if instance.entities.parent.instances.include?(instance)
return instance.entities.parent
else
Sketchup.active_model.definitions.each { |definition|
return definition if definition.instances.include?(instance)
}
end
elsif instance.is_a?(Sketchup::Image)
Sketchup.active_model.definitions.each { |definition|
if definition.image? && definition.instances.include?(instance)
return definition
end
}
end
return nil # Given entity was not an instance of an definition.
end
# Query to whether it's a Group or ComponentInstance
#
# @param [Sketchup::Entity] entity
#
# @return [Boolean]
# @since 2.0.0
def self.is?(entity)
entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
end
end # module TT::Instance
end
@@ -1,53 +0,0 @@
arc.rb
attributes.rb
babelfish.rb
bezier.rb
binary.rb
boolean_attributes.rb
bounds.rb
color.rb
core.rb
cursor.rb
c_extension_manager.rb
debug.rb
deferred_event.rb
definition.rb
dimension.rb
draw_cache.rb
edges.rb
entities.rb
faces.rb
geom3d.rb
gizmo_manipulator.rb
gl_image.rb
gui.rb
image.rb
inputbox.rb
instance.rb
javascript.rb
json.rb
length.rb
locale.rb
materials.rb
metaclass.rb
modal_wrapper.rb
model.rb
point3d.rb
point3d_ex.rb
profiler.rb
progressbar.rb
ray.rb
selection.rb
settings.rb
simpletask.rb
sketchup.rb
system.rb
toolbarwindow.rb
toolwindow.rb
uvq.rb
uv_plane.rb
version.rb
webdialog_patch.rb
win32.rb
win32_shim.rb
window.rb
@@ -1,47 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'json.rb'
# Javascript helper module.
#
# @since 2.5.0
module SpeckleConnector
module TT::Javascript
# Query to whether it's a Group or ComponentInstance
#
# @param [Object] object
#
# @return [String]
# @since 2.5.0
def self.to_js(object, format=false)
if object.is_a?( TT::JSON )
object.to_s(format)
elsif object.is_a?( Hash )
TT::JSON.new(object).to_s(format)
elsif object.is_a?( Symbol ) # 2.5.0
object.inspect.inspect
elsif object.nil?
'null'
elsif object.is_a?( Array ) # 2.7.0
data = object.map { |x| self.to_js(x, format) }
"[#{data.join(',')}]"
elsif object.is_a?( Geom::Point3d ) # 2.7.0
"new Point3d( #{object.to_a.join(', ')} )"
elsif object.is_a?( Geom::Vector3d ) # 2.7.0
"new Vector3d( #{object.to_a.join(', ')} )"
else
# (!) Filter out accepted objects.
# (!) Convert unknown into strings - then inspect.
object.inspect
end
end
end # module TT::Javascript
end
-150
View File
@@ -1,150 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'javascript.rb'
# Sortable Hash that preserves the insertion order.
# Prints out JSON strings of the content.
#
# Based of Bill Kelly's InsertOrderPreservingHash
#
# @see http://www.ruby-forum.com/topic/166075#728764
#
# @since 2.4.0
module SpeckleConnector
class TT::JSON
include Enumerable
# @since 2.4.0
def initialize(*args, &block)
if args.size == 1 && args[0].is_a?(Hash)
@h = args[0].dup
@ordered_keys = @h.keys
else
@h = Hash.new(*args, &block)
@ordered_keys = []
end
end
# @since 2.4.0
def initialize_copy(source)
super
@h = @h.dup
@ordered_keys = @ordered_keys.dup
end
# @since 2.4.0
def []=(key, val)
@ordered_keys << key unless @h.has_key? key
@h[key] = val
end
# @since 2.4.0
def each
@ordered_keys.each {|k| yield(k, @h[k])}
end
alias :each_pair :each
# @since 2.4.0
def each_value
@ordered_keys.each {|k| yield(@h[k])}
end
# @since 2.4.0
def each_key
@ordered_keys.each {|k| yield k}
end
# @since 2.4.0
def key?(key)
@h.key?(key)
end
alias :has_key? :key?
alias :include? :key?
alias :member? :key?
# @since 2.4.0
def keys
@ordered_keys
end
# @since 2.4.0
def values
@ordered_keys.map {|k| @h[k]}
end
# @since 2.4.0
def clear
@ordered_keys.clear
@h.clear
end
# @since 2.4.0
def delete(k, &block)
@ordered_keys.delete k
@h.delete(k, &block)
end
# @since 2.4.0
def reject!
del = []
each_pair {|k,v| del << k if yield k,v}
del.each {|k| delete k}
del.empty? ? nil : self
end
# @since 2.4.0
def delete_if(&block)
reject!(&block)
self
end
# @since 2.4.0
def merge!(hash)
hash.each { |key, value|
if @h.key?(key)
@h[key] = value
else
self[key] = value
end
}
end
#%w(merge!).each do |name|
# define_method(name) do |*args|
# raise NotImplementedError, "#{name} not implemented"
# end
#end
# @since 2.4.0
def method_missing(*args)
@h.send(*args)
end
# @return [Hash]
# @since 2.4.0
def to_hash
@h.dup
end
# Compile JSON Hash into a string.
#
# @since 2.4.0
def to_s(format=false)
arr = self.map { |k,v|
key = ( k.is_a?(Symbol) ) ? k.to_s.inspect : k.inspect
value = TT::Javascript.to_js(v, format)
"#{key}: #{value}"
}
str = (format) ? arr.join(",\n\t") : arr.join(", ")
return (format) ? "{\n\t#{str}\n}\n" : "{#{str}}"
end
alias :inspect :to_s
end # class TT::JSON
end
@@ -1,33 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
# Collection of Length methods.
#
# @since 2.7.0
module SpeckleConnector
module TT::Length
# @param [Length] length
# @param [Length] snap
#
# @return [Length]
# @since 2.7.0
def self.snap( length, snap )
return length.to_l if snap.zero?
diff = length % snap
if diff > snap / 2.0
new_length = length - diff + snap
else
new_length = length - diff
end
new_length.to_l
end
end # module TT::Length
end
@@ -1,12 +0,0 @@
The tt_api.so file in this folder is a recompiled version of api.so from the
Win32Utils API library.
Original version: http://win32utils.rubyforge.org/ ( Artistic License 2.0 )
This modified version is simply rewrapped under my own namespace:
TT::Win32::API
No other changes made. Please do not redistrubute, use the original version
instead as this version would be of no use for anyone else.
-Thomas Thomassen (www.thomthom.net)
-124
View File
@@ -1,124 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
module SpeckleConnector
# Collection of Group, ComponentInstnace and Image methods.
#
# @since 2.4.0
module TT::Locale
# Some locale settings uses the comma as decimal separator. .to_f does not
# account for this, so all commas must be coverted to periods.
#
# @param [String] string
#
# @return [Float]
# @since 2.4.0
def self.string_to_float(string)
# Dirty hack to get the current locale's decimal separator, which is then
# replaced in the string to a period which is what the .to_f method expects.
@decimal_separator ||= self.decimal_separator
string.tr(@decimal_separator, '.').to_f
end
# Returns a string with the decimal separator in the user's locale and
# in the precision of the model units.
#
# @param [Float] float
# @param [Integer] precision (Added: 2.7.0)
#
# @return [String]
# @since 2.4.0
def self.float_to_string( float, precision = nil )
@decimal_separator ||= self.decimal_separator
if precision
sprintf( "%.#{precision}f", float ).tr!( '.', @decimal_separator )
else
float.to_s.tr!( '.', @decimal_separator )
end
end
# Formats the given float to a string with the user's locale decimal delmitor
# and with the precision given in the model's option for lengths.
#
# @param [Float] float
# @param [Sketchup::Model] model
#
# @return [String]
# @since 2.4.0
def self.format_float(float, model)
@decimal_separator ||= self.decimal_separator
precision = model.options['UnitsOptions']['LengthPrecision']
num = sprintf("%.#{precision}f", float)
if num.to_f != float
num = "~ #{num}"
end
num.tr!('.', @decimal_separator)
num
end
# Casts +string+ to +type+s class.
#
# @param [String] string
# @param [Mixed] type Class or an instance of an class.
# @param [String] array_split
#
# @return [Mixed]
# @since 2.4.0
def self.cast_string(string, type, array_split = ',')
type = type.new if type.class == Class
if string == 'null'
value = nil
elsif type.is_a?( Integer )
value = string.to_i
elsif type.is_a?( Float ) && !type.is_a?( Length )
value = self.string_to_float(string)
elsif type.is_a?( Length )
value = string.to_l
elsif type.is_a?( TrueClass ) || type.is_a?( FalseClass )
value = ( string.downcase == 'true' ) ? true : false
elsif type.is_a?( Array )
value = string.split( array_split )
else
value = string
end
value
end
# Hack to determine if the user's locale uses comma or periodas decimal
# separator. Makes the assumption that if it's not period, then is is
# comma. Yields incorrect result if the user has some other obscure
# separator.
#
# @return [String]
# @since 2.4.0
def self.decimal_separator
# If this raises an error the decimal separator is not '.'
'1.2'.to_l
return '.'
rescue
return ','
end
# @note Currently makes the assumption that locales with comma as decimal
# uses semi-colon and locales with period as decimal uses comma.
#
# @return [String]
# @since 2.6.0
def self.list_separator
( self.decimal_separator == '.' ) ? ',' : ';'
end
end # module TT::Locale
end
@@ -1,180 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'system.rb'
module SpeckleConnector
# Collection of Material methods.
#
# @since 2.5.0
module TT::Material
# When the user clicks on a material in the materials browser
# +model.materials.current+ will return a material that does not exist in the
# model. It is possible to apply this material to entities in the model, but
# it will evetually BugSplat.
#
# This method checks if a given material exist in the model and is safe to use.
#
# @param [Sketchup::Material] material
# @param [Sketchup::Model] model
#
# @return [Boolean]
# @since 2.5.0
def self.in_model?( material, model = Sketchup.active_model )
#model.materials.any? { |m| m == material }
# This is probably just as good to write. Is this wrapper method needed?
model.materials.include?( material )
end
# Because SketchUp will bugsplat if a material selected from the library is
# used this method attempts to add the material to the model.
#
# @note Do not use within a start_operation block. This method uses a
# temporary operation which will break any already initiated operations.
#
# @param [Sketchup::Model] model
#
# @return [Sketchup::Material]
# @since 2.7.0
def self.get_current( model = Sketchup.active_model )
materials = model.materials
m = materials.current
return m if m.nil?
# Check if material already exists. If it does - reuse it.
name = m.name
if x = materials[ name ]
if x.color.to_i == m.color.to_i && x.alpha == m.alpha
# No texture applied.
if x.texture.nil? && m.texture.nil?
materials.current = x
return x
end
x_basename = File.basename( x.texture.filename )
m_basename = File.basename( m.texture.filename )
if m_basename == m.texture.filename
# If the texture only have a filename - not a path.
if x_basename == m.texture.filename &&
x.texture.width == m.texture.width &&
x.texture.height == m.texture.height
materials.current = x
return x
end
else
# If the texture only have a filename with full path.
if x.texture.filename == m.texture.filename &&
x.texture.width == m.texture.width &&
x.texture.height == m.texture.height
materials.current = x
return x
end
end
else
end
end
# Transfer name and colour.
new_material = materials.add( name )
new_material.color = m.color
new_material.alpha = m.alpha
# Any textures require special attention. If the filename contains a valid
# path to an existing file nothing special needs to be done.
#
# But if the filename refers to a non-existing file it needs to be written
# out to a temp file. This is where things become a bit risky and hacky.
# Because TextureWriter doesn't accept Material objects the orphan material
# needs to be temporarily applied to the model (risky).
#
# Materials from SketchUp's default library only contains the name of the
# file without any paths. This is because the texture is located only within
# the.skm.
if m.texture
filename = m.texture.filename
if File.exist?( filename )
new_material.texture = filename
else
# Create temp file to write the texture to.
temp_path = TT::System.temp_path
temp_folder = File.join( temp_path, 'tt_su_tmp_mtl' )
temp_filename = File.basename( filename )
temp_file = File.join( temp_folder, temp_filename )
unless File.exist?( temp_folder )
Dir.mkdir( temp_folder )
end
# Create temp group with the orphan material and write it out.
#
# Wrap within start_operation and clean up with abort_operation so it
# doesn't end up in the undo stack.
#
# (!) This means this method should not occur within any other
# start_operation blocks - as operations cannot be nested.
tw = Sketchup.create_texture_writer
model.start_operation( 'Extract Orphan Material' ) # rubocop:disable SketchupPerformance/OperationDisableUI
begin
g = model.entities.add_group # rubocop:disable SketchupSuggestions/ModelEntities
g.material = m
tw.load( g )
tw.write( g, temp_file )
ensure
model.abort_operation
end
# Load texture to material and clean up.
new_material.texture = temp_file
File.delete( temp_file )
end
new_material.texture.size = [ m.texture.width, m.texture.height ]
end
materials.current = new_material
new_material
end
# Safely removes a material from a model.
#
# @param [Sketchup::Material] material
# @param [Sketchup::Model] model
#
# @return [Boolean]
# @since 2.5.0
def self.remove( material, model = Sketchup.active_model )
# SketchUp 8.0M1 introduced model.materials.remove, which turned out to be
# bugged. It didn't remove the material from the entities in the model -
# leaving the model with rouge invalid materials.
# To work around this all entities are processed before the method is called.
# The workaround for older versions also require this to be done.
for e in model.entities # rubocop:disable SketchupSuggestions/ModelEntities
e.material = nil if e.respond_to?( :material ) && e.material == material
e.back_material = nil if e.respond_to?( :back_material ) && e.back_material == material
end
for d in model.definitions
next if d.image?
for e in d.entities
e.material = nil if e.respond_to?( :material ) && e.material == material
e.back_material = nil if e.respond_to?( :back_material ) && e.back_material == material
end
end
materials = model.materials
if materials.respond_to?( :remove )
materials.remove( material )
else
# Workaround for SketchUp versions older than 8.0M1. Add all materials
# except the one to be removed to temporary groups and purge the materials.
temp_group = model.entities.add_group # rubocop:disable SketchupSuggestions/ModelEntities
for m in model.materials
next if m == material
g = temp_group.add_group
g.material = material
end
materials.purge_unused
temp_group.erase!
true
end
end
end # module TT::Material
end
@@ -1,78 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
module SpeckleConnector
# @example
# module Foo
# extend TT::MetaClass
# cattr :bar
# end
#
# @since 2.7.0
module TT::MetaClass
# @since 2.7.0
def metaclass
class << self
self
end
end
# @since 2.7.0
def cattr_accessor( *args )
metaclass.class_eval {
attr_accessor( *args )
}
end
alias :cattr :cattr_accessor
# @since 2.7.0
def cattr_reader( *args )
metaclass.class_eval {
attr_reader( *args )
}
end
# @since 2.7.0
def cattr_writer( *args )
metaclass.class_eval {
attr_writer( *args )
}
end
# @since 2.7.0
def cbattr_accessor( *args )
metaclass.class_eval {
attr_accessor( *args )
for attribute in args
question = "#{attribute}?".to_sym
alias_method( question, attribute )
remove_method( attribute )
end
}
end
alias :cbattr :cbattr_accessor
# @since 2.7.0
def cbattr_reader( *args )
metaclass.class_eval {
attr_reader( *args )
for attribute in args
question = "#{attribute}?".to_sym
alias_method( question, attribute )
remove_method( attribute )
end
}
end
# @since 2.7.0
alias :cbattr_writer :cattr_writer
end # module TT::MetaClass
end
@@ -1,131 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'gui.rb'
require_relative 'sketchup.rb'
module SpeckleConnector
# Creates a wrapper class that simulates a modal environment for the window. It
# is not truly modal because of limitations of the API under OSX, but uses a tool
# class to prevent the user from manipulating the model while the window is open.
# If the user should activate another tool it acts as if the user used the Close
# or cancel button.
#
# @todo Prevent other Windows from opening (?)
#
# @since 2.4.0
class TT::GUI::ModalWrapper
@@open_window = nil
# @param [TT::GUI::Window] window
#
# @since 2.4.0
def initialize(window)
@window = window
end
# @private
# @since 2.4.0
def activate
#puts "T:activate - #{@@open_window}"
Sketchup.active_model.active_view.invalidate
@@open_window = @window
@window.show_window
end
# @private
# @since 2.4.0
def deactivate(view)
#puts 'T:deactivate'
#puts "> visible? #{@window.visible?}"
#puts "> closing? #{@window.closing?}"
@window.close unless @window.closing?
@@open_window = nil
view.invalidate
end
# @private
# @since 2.5.0
def resume(view)
view.invalidate
end
# @private
# @since 2.4.0
def onLButtonDown(flags, x, y, view)
UI.beep
@window.bring_to_front
end
# @private
# @since 2.4.0
def getMenu(menu)
# Suppress the context menu
menu.add_item('Close Dialog') {
@window.close
}
end
# @private
# @since 2.4.0
def onCancel(reason, view)
#puts "T:onCancel: reason #{reason.to_s}"
end
if TT::SketchUp::support?( TT::SketchUp::COLOR_ALPHA )
# Dim the SketchUp viewport while the modal window is open.
# @private
# @since 2.5.0
def draw(view)
pts = [
[0,0,0],
[view.vpwidth,0,0],
[view.vpwidth,view.vpheight,0],
[0,view.vpheight,0]
]
view.drawing_color = Sketchup::Color.new(0,0,0,128)
view.draw2d( GL_QUADS, pts )
end
end
### Public Methods ###
# Displays the modal window as long as there are no other modal windows open.
# It only handles +TT::GUI::Window+ objects that uses the +ModalWrapper+ class.
#
# @since 2.4.0
def show
#puts 'T:show'
@closing = false
if @@open_window
UI.beep
@@open_window.bring_to_front
else
Sketchup.active_model.tools.push_tool( self )
end
end
# Closes the modal window.
#
# @since 2.4.0
def close
#puts 'T:close'
#puts caller.join("\r\n")
# Prevent popping the tool multiple times. This worked fine in older SketchUp
# versions, but regressed in SU2014 where it would then cause a crash.
return false if @closing
@closing = true
Sketchup.active_model.tools.pop_tool
true
end
end # class TT::GUI::ModalWrapper
end
-163
View File
@@ -1,163 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'sketchup.rb'
module SpeckleConnector
# Collection of Model methods.
#
# @since 2.0.0
module TT::Model
# Version safe +model.start_operation+ wrapper.
#
# @param [String] name
# @param [Sketchup::Model] model - defaults to active model.
#
# @return [Boolean]
# @since 2.0.0
def self.start_operation(name, model = Sketchup.active_model)
if TT::SketchUp.version[0] >= 7
model.start_operation(name, true)
else
model.start_operation(name) # rubocop:disable SketchupPerformance/OperationDisableUI
end
end
# Counts all unique +Sketchup::Entities+ collections in the given model,
# excluding Image definitions.
#
# @param [Sketchup::Model] model
# @param [Boolean] only_used_definitions
# @param [Hash] options
#
# @return [Integer]
# @since 2.5.0
def self.count_unique_entities( model, only_used_definitions=true, options={} )
skip_locked = options[:locked] && options[:locked].is_a?( Hash )
if only_used_definitions
model.definitions.reject { |d|
d.image? ||
d.count_instances == 0 ||
(skip_locked && options[:locked].key?(d))
}.length + 1
else
model.definitions.reject { |d|
d.image? ||
(skip_locked && options[:locked].key?(d))
}.length + 1
end
end
# Counts all unique entities in the given model, including sub-entities.
#
# @param [Sketchup::Model] model
# @param [Boolean] only_used_definitions
# @param [Hash] options
#
# @return [Integer]
# @since 2.5.0
def self.count_unique_entity( model, only_used_definitions=true, options={} )
skip_locked = options[:locked] && options[:locked].is_a?( Hash )
c = model.entities.length # rubocop:disable SketchupSuggestions/ModelEntities
for d in model.definitions
next if d.image?
next if only_used_definitions && d.count_instances == 0
next if skip_locked && options[:locked].key?(d)
c += d.entities.count
end
c
end
# Yields each unique Entities collection recursivly.
#
# TT::Model.each_entities { |entities|
# processEntities( entities )
# }
#
# If a number is returned to the processing block it will be used to add up a
# total when +each_entities+ returns.
#
# TT::Model.each_entities { |entities|
# c = 0
# for e in entities
# c += 1 if e.is_a?( SketchUp::Edge )
# end
# c
# }
#
# This example will return the total number of edges processed. Use to keep
# statistic for the iteration.
#
# @param [Sketchup::Model] model
# @param [Boolean] only_used_definitions
# @param [Hash] options
#
# @yield [entities]
# @yieldparam [Enumerable|Sketchup::Entities] entities
#
# @return [Integer] Returns
# @since 2.5.0
def self.each_entities( model, only_used_definitions=true, options={} )
skip_locked = options[:locked] && options[:locked].is_a?( Hash )
c = 0
result = yield( model.entities ) # rubocop:disable SketchupSuggestions/ModelEntities
c += result if result.is_a?( Numeric )
for d in model.definitions
next if d.image?
next if only_used_definitions && d.count_instances == 0
next if skip_locked && options[:locked].key?(d)
result += yield( d.entities )
c += result if result.is_a?( Numeric )
end
c
end
# Yields all the entities in the model. Note that it does not traverse the
# model hierarchy.
#
# When +only_used_definitions+ is true, only entities that is used in the model
# is yielded. When +only_used_definitions+ is false, also the entiites in
# unused definitions are yielded.
#
# @param [Sketchup::Model] model
# @param [Boolean] only_used_definitions
# @param [Hash] options
#
# @yield [entity]
# @yieldparam [Sketchup::Entity] entity
#
# @return [Integer] Number of entities which returned non-false.
# @since 2.5.0
def self.each_entity( model, only_used_definitions=true, options={} )
skip_locked = options[:locked] && options[:locked].is_a?( Hash )
# c = c.next is faster than c += 1
# http://forums.sketchucation.com/viewtopic.php?f=180&t=25305&p=305810#p305810
c = 0
for e in model.entities.to_a # # rubocop:disable SketchupSuggestions/ModelEntities
next if skip_locked && e.respond_to?( :locked? ) && e.locked?
c = c.next if yield( e )
end
for d in model.definitions
next if d.image?
next if only_used_definitions && d.count_instances == 0
next if skip_locked && options[:locked].key?(d)
for e in d.entities.to_a
next if skip_locked && e.respond_to?( :locked? ) && e.locked?
c = c.next if yield( e )
end
end
c
end
end # module TT::Model
end
@@ -1,127 +0,0 @@
#-----------------------------------------------------------------------------
#
# Thomas Thomassen
# thomas[at]thomthom[dot]net
#
#-----------------------------------------------------------------------------
require_relative 'core.rb'
require_relative 'point3d_ex.rb'
module SpeckleConnector
# Collection of Point3d methods.
#
# @since 2.0.0
module TT::Point3d
# Checks if point +c+ is between point +a+ and +b+.
#
# Return true if +c+ is on +a+ or +b+.
#
# @param [Geom::Point3d] a
# @param [Geom::Point3d] b
# @param [Geom::Point3d] c
# @param [Geom::Point3d] on_point - When +true+, if point +c+ is at the same
# position as +a+ or +b+ it is considered to be in between.
#
# @return [Boolean]
# @since 2.0.0
def self.between?(a, b, c, on_point = true)
return false unless c.on_line?([a,b])
v1 = c.vector_to(a)
v2 = c.vector_to(b)
if on_point
return true if !v1.valid? || !v2.valid?
else
return false if !v1.valid? || !v2.valid?
end
!v1.samedirection?(v2)
end
# Implementation of the Douglas-Peucker algorithm.
#
# http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
# http://en.wiki.mcneel.com/default.aspx/McNeel/PolylineSimplification.html
#
# @param [Array<Geom::Point3d>] points Point set to be simplified. No Loop!
# @param [Length] epsilon Maximin deviance from original curve.
#
# @return [Array<Geom::Point3d>]
# @since 2.5.0
def self.douglas_peucker(points, epsilon)
return points if points.length < 3
# Find the point with the maximum distance
dmax = 0
index = 0
line = [points.first, points.last]
1.upto(points.length - 2) { |i|
d = points[i].distance_to_line(line)
if d > dmax
index = i
dmax = d
end
}
# If max distance is greater than epsilon, recursively simplify
result = []
if dmax >= epsilon
# Recursive call
recResults1 = self.douglas_peucker(points[0..index], epsilon)
recResults2 = self.douglas_peucker(points[index...points.length], epsilon)
# Build the result list
result = recResults1[0...-1] + recResults2
else
result = [points.first, points.last]
end
return result
end
# Extends all the points (+Geom::Point3d+ and +Array+ in +points+) with the
# +TT::Point3d_Ex+ mix-in module.
#
# All +Array+ objects that represent a 3d point will be converted into
# +Geom::Point3d+ before being extended.
#
# @param [Array<Geom::Point3d>] points
#
# @return [Array<TT::Point3d_Ex>] Geom::Point3d objects extended by TT::Point3d_Ex
# @since 2.5.0
def self.extend_all( points )
extended_points = []
for point in points
if point.is_a?( Geom::Point3d )
point_ex = point
elsif point.is_a?( Array )
next unless point.size == 3 && point.all? { |n| n.is_a?( Numeric ) }
point_ex = Geom::Point3d.new( point.x, point.y, point.z )
elsif point.respond_to?( :position )
position = point.position
point_ex = position if position.is_a?( Geom::Point3d )
end
point_ex.extend( TT::Point3d_Ex ) unless point_ex.is_a?( TT::Point3d_Ex )
extended_points << point_ex
end
extended_points
end
# Wrapper of the Douglas-Peucker algorithm. Handles looping curves.
#
# @param [Array<Geom::Point3d>] points Point set to be simplified. No Loop!
# @param [Length] epsilon Maximin deviance from original curve.
#
# @return [Array<Geom::Point3d>]
# @since 2.5.0
def self.simplify_curve(points, epsilon)
if points.first == points.last # Detect loop
points.pop
simplified_curve = self.douglas_peucker(points, epsilon)
simplified_curve << points.first
else
simplified_curve = self.douglas_peucker(points, epsilon)
end
end
end # module TT::Point3d
end

Some files were not shown because too many files have changed in this diff Show More