diff --git a/skp2ocli/README.md b/skp2ocli/README.md
new file mode 100644
index 0000000..3e9ee71
--- /dev/null
+++ b/skp2ocli/README.md
@@ -0,0 +1,73 @@
+# OGREE TOOL: Sketchup to OGREE Converter Plugin
+
+This is a guide for the plugin OGREE Converter for Sketchup PRO 2023
+
+- [OGREE TOOL: Sketchup to OGREE Converter Plugin](#ogree-tool-sketchup-to-ogree-converter-plugin)
+ - [Usage](#usage)
+ - [Compile](#compile)
+ - [Installation](#installation)
+ - [Remove](#remove)
+ - [Code details?](#code-details)
+
+
+## Usage
+After installing the plugin, you simply:
+* Select the entity that you want to know its details
+* Open the drop down menu **Extension** and click **Generate OCLI files** like shown in the image below
+
+
+## Compile
+The plugin is written in Ruby, but in order for plugins to be registrable in Sketchup PRO 2023, you **must** compile the code to a **.rbz** compressed file.
+
+To do that, you must :
+1. Create a **.zip** compressed file that contains all the **.rb** files of the plugin
+2. Change the compressed file extension to **.rbz**
+
+*Optional*: You can use the batch script below to automate the compilation of the plugin
+```bash
+@echo off
+
+rem Define the directory to zip, a directory that contains the implementation of the plugin
+set "dir_to_zip=skp2ocli"
+
+rem Define the file to include, a file that contains the declaration and metadata of the plugins
+set "file_to_include=skp2ocli.rb"
+
+rem Build the zip file
+set "zip_name=skp2ocli.zip"
+if exist "skp2ocli.rbz" (
+ del /q "skp2ocli.rbz"
+ echo Deleted existing zip file: skp2ocli.rbz
+)
+PowerShell -ExecutionPolicy Bypass -NoProfile -Command "& { Compress-Archive -Path %dir_to_zip%* -DestinationPath %zip_name%}"echo Created zip file: %zip_name%
+rem Rename the temporary zip to the desired name
+ren "%zip_name%" "skp2ocli.rbz"
+
+echo renamed zip file: skp2ocli.rbz
+
+pause
+```
+
+*NB*: The output file will be saved in the directory where the script was executed
+
+## Installation
+1. You must click on Extension in the menu
+
+2. From the dropdown menu, click Extension Manager
+
+3. From the new windows that pops up, click on Install Extension
+
+
+
+
+## Remove
+To remove the plugin, simply follow the steps below
+1. You must click on Extension in the menu
+
+2. From the dropdown menu, click Extension Manager
+
+3. Click on Manage to change tab
+
+
+## Code details?
+To get details about the code you can check directly the source code which is well documented and you can also check directory **[html page](./doc/index.html)** which contains a webpage that document the different functions and classes implemented.
diff --git a/skp2ocli/create_rbz.bat b/skp2ocli/create_rbz.bat
new file mode 100644
index 0000000..4c3d9d2
--- /dev/null
+++ b/skp2ocli/create_rbz.bat
@@ -0,0 +1,24 @@
+@echo off
+
+rem Define the directory to zip (replace "mydirectory" with your actual directory)
+set "dir_to_zip=skp2ocli"
+
+rem Define the file to include (replace "myfile.txt" with your actual filename)
+set "file_to_include=skp2ocli.rb"
+
+rem Get the current directory (where the script is executed)
+set "script_dir=%~dp0"
+
+rem Build the zip filename based on directory name and timestamp
+set "zip_name=skp2ocli.zip"
+if exist "skp2ocli.rbz" (
+ del /q "skp2ocli.rbz"
+ echo Deleted existing zip file: skp2ocli.rbz
+)
+PowerShell -ExecutionPolicy Bypass -NoProfile -Command "& { Compress-Archive -Path %dir_to_zip%* -DestinationPath %zip_name%}"echo Created zip file: %zip_name%
+rem Rename the temporary zip to the desired name
+ren "%zip_name%" "skp2ocli.rbz"
+
+echo renamed zip file: skp2ocli.rbz
+
+pause
\ No newline at end of file
diff --git a/skp2ocli/doc/Skp2ocli.html b/skp2ocli/doc/Skp2ocli.html
new file mode 100644
index 0000000..545a60a
--- /dev/null
+++ b/skp2ocli/doc/Skp2ocli.html
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+module Skp2ocli - RDoc Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ module Skp2ocli
+
+
+
+
+
+
+
+
+
+
diff --git a/skp2ocli/doc/Skp2ocli/OCliExport.html b/skp2ocli/doc/Skp2ocli/OCliExport.html
new file mode 100644
index 0000000..2dd41a1
--- /dev/null
+++ b/skp2ocli/doc/Skp2ocli/OCliExport.html
@@ -0,0 +1,580 @@
+
+
+
+
+
+
+module Skp2ocli::OCliExport - RDoc Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ module Skp2ocli::OCliExport
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .angle_between_vectors (vector1 , vector2 )
+ dot_product = vector1 .dot (vector2 )
+ magnitudes = vector1 .length * vector2 .length
+ Math .acos (dot_product / magnitudes ).radians
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .calculate_total_edge_length (entity )
+ case entity
+ when Sketchup :: Group , Sketchup :: ComponentInstance
+ entity .definition .entities .grep (Sketchup :: Edge ).map { | e | e .length .to_i }
+ when Sketchup :: Face
+ entity .edges .map { | edge | edge .length .to_i }
+ when Sketchup :: Edge
+ [entity .length .to_i ]
+ when Sketchup :: Entities
+ entity .grep (Sketchup :: Edge ).map { | e | e .length .to_i }
+ else
+ []
+ end
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .check_intersection (selected_entity , all_entities )
+ all_entities .reject { | entity | entity == selected_entity || entity .parent == selected_entity }
+ .select { | entity | entities_intersect? (selected_entity , entity ) }
+ .uniq
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .check_right_angles (entity )
+ face = get_face (entity )
+ return nil unless face .is_a? (Sketchup :: Face )
+ return nil unless face .edges .length == 4
+
+ vertices = face .vertices
+ return nil unless vertices .length == 4
+
+ vectors = vertices .each_with_index .map do | vertex , i |
+ next_vertex = vertices [(i + 1 ) % 4 ]
+ vertex .position .vector_to (next_vertex .position )
+ end
+
+ angles = vectors .each_with_index .map do | vector , i |
+ next_vector = vectors [(i + 1 ) % 4 ]
+ angle_between_vectors (vector , next_vector )
+ end
+
+ angles .all? { | angle | (90 - angle ).abs < TOLERANCE }
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .entities_intersect? (entity1 , entity2 )
+ entity1 .bounds .intersect (entity2 .bounds ).valid?
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .export (data , path )
+ File .open (path , "w" ) do | file |
+
+ file .write (JSON .pretty_generate (data ))
+ puts "Data exported successfully to #{path}"
+ end
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .face_and_adjacent_are_squares? (face )
+ return false unless is_square? (face )
+ adjacent_faces = face .edges .flat_map (& :faces ).uniq - [face ]
+ adjacent_faces .all? { | adjacent_face | is_square? (adjacent_face ) }
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .get_entity_coordinates (entity , transform )
+ origin = Geom :: Point3d .new (0 , 0 , 0 )
+ local_origin = origin .transform (transform )
+ global_origin = entity .transformation * origin
+ {
+ local: local_origin .to_a .map (& :to_i ),
+ global: global_origin .to_a .map (& :to_i )
+ }
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .get_entity_details (child_entity )
+ details = {
+ "type": child_entity .typename ,
+ }
+ case child_entity
+ when Sketchup :: Face
+ details ["points" ] = child_entity .vertices .map (& :position ).map (& :to_a )
+ details ["material" ] = child_entity .material ? child_entity .material .name : nil
+ when Sketchup :: Edge
+ details ["start_point" ] = {
+ "x": child_entity .start .position .x ,
+ "y": child_entity .start .position .y ,
+ "z": child_entity .start .position .z ,
+ }
+ details ["end_point" ] = {
+ "x": child_entity .end .position .x ,
+ "y": child_entity .end .position .y ,
+ "z": child_entity .end .position .z ,
+ }
+ details ["length" ] = child_entity .length
+
+ when Sketchup :: Group
+ details ["material" ] = child_entity .material ? child_entity .material .name : nil
+ entities_details = []
+ child_entity .definition .entities .each do | child_entity |
+
+
+ child_details = get_entity_details (child_entity )
+
+ entities_details << child_details
+ end
+ details ["entities" ] = entities_details
+ end
+ return details
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .get_entity_identifier (entity )
+ entity_tag = entity .layer .display_name
+ entity_definition = entity .definition .name if entity .respond_to? (:definition )
+ "Definition: #{entity_definition}, Tag: #{entity_tag}"
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .get_face (entity )
+ case entity
+ when Sketchup :: Face then entity
+ when Sketchup :: Edge then entity .faces .first
+ when Sketchup :: Vertex then entity .edges .map (& :faces ).flatten .uniq .first
+ else nil
+ end
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def self .is_square? (face )
+ return false unless face .is_a? (Sketchup :: Face ) && face .edges .length == 4
+ vertices = face .vertices
+ return false unless vertices .length == 4
+
+ vectors = vertices .each_with_index .map do | vertex , i |
+ next_vertex = vertices [(i + 1 ) % 4 ]
+ vertex .position .vector_to (next_vertex .position )
+ end
+
+ lengths = vectors .map (& :length )
+ lengths .uniq .length == 1
+end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/skp2ocli/doc/created.rid b/skp2ocli/doc/created.rid
new file mode 100644
index 0000000..6f62537
--- /dev/null
+++ b/skp2ocli/doc/created.rid
@@ -0,0 +1,2 @@
+Fri, 19 Jul 2024 15:08:51 +0200
+main.rb Fri, 19 Jul 2024 15:08:41 +0200
diff --git a/skp2ocli/doc/css/fonts.css b/skp2ocli/doc/css/fonts.css
new file mode 100644
index 0000000..57302b5
--- /dev/null
+++ b/skp2ocli/doc/css/fonts.css
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/),
+ * with Reserved Font Name "Source". All Rights Reserved. Source is a
+ * trademark of Adobe Systems Incorporated in the United States and/or other
+ * countries.
+ *
+ * This Font Software is licensed under the SIL Open Font License, Version
+ * 1.1.
+ *
+ * This license is copied below, and is also available with a FAQ at:
+ * http://scripts.sil.org/OFL
+ */
+
+@font-face {
+ font-family: "Source Code Pro";
+ font-style: normal;
+ font-weight: 400;
+ src: local("Source Code Pro"),
+ local("SourceCodePro-Regular"),
+ url("../fonts/SourceCodePro-Regular.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Source Code Pro";
+ font-style: normal;
+ font-weight: 700;
+ src: local("Source Code Pro Bold"),
+ local("SourceCodePro-Bold"),
+ url("../fonts/SourceCodePro-Bold.ttf") format("truetype");
+}
+
+/*
+ * Copyright (c) 2010, Ćukasz Dziedzic (dziedzic@typoland.com),
+ * with Reserved Font Name Lato.
+ *
+ * This Font Software is licensed under the SIL Open Font License, Version
+ * 1.1.
+ *
+ * This license is copied below, and is also available with a FAQ at:
+ * http://scripts.sil.org/OFL
+ */
+
+@font-face {
+ font-family: "Lato";
+ font-style: normal;
+ font-weight: 300;
+ src: local("Lato Light"),
+ local("Lato-Light"),
+ url("../fonts/Lato-Light.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Lato";
+ font-style: italic;
+ font-weight: 300;
+ src: local("Lato Light Italic"),
+ local("Lato-LightItalic"),
+ url("../fonts/Lato-LightItalic.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Lato";
+ font-style: normal;
+ font-weight: 700;
+ src: local("Lato Regular"),
+ local("Lato-Regular"),
+ url("../fonts/Lato-Regular.ttf") format("truetype");
+}
+
+@font-face {
+ font-family: "Lato";
+ font-style: italic;
+ font-weight: 700;
+ src: local("Lato Italic"),
+ local("Lato-Italic"),
+ url("../fonts/Lato-RegularItalic.ttf") format("truetype");
+}
+
+/*
+ * -----------------------------------------------------------
+ * SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+ * -----------------------------------------------------------
+ *
+ * PREAMBLE
+ * The goals of the Open Font License (OFL) are to stimulate worldwide
+ * development of collaborative font projects, to support the font creation
+ * efforts of academic and linguistic communities, and to provide a free and
+ * open framework in which fonts may be shared and improved in partnership
+ * with others.
+ *
+ * The OFL allows the licensed fonts to be used, studied, modified and
+ * redistributed freely as long as they are not sold by themselves. The
+ * fonts, including any derivative works, can be bundled, embedded,
+ * redistributed and/or sold with any software provided that any reserved
+ * names are not used by derivative works. The fonts and derivatives,
+ * however, cannot be released under any other type of license. The
+ * requirement for fonts to remain under this license does not apply
+ * to any document created using the fonts or their derivatives.
+ *
+ * DEFINITIONS
+ * "Font Software" refers to the set of files released by the Copyright
+ * Holder(s) under this license and clearly marked as such. This may
+ * include source files, build scripts and documentation.
+ *
+ * "Reserved Font Name" refers to any names specified as such after the
+ * copyright statement(s).
+ *
+ * "Original Version" refers to the collection of Font Software components as
+ * distributed by the Copyright Holder(s).
+ *
+ * "Modified Version" refers to any derivative made by adding to, deleting,
+ * or substituting -- in part or in whole -- any of the components of the
+ * Original Version, by changing formats or by porting the Font Software to a
+ * new environment.
+ *
+ * "Author" refers to any designer, engineer, programmer, technical
+ * writer or other person who contributed to the Font Software.
+ *
+ * PERMISSION & CONDITIONS
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of the Font Software, to use, study, copy, merge, embed, modify,
+ * redistribute, and sell modified and unmodified copies of the Font
+ * Software, subject to the following conditions:
+ *
+ * 1) Neither the Font Software nor any of its individual components,
+ * in Original or Modified Versions, may be sold by itself.
+ *
+ * 2) Original or Modified Versions of the Font Software may be bundled,
+ * redistributed and/or sold with any software, provided that each copy
+ * contains the above copyright notice and this license. These can be
+ * included either as stand-alone text files, human-readable headers or
+ * in the appropriate machine-readable metadata fields within text or
+ * binary files as long as those fields can be easily viewed by the user.
+ *
+ * 3) No Modified Version of the Font Software may use the Reserved Font
+ * Name(s) unless explicit written permission is granted by the corresponding
+ * Copyright Holder. This restriction only applies to the primary font name as
+ * presented to the users.
+ *
+ * 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+ * Software shall not be used to promote, endorse or advertise any
+ * Modified Version, except to acknowledge the contribution(s) of the
+ * Copyright Holder(s) and the Author(s) or with their explicit written
+ * permission.
+ *
+ * 5) The Font Software, modified or unmodified, in part or in whole,
+ * must be distributed entirely under this license, and must not be
+ * distributed under any other license. The requirement for fonts to
+ * remain under this license does not apply to any document created
+ * using the Font Software.
+ *
+ * TERMINATION
+ * This license becomes null and void if any of the above conditions are
+ * not met.
+ *
+ * DISCLAIMER
+ * THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+ * OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+ * DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+ * OTHER DEALINGS IN THE FONT SOFTWARE.
+ */
+
diff --git a/skp2ocli/doc/css/rdoc.css b/skp2ocli/doc/css/rdoc.css
new file mode 100644
index 0000000..1be815f
--- /dev/null
+++ b/skp2ocli/doc/css/rdoc.css
@@ -0,0 +1,662 @@
+/*
+ * "Darkfish" Rdoc CSS
+ * $Id: rdoc.css 54 2009-01-27 01:09:48Z deveiant $
+ *
+ * Author: Michael Granger
+ *
+ */
+
+/* vim: ft=css et sw=2 ts=2 sts=2 */
+/* Base Green is: #6C8C22 */
+
+.hide { display: none !important; }
+
+* { padding: 0; margin: 0; }
+
+body {
+ background: #fafafa;
+ font-family: Lato, sans-serif;
+ font-weight: 300;
+}
+
+h1 span,
+h2 span,
+h3 span,
+h4 span,
+h5 span,
+h6 span {
+ position: relative;
+
+ display: none;
+ padding-left: 1em;
+ line-height: 0;
+ vertical-align: baseline;
+ font-size: 10px;
+}
+
+h1 span { top: -1.3em; }
+h2 span { top: -1.2em; }
+h3 span { top: -1.0em; }
+h4 span { top: -0.8em; }
+h5 span { top: -0.5em; }
+h6 span { top: -0.5em; }
+
+h1:hover span,
+h2:hover span,
+h3:hover span,
+h4:hover span,
+h5:hover span,
+h6:hover span {
+ display: inline;
+}
+
+h1:target,
+h2:target,
+h3:target,
+h4:target,
+h5:target,
+h6:target {
+ margin-left: -10px;
+ border-left: 10px solid #f1edba;
+}
+
+:link,
+:visited {
+ color: #6C8C22;
+ text-decoration: none;
+}
+
+:link:hover,
+:visited:hover {
+ border-bottom: 1px dotted #6C8C22;
+}
+
+code,
+pre {
+ font-family: "Source Code Pro", Monaco, monospace;
+ background-color: rgba(27,31,35,0.05);
+ padding: 0em 0.2em;
+ border-radius: 0.2em;
+}
+
+table {
+ margin: 0;
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+
+table tr th, table tr td {
+ padding: 0.2em 0.4em;
+ border: 1px solid #ccc;
+}
+
+table tr th {
+ background-color: #eceaed;
+}
+
+table tr:nth-child(even) td {
+ background-color: #f5f4f6;
+}
+
+/* @group Generic Classes */
+
+.initially-hidden {
+ display: none;
+}
+
+#search-field {
+ width: 98%;
+ background: white;
+ border: none;
+ height: 1.5em;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ text-align: left;
+}
+#search-field:focus {
+ background: #f1edba;
+}
+#search-field:-moz-placeholder,
+#search-field::-webkit-input-placeholder {
+ font-weight: bold;
+ color: #666;
+}
+
+.missing-docs {
+ font-size: 120%;
+ background: white url(../images/wrench_orange.png) no-repeat 4px center;
+ color: #ccc;
+ line-height: 2em;
+ border: 1px solid #d00;
+ opacity: 1;
+ padding-left: 20px;
+ text-indent: 24px;
+ letter-spacing: 3px;
+ font-weight: bold;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+}
+
+.target-section {
+ border: 2px solid #dcce90;
+ border-left-width: 8px;
+ padding: 0 1em;
+ background: #fff3c2;
+}
+
+/* @end */
+
+/* @group Index Page, Standalone file pages */
+.table-of-contents ul {
+ margin: 1em;
+ list-style: none;
+}
+
+.table-of-contents ul ul {
+ margin-top: 0.25em;
+}
+
+.table-of-contents ul :link,
+.table-of-contents ul :visited {
+ font-size: 16px;
+}
+
+.table-of-contents li {
+ margin-bottom: 0.25em;
+}
+
+.table-of-contents li .toc-toggle {
+ width: 16px;
+ height: 16px;
+ background: url(../images/add.png) no-repeat;
+}
+
+.table-of-contents li .toc-toggle.open {
+ background: url(../images/delete.png) no-repeat;
+}
+
+/* @end */
+
+/* @group Top-Level Structure */
+
+nav {
+ float: left;
+ width: 260px;
+ font-family: Helvetica, sans-serif;
+ font-size: 14px;
+ border-right: 1px solid #ccc;
+ position: sticky;
+ top: 0;
+ overflow: auto;
+ height: calc(100vh - 100px); /* reduce the footer height */
+}
+
+main {
+ display: block;
+ margin: 0 2em 5em 260px;
+ padding-left: 20px;
+ min-width: 340px;
+ font-size: 16px;
+}
+
+main h1,
+main h2,
+main h3,
+main h4,
+main h5,
+main h6 {
+ font-family: Helvetica, sans-serif;
+}
+
+.table-of-contents main {
+ margin-left: 2em;
+}
+
+#validator-badges {
+ clear: both;
+ margin: 1em 1em 2em;
+ font-size: smaller;
+}
+
+/* @end */
+
+/* @group navigation */
+nav {
+ margin-bottom: 1em;
+}
+
+nav .nav-section {
+ margin-top: 2em;
+ border-top: 2px solid #aaa;
+ font-size: 90%;
+ overflow: hidden;
+}
+
+nav h2 {
+ margin: 0;
+ padding: 2px 8px 2px 8px;
+ background-color: #e8e8e8;
+ color: #555;
+ font-size: 125%;
+ text-align: center;
+}
+
+nav h3,
+#table-of-contents-navigation {
+ margin: 0;
+ padding: 2px 8px 2px 8px;
+ text-align: right;
+ background-color: #e8e8e8;
+ color: #555;
+}
+
+nav ul,
+nav dl,
+nav p {
+ padding: 4px 8px 0;
+ list-style: none;
+}
+
+#project-navigation .nav-section {
+ margin: 0;
+ border-top: 0;
+}
+
+#home-section h2 {
+ text-align: center;
+}
+
+#table-of-contents-navigation {
+ font-size: 1.2em;
+ font-weight: bold;
+ text-align: center;
+}
+
+#search-section {
+ margin-top: 0;
+ border-top: 0;
+}
+
+#search-field-wrapper {
+ border-top: 1px solid #aaa;
+ border-bottom: 1px solid #aaa;
+ padding: 3px 8px;
+ background-color: #e8e8e8;
+ color: #555;
+}
+
+ul.link-list li {
+ white-space: nowrap;
+ line-height: 1.4em;
+}
+
+ul.link-list .type {
+ font-size: 8px;
+ text-transform: uppercase;
+ color: white;
+ background: #969696;
+ padding: 2px 4px;
+ -webkit-border-radius: 5px;
+}
+
+dl.note-list dt {
+ float: left;
+ margin-right: 1em;
+}
+
+.calls-super {
+ background: url(../images/arrow_up.png) no-repeat right center;
+}
+
+.nav-section details summary {
+ display: block;
+}
+
+.nav-section details summary::-webkit-details-marker {
+ display: none;
+}
+
+.nav-section details summary:before {
+ content: "";
+}
+
+.nav-section details summary:after {
+ content: " \25B6"; /* BLACK RIGHT-POINTING TRIANGLE */
+}
+.nav-section details[open] > summary:after {
+ content: " \25BD"; /* WHITE DOWN-POINTING TRIANGLE */
+}
+
+/* @end */
+
+/* @group Documentation Section */
+main {
+ color: #333;
+}
+
+main > h1:first-child,
+main > h2:first-child,
+main > h3:first-child,
+main > h4:first-child,
+main > h5:first-child,
+main > h6:first-child {
+ margin-top: 0px;
+}
+
+main sup {
+ vertical-align: super;
+ font-size: 0.8em;
+}
+
+/* The heading with the class name */
+main h1[class] {
+ margin-top: 0;
+ margin-bottom: 1em;
+ font-size: 2em;
+ color: #6C8C22;
+}
+
+main h1 {
+ margin: 2em 0 0.5em;
+ font-size: 1.7em;
+}
+
+main h2 {
+ margin: 2em 0 0.5em;
+ font-size: 1.5em;
+}
+
+main h3 {
+ margin: 2em 0 0.5em;
+ font-size: 1.2em;
+}
+
+main h4 {
+ margin: 2em 0 0.5em;
+ font-size: 1.1em;
+}
+
+main h5 {
+ margin: 2em 0 0.5em;
+ font-size: 1em;
+}
+
+main h6 {
+ margin: 2em 0 0.5em;
+ font-size: 1em;
+}
+
+main p {
+ margin: 0 0 0.5em;
+ line-height: 1.4em;
+}
+
+main pre {
+ margin: 1.2em 0.5em;
+ padding: 1em;
+ font-size: 0.8em;
+}
+
+main hr {
+ margin: 1.5em 1em;
+ border: 2px solid #ddd;
+}
+
+main blockquote {
+ margin: 0 2em 1.2em 1.2em;
+ padding-left: 0.5em;
+ border-left: 2px solid #ddd;
+}
+
+main ol,
+main ul {
+ margin: 1em 2em;
+}
+
+main li > p {
+ margin-bottom: 0.5em;
+}
+
+main dl {
+ margin: 1em 0.5em;
+}
+
+main dt {
+ margin-bottom: 0.5em;
+ font-weight: bold;
+}
+
+main dd {
+ margin: 0 1em 1em 0.5em;
+}
+
+main header h2 {
+ margin-top: 2em;
+ border-width: 0;
+ border-top: 4px solid #bbb;
+ font-size: 130%;
+}
+
+main header h3 {
+ margin: 2em 0 1.5em;
+ border-width: 0;
+ border-top: 3px solid #bbb;
+ font-size: 120%;
+}
+
+.documentation-section-title {
+ position: relative;
+}
+.documentation-section-title .section-click-top {
+ position: absolute;
+ top: 6px;
+ left: 12px;
+ font-size: 10px;
+ color: #9b9877;
+ visibility: hidden;
+ padding-left: 0.5px;
+}
+
+.documentation-section-title:hover .section-click-top {
+ visibility: visible;
+}
+
+.constants-list > dl {
+ margin: 1em 0 2em;
+ border: 0;
+}
+
+.constants-list > dl dt {
+ margin-bottom: 0.75em;
+ padding-left: 0;
+ font-family: "Source Code Pro", Monaco, monospace;
+ font-size: 110%;
+}
+
+.constants-list > dl dt a {
+ color: inherit;
+}
+
+.constants-list > dl dd {
+ margin: 0 0 2em 0;
+ padding: 0;
+ color: #666;
+}
+
+.documentation-section h2 {
+ position: relative;
+}
+
+.documentation-section h2 a {
+ position: absolute;
+ top: 8px;
+ right: 10px;
+ font-size: 12px;
+ color: #9b9877;
+ visibility: hidden;
+}
+
+.documentation-section h2:hover a {
+ visibility: visible;
+}
+
+/* @group Method Details */
+
+main .method-source-code {
+ max-height: 0;
+ overflow: auto;
+ transition-duration: 200ms;
+ transition-delay: 0ms;
+ transition-property: all;
+ transition-timing-function: ease-in-out;
+}
+
+main .method-source-code.active-menu {
+ max-height: 100vh;
+}
+
+main .method-description .method-calls-super {
+ color: #333;
+ font-weight: bold;
+}
+
+main .method-detail {
+ margin-bottom: 2.5em;
+ cursor: pointer;
+}
+
+main .method-detail:target {
+ margin-left: -10px;
+ border-left: 10px solid #f1edba;
+}
+
+main .method-heading {
+ position: relative;
+ font-family: "Source Code Pro", Monaco, monospace;
+ font-size: 110%;
+ font-weight: bold;
+ color: #333;
+}
+main .method-heading :link,
+main .method-heading :visited {
+ color: inherit;
+}
+main .method-click-advice {
+ position: absolute;
+ top: 2px;
+ right: 5px;
+ font-size: 12px;
+ color: #9b9877;
+ visibility: hidden;
+ padding-right: 20px;
+ line-height: 20px;
+ background: url(../images/zoom.png) no-repeat right top;
+}
+main .method-heading:hover .method-click-advice {
+ visibility: visible;
+}
+
+main .method-alias .method-heading {
+ color: #666;
+}
+
+main .method-description,
+main .aliases {
+ margin-top: 0.75em;
+ color: #333;
+}
+
+main .aliases {
+ padding-top: 4px;
+ font-style: italic;
+ cursor: default;
+}
+main .method-description ul {
+ margin-left: 1.5em;
+}
+
+main #attribute-method-details .method-detail:hover {
+ background-color: transparent;
+ cursor: default;
+}
+main .attribute-access-type {
+ text-transform: uppercase;
+ padding: 0 1em;
+}
+/* @end */
+
+/* @end */
+
+/* @group Source Code */
+
+pre {
+ margin: 0.5em 0;
+ border: 1px dashed #999;
+ padding: 0.5em;
+ background: #262626;
+ color: white;
+ overflow: auto;
+}
+
+.ruby-constant { color: #7fffd4; background: transparent; }
+.ruby-keyword { color: #00ffff; background: transparent; }
+.ruby-ivar { color: #eedd82; background: transparent; }
+.ruby-operator { color: #00ffee; background: transparent; }
+.ruby-identifier { color: #ffdead; background: transparent; }
+.ruby-node { color: #ffa07a; background: transparent; }
+.ruby-comment { color: #dc0000; background: transparent; }
+.ruby-regexp { color: #ffa07a; background: transparent; }
+.ruby-value { color: #7fffd4; background: transparent; }
+
+/* @end */
+
+
+/* @group search results */
+#search-results {
+ font-family: Lato, sans-serif;
+ font-weight: 300;
+}
+
+#search-results .search-match {
+ font-family: Helvetica, sans-serif;
+ font-weight: normal;
+}
+
+#search-results .search-selected {
+ background: #e8e8e8;
+ border-bottom: 1px solid transparent;
+}
+
+#search-results li {
+ list-style: none;
+ border-bottom: 1px solid #aaa;
+ margin-bottom: 0.5em;
+}
+
+#search-results li:last-child {
+ border-bottom: none;
+ margin-bottom: 0;
+}
+
+#search-results li p {
+ padding: 0;
+ margin: 0.5em;
+}
+
+#search-results .search-namespace {
+ font-weight: bold;
+}
+
+#search-results li em {
+ background: yellow;
+ font-style: normal;
+}
+
+#search-results pre {
+ margin: 0.5em;
+ font-family: "Source Code Pro", Monaco, monospace;
+}
+
+/* @end */
+
diff --git a/skp2ocli/doc/fonts/Lato-Light.ttf b/skp2ocli/doc/fonts/Lato-Light.ttf
new file mode 100644
index 0000000..b49dd43
Binary files /dev/null and b/skp2ocli/doc/fonts/Lato-Light.ttf differ
diff --git a/skp2ocli/doc/fonts/Lato-LightItalic.ttf b/skp2ocli/doc/fonts/Lato-LightItalic.ttf
new file mode 100644
index 0000000..7959fef
Binary files /dev/null and b/skp2ocli/doc/fonts/Lato-LightItalic.ttf differ
diff --git a/skp2ocli/doc/fonts/Lato-Regular.ttf b/skp2ocli/doc/fonts/Lato-Regular.ttf
new file mode 100644
index 0000000..839cd58
Binary files /dev/null and b/skp2ocli/doc/fonts/Lato-Regular.ttf differ
diff --git a/skp2ocli/doc/fonts/Lato-RegularItalic.ttf b/skp2ocli/doc/fonts/Lato-RegularItalic.ttf
new file mode 100644
index 0000000..bababa0
Binary files /dev/null and b/skp2ocli/doc/fonts/Lato-RegularItalic.ttf differ
diff --git a/skp2ocli/doc/fonts/SourceCodePro-Bold.ttf b/skp2ocli/doc/fonts/SourceCodePro-Bold.ttf
new file mode 100644
index 0000000..dd00982
Binary files /dev/null and b/skp2ocli/doc/fonts/SourceCodePro-Bold.ttf differ
diff --git a/skp2ocli/doc/fonts/SourceCodePro-Regular.ttf b/skp2ocli/doc/fonts/SourceCodePro-Regular.ttf
new file mode 100644
index 0000000..1decfb9
Binary files /dev/null and b/skp2ocli/doc/fonts/SourceCodePro-Regular.ttf differ
diff --git a/skp2ocli/doc/images/add.png b/skp2ocli/doc/images/add.png
new file mode 100644
index 0000000..6332fef
Binary files /dev/null and b/skp2ocli/doc/images/add.png differ
diff --git a/skp2ocli/doc/images/arrow_up.png b/skp2ocli/doc/images/arrow_up.png
new file mode 100644
index 0000000..1ebb193
Binary files /dev/null and b/skp2ocli/doc/images/arrow_up.png differ
diff --git a/skp2ocli/doc/images/brick.png b/skp2ocli/doc/images/brick.png
new file mode 100644
index 0000000..7851cf3
Binary files /dev/null and b/skp2ocli/doc/images/brick.png differ
diff --git a/skp2ocli/doc/images/brick_link.png b/skp2ocli/doc/images/brick_link.png
new file mode 100644
index 0000000..9ebf013
Binary files /dev/null and b/skp2ocli/doc/images/brick_link.png differ
diff --git a/skp2ocli/doc/images/bug.png b/skp2ocli/doc/images/bug.png
new file mode 100644
index 0000000..2d5fb90
Binary files /dev/null and b/skp2ocli/doc/images/bug.png differ
diff --git a/skp2ocli/doc/images/bullet_black.png b/skp2ocli/doc/images/bullet_black.png
new file mode 100644
index 0000000..5761970
Binary files /dev/null and b/skp2ocli/doc/images/bullet_black.png differ
diff --git a/skp2ocli/doc/images/bullet_toggle_minus.png b/skp2ocli/doc/images/bullet_toggle_minus.png
new file mode 100644
index 0000000..b47ce55
Binary files /dev/null and b/skp2ocli/doc/images/bullet_toggle_minus.png differ
diff --git a/skp2ocli/doc/images/bullet_toggle_plus.png b/skp2ocli/doc/images/bullet_toggle_plus.png
new file mode 100644
index 0000000..9ab4a89
Binary files /dev/null and b/skp2ocli/doc/images/bullet_toggle_plus.png differ
diff --git a/skp2ocli/doc/images/date.png b/skp2ocli/doc/images/date.png
new file mode 100644
index 0000000..783c833
Binary files /dev/null and b/skp2ocli/doc/images/date.png differ
diff --git a/skp2ocli/doc/images/delete.png b/skp2ocli/doc/images/delete.png
new file mode 100644
index 0000000..08f2493
Binary files /dev/null and b/skp2ocli/doc/images/delete.png differ
diff --git a/skp2ocli/doc/images/find.png b/skp2ocli/doc/images/find.png
new file mode 100644
index 0000000..1547479
Binary files /dev/null and b/skp2ocli/doc/images/find.png differ
diff --git a/skp2ocli/doc/images/loadingAnimation.gif b/skp2ocli/doc/images/loadingAnimation.gif
new file mode 100644
index 0000000..82290f4
Binary files /dev/null and b/skp2ocli/doc/images/loadingAnimation.gif differ
diff --git a/skp2ocli/doc/images/macFFBgHack.png b/skp2ocli/doc/images/macFFBgHack.png
new file mode 100644
index 0000000..c6473b3
Binary files /dev/null and b/skp2ocli/doc/images/macFFBgHack.png differ
diff --git a/skp2ocli/doc/images/package.png b/skp2ocli/doc/images/package.png
new file mode 100644
index 0000000..da3c2a2
Binary files /dev/null and b/skp2ocli/doc/images/package.png differ
diff --git a/skp2ocli/doc/images/page_green.png b/skp2ocli/doc/images/page_green.png
new file mode 100644
index 0000000..de8e003
Binary files /dev/null and b/skp2ocli/doc/images/page_green.png differ
diff --git a/skp2ocli/doc/images/page_white_text.png b/skp2ocli/doc/images/page_white_text.png
new file mode 100644
index 0000000..813f712
Binary files /dev/null and b/skp2ocli/doc/images/page_white_text.png differ
diff --git a/skp2ocli/doc/images/page_white_width.png b/skp2ocli/doc/images/page_white_width.png
new file mode 100644
index 0000000..1eb8809
Binary files /dev/null and b/skp2ocli/doc/images/page_white_width.png differ
diff --git a/skp2ocli/doc/images/plugin.png b/skp2ocli/doc/images/plugin.png
new file mode 100644
index 0000000..6187b15
Binary files /dev/null and b/skp2ocli/doc/images/plugin.png differ
diff --git a/skp2ocli/doc/images/ruby.png b/skp2ocli/doc/images/ruby.png
new file mode 100644
index 0000000..f763a16
Binary files /dev/null and b/skp2ocli/doc/images/ruby.png differ
diff --git a/skp2ocli/doc/images/tag_blue.png b/skp2ocli/doc/images/tag_blue.png
new file mode 100644
index 0000000..3f02b5f
Binary files /dev/null and b/skp2ocli/doc/images/tag_blue.png differ
diff --git a/skp2ocli/doc/images/tag_green.png b/skp2ocli/doc/images/tag_green.png
new file mode 100644
index 0000000..83ec984
Binary files /dev/null and b/skp2ocli/doc/images/tag_green.png differ
diff --git a/skp2ocli/doc/images/transparent.png b/skp2ocli/doc/images/transparent.png
new file mode 100644
index 0000000..d665e17
Binary files /dev/null and b/skp2ocli/doc/images/transparent.png differ
diff --git a/skp2ocli/doc/images/wrench.png b/skp2ocli/doc/images/wrench.png
new file mode 100644
index 0000000..5c8213f
Binary files /dev/null and b/skp2ocli/doc/images/wrench.png differ
diff --git a/skp2ocli/doc/images/wrench_orange.png b/skp2ocli/doc/images/wrench_orange.png
new file mode 100644
index 0000000..565a933
Binary files /dev/null and b/skp2ocli/doc/images/wrench_orange.png differ
diff --git a/skp2ocli/doc/images/zoom.png b/skp2ocli/doc/images/zoom.png
new file mode 100644
index 0000000..908612e
Binary files /dev/null and b/skp2ocli/doc/images/zoom.png differ
diff --git a/skp2ocli/doc/index.html b/skp2ocli/doc/index.html
new file mode 100644
index 0000000..a675df9
--- /dev/null
+++ b/skp2ocli/doc/index.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+RDoc Documentation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+This is the API documentation for RDoc Documentation.
+
+
+
+
+
diff --git a/skp2ocli/doc/js/darkfish.js b/skp2ocli/doc/js/darkfish.js
new file mode 100644
index 0000000..d0c9467
--- /dev/null
+++ b/skp2ocli/doc/js/darkfish.js
@@ -0,0 +1,84 @@
+/**
+ *
+ * Darkfish Page Functions
+ * $Id: darkfish.js 53 2009-01-07 02:52:03Z deveiant $
+ *
+ * Author: Michael Granger
+ *
+ */
+
+/* Provide console simulation for firebug-less environments */
+/*
+if (!("console" in window) || !("firebug" in console)) {
+ var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
+ "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
+
+ window.console = {};
+ for (var i = 0; i < names.length; ++i)
+ window.console[names[i]] = function() {};
+};
+*/
+
+
+function showSource( e ) {
+ var target = e.target;
+ while (!target.classList.contains('method-detail')) {
+ target = target.parentNode;
+ }
+ if (typeof target !== "undefined" && target !== null) {
+ target = target.querySelector('.method-source-code');
+ }
+ if (typeof target !== "undefined" && target !== null) {
+ target.classList.toggle('active-menu')
+ }
+};
+
+function hookSourceViews() {
+ document.querySelectorAll('.method-heading').forEach(function (codeObject) {
+ codeObject.addEventListener('click', showSource);
+ });
+};
+
+function hookSearch() {
+ var input = document.querySelector('#search-field');
+ var result = document.querySelector('#search-results');
+ result.classList.remove("initially-hidden");
+
+ var search_section = document.querySelector('#search-section');
+ search_section.classList.remove("initially-hidden");
+
+ var search = new Search(search_data, input, result);
+
+ search.renderItem = function(result) {
+ var li = document.createElement('li');
+ var html = '';
+
+ // TODO add relative path to
+
+
+
+
+
+
+
+
+
+
+
+
+
+Table of Contents - RDoc Documentation
+
+
+Classes and Modules
+
+
+Methods
+
+
+
+
+
+
diff --git a/skp2ocli/img/installation-1.png b/skp2ocli/img/installation-1.png
new file mode 100644
index 0000000..d8386a5
Binary files /dev/null and b/skp2ocli/img/installation-1.png differ
diff --git a/skp2ocli/img/installation-2.png b/skp2ocli/img/installation-2.png
new file mode 100644
index 0000000..2889000
Binary files /dev/null and b/skp2ocli/img/installation-2.png differ
diff --git a/skp2ocli/img/installation-3.png b/skp2ocli/img/installation-3.png
new file mode 100644
index 0000000..13565e7
Binary files /dev/null and b/skp2ocli/img/installation-3.png differ
diff --git a/skp2ocli/img/remove-3.png b/skp2ocli/img/remove-3.png
new file mode 100644
index 0000000..ed136dd
Binary files /dev/null and b/skp2ocli/img/remove-3.png differ
diff --git a/skp2ocli/img/remove-4.png b/skp2ocli/img/remove-4.png
new file mode 100644
index 0000000..01837cd
Binary files /dev/null and b/skp2ocli/img/remove-4.png differ
diff --git a/skp2ocli/img/usage-1.png b/skp2ocli/img/usage-1.png
new file mode 100644
index 0000000..0a0d092
Binary files /dev/null and b/skp2ocli/img/usage-1.png differ
diff --git a/skp2ocli/skp2ocli.rb b/skp2ocli/skp2ocli.rb
new file mode 100644
index 0000000..ed99335
--- /dev/null
+++ b/skp2ocli/skp2ocli.rb
@@ -0,0 +1,35 @@
+# This Plugin will automatically generate a JSON file containing the
+# details about the components of the Sketchup Model
+#
+# * sketchup.rb is needed for `file_loaded?` and `file_loaded`.
+#
+# * extensions.rb is needed for the `SketchupExtension` class.
+
+require "sketchup.rb"
+require "extensions.rb"
+
+module Skp2ocli
+ module OCliExport
+ # The use of `file_loaded?` here is to prevent the extension from being
+ # registered multiple times. This can happen for a number of reasons when
+ # the file is reloaded - either when debugging during development or
+ # extension updates etc.
+ #
+ # The `__FILE__` constant is a "magic" Ruby constant that returns a string
+ # with the path to the current file. You don't have to use this constant
+ # with `file_loaded?` - you can use any unique string to represent this
+ # file. But `__FILE__` is very convenient for this.
+ unless file_loaded?(__FILE__)
+ ex = SketchupExtension.new('Generate OCLI files', 'skp2ocli/main')
+ ex.description = "Generate OCLI file that works with OGREE from Sketchup model."
+ ex.version = "0.0.1"
+ ex.copyright = "#{ex.creator} 2024"
+ ex.creator = "Kais BETTAIEB"
+ Sketchup.register_extension(ex, true)
+
+ # This is needed for the load guard to prevent the extension being
+ # registered multiple times.
+ file_loaded(__FILE__)
+ end
+ end #end OCliExport
+end # end Skp2ocli
\ No newline at end of file
diff --git a/skp2ocli/skp2ocli.rbz b/skp2ocli/skp2ocli.rbz
new file mode 100644
index 0000000..ee958d8
Binary files /dev/null and b/skp2ocli/skp2ocli.rbz differ
diff --git a/skp2ocli/skp2ocli/main.rb b/skp2ocli/skp2ocli/main.rb
new file mode 100644
index 0000000..d0cd10d
--- /dev/null
+++ b/skp2ocli/skp2ocli/main.rb
@@ -0,0 +1,272 @@
+require 'sketchup.rb'
+require 'extensions.rb'
+
+module Skp2ocli
+ module OCliExport
+ TOLERANCE = 1.0 # Define TOLERANCE constant
+
+ def self.check_right_angles(entity)
+ face = get_face(entity)
+ return nil unless face.is_a?(Sketchup::Face)
+ return nil unless face.edges.length == 4
+
+ vertices = face.vertices
+ return nil unless vertices.length == 4
+
+ vectors = vertices.each_with_index.map do |vertex, i|
+ next_vertex = vertices[(i + 1) % 4]
+ vertex.position.vector_to(next_vertex.position)
+ end
+
+ angles = vectors.each_with_index.map do |vector, i|
+ next_vector = vectors[(i + 1) % 4]
+ angle_between_vectors(vector, next_vector)
+ end
+
+ angles.all? { |angle| (90 - angle).abs < TOLERANCE }
+ end
+
+ def self.get_face(entity)
+ case entity
+ when Sketchup::Face then entity
+ when Sketchup::Edge then entity.faces.first
+ when Sketchup::Vertex then entity.edges.map(&:faces).flatten.uniq.first
+ else nil
+ end
+ end
+
+ def self.angle_between_vectors(vector1, vector2)
+ dot_product = vector1.dot(vector2)
+ magnitudes = vector1.length * vector2.length
+ Math.acos(dot_product / magnitudes).radians
+ end
+
+ def self.is_square?(face)
+ return false unless face.is_a?(Sketchup::Face) && face.edges.length == 4
+ vertices = face.vertices
+ return false unless vertices.length == 4
+
+ vectors = vertices.each_with_index.map do |vertex, i|
+ next_vertex = vertices[(i + 1) % 4]
+ vertex.position.vector_to(next_vertex.position)
+ end
+
+ lengths = vectors.map(&:length)
+ lengths.uniq.length == 1
+ end
+
+ def self.face_and_adjacent_are_squares?(face)
+ return false unless is_square?(face)
+ adjacent_faces = face.edges.flat_map(&:faces).uniq - [face]
+ adjacent_faces.all? { |adjacent_face| is_square?(adjacent_face) }
+ end
+
+ def self.calculate_total_edge_length(entity)
+ case entity
+ when Sketchup::Group, Sketchup::ComponentInstance
+ entity.definition.entities.grep(Sketchup::Edge).map { |e| e.length.to_i }
+ when Sketchup::Face
+ entity.edges.map { |edge| edge.length.to_i }
+ when Sketchup::Edge
+ [entity.length.to_i]
+ when Sketchup::Entities
+ entity.grep(Sketchup::Edge).map { |e| e.length.to_i }
+ else
+ []
+ end
+ end
+
+ def self.entities_intersect?(entity1, entity2)
+ entity1.bounds.intersect(entity2.bounds).valid?
+ end
+
+ def self.check_intersection(selected_entity, all_entities)
+ all_entities.reject { |entity| entity == selected_entity || entity.parent == selected_entity }
+ .select { |entity| entities_intersect?(selected_entity, entity) }
+ .uniq
+ end
+
+ def self.get_entity_coordinates(entity, transform)
+ origin = Geom::Point3d.new(0, 0, 0)
+ local_origin = origin.transform(transform)
+ global_origin = entity.transformation * origin
+ {
+ local: local_origin.to_a.map(&:to_i),
+ global: global_origin.to_a.map(&:to_i)
+ }
+ end
+
+ def self.get_entity_identifier(entity)
+ entity_tag = entity.layer.display_name
+ entity_definition = entity.definition.name if entity.respond_to?(:definition)
+ "Definition: #{entity_definition}, Tag: #{entity_tag}"
+ end
+
+ def self.get_entity_details(child_entity)
+ details = {
+ "type": child_entity.typename,
+ }
+ case child_entity
+ when Sketchup::Face
+ details["points"] = child_entity.vertices.map(&:position).map(&:to_a) # Get vertex positions as arrays
+ details["material"] = child_entity.material ? child_entity.material.name : nil # Material name (if available)
+ when Sketchup::Edge
+ details["start_point"] = {
+ "x": child_entity.start.position.x,
+ "y":child_entity.start.position.y,
+ "z":child_entity.start.position.z,
+ } # Start point position
+ details["end_point"] = {
+ "x": child_entity.end.position.x,
+ "y":child_entity.end.position.y,
+ "z":child_entity.end.position.z,
+ } # End point position
+ details["length"] = child_entity.length
+ # Add cases for other geometry types (arc, solid, etc.) with their specific details
+ when Sketchup::Group
+ details["material"] = child_entity.material ? child_entity.material.name : nil # Material name (if available)
+ entities_details = []
+ child_entity.definition.entities.each do |child_entity|
+ # Get details of each child entity
+ # here error below
+ child_details = get_entity_details(child_entity)
+ # Get the component's origin (outside the loop)
+ entities_details << child_details
+ end
+ details["entities"] = entities_details
+ end
+ return details
+ end
+
+ def self.extract_children_entities(entity)
+ # return empty list if no children
+ definition = entity.definition
+ if not definition.entities
+ return []
+ else
+ entities_details = []
+ definition.entities.each do |child_entity|
+ # Get details of each child entity
+ entities_details << get_entity_details(child_entity)
+ end
+ return entities_details
+ end
+ end
+
+ def self.extract_model_objects(model)
+ # Initialize empty array to store component data
+ objects = []
+ model.entities.each do |entity|
+ # Check if entity is a component instance
+ next unless entity.is_a?(Sketchup::ComponentInstance) or entity.is_a?(Sketchup::Group)
+
+ # Get the component definition
+ definition = entity.definition
+
+ # Collect component details
+ component_data = {
+ "name": definition.name,
+ "description": definition.description,
+ "exact_position": entity.transformation.origin, # Get transformation for component instance
+ }
+ component_data["position"] = {
+ "x":entity.transformation.origin.x,
+ "y":entity.transformation.origin.y,
+ "z":entity.transformation.origin.z
+ }
+ # get children entities
+ component_data["entities"] = extract_children_entities(entity)
+ # Store component data
+ objects << component_data
+ end
+ return objects
+ end
+
+ def self.export(data, path)
+ File.open(path, "w") do |file|
+ # Write the JSON data to the file
+ file.write(JSON.pretty_generate(data))
+ puts "Data exported successfully to #{path}"
+ end
+ end
+
+ unless file_loaded?(__FILE__)
+ menu = UI.menu('Plugins')
+ menu.add_item('Generate OCLI files') {
+ model = Sketchup.active_model
+ selection = model.selection
+ entities = model.entities
+
+ if selection.empty?
+ UI.messagebox("Please select a single face!")
+ return
+ end
+
+ selected_entity = selection.first
+ angles = check_right_angles(selected_entity)
+
+ if angles.nil?
+ puts "Failed to check object 3D shape"
+ else
+ total_length = calculate_total_edge_length(selected_entity)
+ puts "Total length of all edges: #{total_length}"
+
+ if angles && total_length.uniq.size == 1
+ puts "All edges of the selected entity are equal and all angles are 90 degrees. A cube"
+ #UI.messagebox("Cube")
+ elsif angles && total_length.uniq.size != 1
+ puts "Edges of the selected entity are not all equal but all angles are 90 degrees. A cuboid"
+ #UI.messagebox("Cuboid")
+ else
+ puts "Edges of the selected entity are not all equal and all the angles are not 90 degrees"
+ #UI.messagebox("Unknown")
+ end
+ end
+
+ parent_component = selected_entity
+ while parent_component.parent.is_a?(Sketchup::Entities)
+ parent_component = parent_component.parent
+ end
+
+ unless parent_component.is_a?(Sketchup::ComponentInstance) || parent_component.is_a?(Sketchup::Group)
+ UI.messagebox("Please select an entity within a component or group.")
+ return
+ end
+
+ selected_entity_identifier = get_entity_identifier(parent_component)
+
+ all_entities = entities.grep(Sketchup::Group) + entities.grep(Sketchup::ComponentInstance)
+ intersecting_entities = check_intersection(parent_component, all_entities)
+
+ if intersecting_entities.empty?
+ puts "No intersections found with the selected entity."
+ else
+ puts "##################"
+ intersecting_entities.each do |entity|
+ intersecting_entity_identifier = get_entity_identifier(entity)
+
+ transform = entity.transformation.inverse * parent_component.transformation
+
+ coords = get_entity_coordinates(parent_component, transform)
+
+ puts "Selected entity (#{selected_entity_identifier}):"
+ puts " Global coordinates (origin 0,0,0): #{coords[:global]}"
+ puts " Local coordinates relative to intersecting entity (#{intersecting_entity_identifier}): #{coords[:local]}"
+ end
+ puts "#{intersecting_entities.size} intersecting entities found."
+ puts "##################"
+ end
+
+ ## below implementation to export data in json format
+ ## Create the JSON hash with additional information (optional)
+ ## Uncomment the following lines if you wan to generate and export data in JSON format
+ # exportable = { "model_name": model.name, "entities": extract_model_objects(model) }
+ # puts exportable
+ # filename = "diameters.json"
+ # output_path = File.join("