{"id":1548,"date":"2025-06-02T17:28:52","date_gmt":"2025-06-02T17:28:52","guid":{"rendered":"https:\/\/www.shotland.co.il\/3dgarage\/?page_id=1548"},"modified":"2025-06-22T13:14:56","modified_gmt":"2025-06-22T13:14:56","slug":"stl-to-gcode","status":"publish","type":"page","link":"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/","title":{"rendered":"Stl to Gcode"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"1548\" class=\"elementor elementor-1548\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1ea383d e-con-full e-flex e-con e-parent\" data-id=\"1ea383d\" data-element_type=\"container\" data-core-v316-plus=\"true\">\n\t\t\t\t<div class=\"elementor-element elementor-element-281abd0 elementor-widget__width-inherit elementor-widget elementor-widget-html\" data-id=\"281abd0\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n<script async src=\"https:\/\/pagead2.googlesyndication.com\/pagead\/js\/adsbygoogle.js?client=ca-pub-8501120542539638\"\r\n     crossorigin=\"anonymous\"><\/script>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <title>Online 3d slicer<\/title>\r\n    <style>\r\n        * {\r\n            margin: 0;\r\n            padding: 0;\r\n            box-sizing: border-box;\r\n        }\r\n\r\n        body {\r\n            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\r\n            background: linear-gradient(135deg, #ffffff 0%, #ffffff 100%);\r\n            min-height: 100vh;\r\n            color: #333;\r\n        }\r\n\r\n        .app-container {\r\n            display: flex;\r\n            height: 100vh;\r\n            overflow: hidden;\r\n        }\r\n\r\n        .sidebar {\r\n            width: 350px;\r\n            background: rgba(255, 255, 255, 0.95);\r\n            backdrop-filter: blur(10px);\r\n            padding: 20px;\r\n            overflow-y: auto;\r\n            box-shadow: 2px 0 20px rgba(0, 0, 0, 0.1);\r\n        }\r\n\r\n        .main-content {\r\n            flex: 1;\r\n            display: flex;\r\n            flex-direction: column;\r\n        }\r\n\r\n        .toolbar {\r\n            background: rgba(255, 255, 255, 0.9);\r\n            padding: 15px 20px;\r\n            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\r\n            display: flex;\r\n            justify-content: space-between;\r\n            align-items: center;\r\n        }\r\n\r\n        .viewer-area {\r\n            flex: 1;\r\n            position: relative;\r\n            background: #1a1a2e;\r\n        }\r\n\r\n        .status-bar {\r\n            background: rgba(0, 0, 0, 0.8);\r\n            color: white;\r\n            padding: 10px 20px;\r\n            font-size: 12px;\r\n            display: flex;\r\n            justify-content: space-between;\r\n        }\r\n\r\n        h1 {\r\n            color: #ce2554;\r\n            margin-bottom: 20px;\r\n            font-size: 1.8em;\r\n            text-align: center;\r\n        }\r\n\r\n        .section {\r\n            background: white;\r\n            border-radius: 10px;\r\n            padding: 20px;\r\n            margin-bottom: 20px;\r\n            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);\r\n        }\r\n\r\n        .section h3 {\r\n            color: #333;\r\n            margin-bottom: 15px;\r\n            padding-bottom: 8px;\r\n            border-bottom: 2px solid #d3189a;\r\n            \r\n        }\r\n\r\n        \/* .file-upload {\r\n            border: 3px dashed #4facfe;\r\n            border-radius: 10px;\r\n            padding: 30px;\r\n            text-align: center;\r\n            cursor: pointer;\r\n            transition: all 0.3s ease;\r\n            background: linear-gradient(135deg, #f8fbff 0%, #e8f4fd 100%);\r\n        } *\/\r\n         .file-upload {\r\n    border: 3px dashed #000000;\r\n    border-radius: 10px;\r\n    padding: 30px;\r\n    text-align: center;\r\n    cursor: pointer;\r\n    transition: all 0.3s ease;\r\n    background: linear-gradient(135deg, #f8fbff 0%, #cdd3d8 100%);\r\n}\r\n\r\n        .file-upload:hover {\r\n            border-color: #ca1561;\r\n            transform: translateY(-2px);\r\n            box-shadow: 0 5px 15px rgba(79, 172, 254, 0.2);\r\n        }\r\n\r\n        .file-upload.dragover {\r\n            border-color: #00f2fe;\r\n            background: linear-gradient(135deg, #e8f4fd 0%, #d1ebfa 100%);\r\n        }\r\n\r\n        .file-input {\r\n            display: none;\r\n        }\r\n\r\n        .setting-group {\r\n            margin-bottom: 15px;\r\n        }\r\n\r\n        .setting-group1 {\r\n            margin-bottom: 15px;\r\n        }\r\n\r\n        .setting-group label {\r\n            display: block;\r\n            margin-bottom: 5px;\r\n            font-weight: 600;\r\n            color: #555;\r\n        }\r\n\r\n        .setting-group input, .setting-group select {\r\n            width: 100%;\r\n            padding: 10px;\r\n            border: 2px solid #e0e0e0;\r\n            border-radius: 6px;\r\n            font-size: 14px;\r\n            transition: border-color 0.3s;\r\n        }\r\n\r\n        .setting-group input:focus, .setting-group select:focus {\r\n            outline: none;\r\n            border-color: #4facfe;\r\n            box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);\r\n        }\r\n\r\n        .btn {\r\n            padding: 12px 20px;\r\n            border: none;\r\n            border-radius: 6px;\r\n            font-weight: bold;\r\n            cursor: pointer;\r\n            transition: all 0.3s ease;\r\n            font-size: 14px;\r\n        }\r\n\r\n        .btn-primary {\r\n            background: #9b0745;\r\n            color: white;\r\n        }\r\n\r\n        .btn-primary:hover:not(:disabled) {\r\n            transform: translateY(-2px);\r\n            box-shadow: 0 5px 15px rgba(79, 172, 254, 0.3);\r\n        }\r\n\r\n        .btn-success {\r\n            background: #666666;\r\n            color: white;\r\n        }\r\n\r\n        .btn-success:hover:not(:disabled) {\r\n            transform: translateY(-2px);\r\n            box-shadow: 0 5px 15px rgba(67, 233, 123, 0.3);\r\n        }\r\n\r\n        .btn:disabled {\r\n            opacity: 0.6;\r\n            cursor: not-allowed;\r\n            transform: none !important;\r\n        }\r\n\r\n        .progress-container {\r\n            margin: 15px 0;\r\n            display: none;\r\n        }\r\n\r\n        .progress-bar {\r\n            width: 100%;\r\n            height: 8px;\r\n            background: #e0e0e0;\r\n            border-radius: 4px;\r\n            overflow: hidden;\r\n        }\r\n\r\n        .progress-fill {\r\n            height: 100%;\r\n            background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);\r\n            width: 0%;\r\n            transition: width 0.3s ease;\r\n        }\r\n\r\n        .progress-text {\r\n            text-align: center;\r\n            margin-top: 8px;\r\n            font-size: 12px;\r\n            color: #666;\r\n        }\r\n\r\n        .alert {\r\n            padding: 12px;\r\n            border-radius: 6px;\r\n            margin: 10px 0;\r\n            font-weight: 600;\r\n        }\r\n\r\n        .alert-success {\r\n            background: #d4edda;\r\n            color: #155724;\r\n            border: 1px solid #c3e6cb;\r\n        }\r\n\r\n        .alert-error {\r\n            background: #f8d7da;\r\n            color: #721c24;\r\n            border: 1px solid #f5c6cb;\r\n        }\r\n\r\n        \/* .alert-info {\r\n            background: #cce7ff;\r\n            color: #004085;\r\n            border: 1px solid #b3d9ff;\r\n        } *\/\r\n      .alert-info {\r\n   background: #ffffff;\r\n    color: #131414;\r\n    border: 1px solid #ffffff;\r\n}\r\n\r\n        .toolbar-group {\r\n            display: flex;\r\n            gap: 10px;\r\n            align-items: center;\r\n        }\r\n\r\n        .toolbar-btn {\r\n            background: rgba(255, 255, 255, 0.9);\r\n            border: 1px solid #ddd;\r\n            border-radius: 6px;\r\n            padding: 8px 12px;\r\n            cursor: pointer;\r\n            font-size: 12px;\r\n            transition: all 0.3s ease;\r\n        }\r\n\r\n        .toolbar-btn:hover {\r\n            background: white;\r\n            transform: translateY(-1px);\r\n            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\r\n        }\r\n\r\n        .toolbar-btn.active {\r\n            background: #db2378;\r\n            color: white;\r\n        }\r\n\r\n        #viewer3d {\r\n            width: 100%;\r\n            height: 100%;\r\n        }\r\n\r\n        .model-info {\r\n            position: absolute;\r\n            top: 20px;\r\n            left: 20px;\r\n            background: rgba(0, 0, 0, 0.8);\r\n            color: white;\r\n            padding: 15px;\r\n            border-radius: 8px;\r\n            font-size: 12px;\r\n            min-width: 200px;\r\n            display: none;\r\n        }\r\n\r\n        .model-info h4 {\r\n            margin-bottom: 10px;\r\n            color:#f793b8;       }\r\n\r\n        .model-info div {\r\n            margin-bottom: 5px;\r\n        }\r\n\r\n        .layer-preview {\r\n            position: absolute;\r\n            bottom: 20px;\r\n            right: 20px;\r\n            background: rgba(0, 0, 0, 0.8);\r\n            color: white;\r\n            padding: 15px;\r\n            border-radius: 8px;\r\n            display: none;\r\n        }\r\n\r\n        .layer-slider {\r\n            width: 200px;\r\n            margin: 10px 0;\r\n        }\r\n\r\n        .preset-buttons {\r\n            display: grid;\r\n            grid-template-columns: 1fr 1fr;\r\n            gap: 10px;\r\n            margin-bottom: 15px;\r\n        }\r\n\r\n        .preset-btn {\r\n            padding: 8px;\r\n            background: #f8f9fa;\r\n            border: 2px solid #e0e0e0;\r\n            border-radius: 6px;\r\n            cursor: pointer;\r\n            text-align: center;\r\n            font-size: 12px;\r\n            transition: all 0.3s ease;\r\n        }\r\n\r\n        .preset-btn:hover {\r\n            border-color: #4facfe;\r\n            background: #f0f8ff;\r\n        }\r\n\r\n        .preset-btn.active {\r\n            \/* border-color: #050505; *\/\r\n            background: #9b0745;\r\n            color: rgb(255, 255, 255);\r\n        }\r\n\r\n        .setting-tabs {\r\n            display: flex;\r\n            margin-bottom: 15px;\r\n            border-bottom: 1px solid #e0e0e0;\r\n        }\r\n\r\n        .tab-btn {\r\n            padding: 10px 15px;\r\n            background: none;\r\n            border: none;\r\n            cursor: pointer;\r\n            border-bottom: 2px solid transparent;\r\n            transition: all 0.3s ease;\r\n        }\r\n\r\n        .tab-btn.active {\r\n           border-bottom-color: #9b0745;\r\n    color: #9b0745;\r\n    font-weight: bold;\r\n        }\r\n\r\n        .tab-content {\r\n            display: none;\r\n        }\r\n\r\n        .tab-content.active {\r\n            display: block;\r\n        }\r\n\r\n        .advanced-settings {\r\n            display: grid;\r\n            grid-template-columns: 1fr 1fr;\r\n            gap: 10px;\r\n        }\r\n\r\n        .log-area {\r\n            background: #f8f9fa;\r\n            border: 1px solid #e0e0e0;\r\n            border-radius: 6px;\r\n            padding: 10px;\r\n            height: 150px;\r\n            overflow-y: auto;\r\n            font-family: 'Courier New', monospace;\r\n            font-size: 11px;\r\n            margin-top: 10px;\r\n        }\r\n\r\n        .log-entry {\r\n            margin-bottom: 5px;\r\n            padding: 3px 0;\r\n        }\r\n\r\n        .log-entry.info { color: #0066cc; }\r\n        .log-entry.success { color: #28a745; }\r\n        .log-entry.warning { color: #ffc107; }\r\n        .log-entry.error { color: #dc3545; }\r\n\r\n        .demo-models {\r\n            display: flex;\r\n            gap: 10px;\r\n            margin-top: 10px;\r\n            flex-wrap: wrap;\r\n        }\r\n\r\n        .demo-btn {\r\n            padding: 8px 12px;\r\n            background: #f0f8ff;\r\n            border: 1px solid #4facfe;\r\n            border-radius: 4px;\r\n            cursor: pointer;\r\n            font-size: 11px;\r\n            transition: all 0.3s ease;\r\n        }\r\n\r\n        .demo-btn:hover {\r\n            background: #4facfe;\r\n            color: white;\r\n        }\r\n\r\n        @media (max-width: 768px) {\r\n            .app-container {\r\n                flex-direction: column;\r\n            }\r\n            \r\n            .sidebar {\r\n                width: 100%;\r\n                height: auto;\r\n                max-height: 40vh;\r\n            }\r\n            \r\n            .advanced-settings {\r\n                grid-template-columns: 1fr;\r\n            }\r\n        }\r\n    <\/style>\r\n<\/head>\r\n<body>\r\n    <div class=\"app-container\">\r\n        <div class=\"sidebar\">\r\n             <!-- <h1 style=\"font-size:18px;\">\ud83d\udd27 Online 3d Slicer(Pre-alpha)<\/h1> -->\r\n            \r\n            <!-- File Upload Section -->\r\n            <div class=\"section\">\r\n                <h3>\ud83d\udcc1 File Upload<\/h3>\r\n                <div class=\"file-upload\" id=\"fileUpload\">\r\n                    <input type=\"file\" id=\"fileInput\" class=\"file-input\" accept=\".stl,.obj,.3mf,.gcode,.g\" multiple>\r\n                    <div>\r\n                        <div style=\"font-size: 2em; margin-bottom: 10px;\">\ud83d\udcce<\/div>\r\n                        <div><strong>Click or drag files here<\/strong><\/div>\r\n                        <div style=\"font-size: 12px; color: #666; margin-top: 5px;\">\r\n                            Supports STL, OBJ, 3MF, G-code files\r\n                        <\/div>\r\n                    <\/div>\r\n                <\/div>\r\n                <div id=\"fileInfo\"><\/div>\r\n            <\/div>\r\n\r\n            <!-- Print Settings -->\r\n            <div class=\"section\">\r\n                <h3>\u2699\ufe0f Print Settings<\/h3>\r\n                \r\n                <!-- Preset Buttons -->\r\n                <div class=\"preset-buttons\">\r\n                    <div class=\"preset-btn\" data-preset=\"draft\">\ud83d\ude80 Draft<\/div>\r\n                    <div class=\"preset-btn active\" data-preset=\"normal\">\u26a1 Normal<\/div>\r\n                    <div class=\"preset-btn\" data-preset=\"fine\">\u2728 Fine<\/div>\r\n                    <div class=\"preset-btn\" data-preset=\"custom\">\ud83d\udd27 Custom<\/div>\r\n                <\/div>\r\n\r\n                <!-- Setting Tabs -->\r\n                <div class=\"setting-tabs\">\r\n                    <button class=\"tab-btn active\" data-tab=\"basic\">Basic<\/button>\r\n                    <button class=\"tab-btn\" data-tab=\"advanced\">Advanced<\/button>\r\n                    <button class=\"tab-btn\" data-tab=\"material\">Material<\/button>\r\n                <\/div>\r\n\r\n                <!-- Basic Settings -->\r\n                <div class=\"tab-content active\" id=\"basic\">\r\n                     <div class=\"setting-group1\">\r\n                        <label for=\"layerHeight\">Bed Size<BR><\/label><label for=\"layerHeight\"><\/label><br>X&nbsp;\r\n                        <input type=\"number\" id=\"BedSizeX\" value=\"300\" min=\"0.05\" max=\"1000\" step=\"1\" style=\"width:62px;\" class=\"preset-btn\" ><label for=\"layerHeight\">&nbsp;Y&nbsp;<\/label>\r\n                        <input type=\"number\" id=\"BedSizeY\" value=\"300\" min=\"0.05\" max=\"1000\" step=\"1\" style=\"width:62px;\" class=\"preset-btn\">\r\n                        <label for=\"layerHeight\">&nbsp;Z&nbsp;<\/label>\r\n                        <input type=\"number\" id=\"BedSizeZ\" value=\"300\" min=\"0.05\" max=\"1000\" step=\"1\" style=\"width:62px;\"  class=\"preset-btn\">\r\n                    <\/div><br>\r\n                    <div class=\"setting-group\">\r\n                        <label for=\"nozzleDiameter\">Nozzle Diameter (mm)<\/label>\r\n                        <input type=\"number\" id=\"nozzleDiameter\" value=\"0.4\" min=\"0.1\" max=\"2.0\" step=\"0.1\">\r\n                    <\/div>\r\n                    <div class=\"setting-group\">\r\n                        <label for=\"layerHeight\">Layer Height (mm)<\/label>\r\n                        <input type=\"number\" id=\"layerHeight\" value=\"0.2\" min=\"0.05\" max=\"1.0\" step=\"0.05\">\r\n                    <\/div>\r\n                    <div class=\"setting-group\">\r\n                        <label for=\"infillDensity\">Infill Density (%)<\/label>\r\n                        <input type=\"number\" id=\"infillDensity\" value=\"20\" min=\"0\" max=\"100\" step=\"5\">\r\n                    <\/div>\r\n                    <div class=\"setting-group\">\r\n                        <label for=\"printSpeed\">Print Speed (mm\/s)<\/label>\r\n                        <input type=\"number\" id=\"printSpeed\" value=\"60\" min=\"10\" max=\"150\" step=\"5\">\r\n                    <\/div>\r\n                    <div class=\"setting-group\">\r\n                        <label for=\"infillPattern\">Infill Pattern<\/label>\r\n                        <select id=\"infillPattern\">\r\n                            <option value=\"grid\">Grid<\/option>\r\n                            <option value=\"lines\">Lines<\/option>\r\n                            <option value=\"triangular\">Triangular<\/option>\r\n                            <option value=\"honeycomb\">Honeycomb<\/option>\r\n                            <option value=\"gyroid\">Gyroid<\/option>\r\n                        <\/select>\r\n                    <\/div>\r\n                <\/div>\r\n\r\n                <!-- Advanced Settings -->\r\n                <div class=\"tab-content\" id=\"advanced\">\r\n                    <div class=\"advanced-settings\">\r\n                        <div class=\"setting-group\">\r\n                            <label for=\"wallThickness\">Wall Thickness (mm)<\/label>\r\n                            <input type=\"number\" id=\"wallThickness\" value=\"1.2\" min=\"0.4\" max=\"5\" step=\"0.1\">\r\n                        <\/div>\r\n                        <div class=\"setting-group\">\r\n                            <label for=\"topBottomLayers\">Top\/Bottom Layers<\/label>\r\n                            <input type=\"number\" id=\"topBottomLayers\" value=\"3\" min=\"1\" max=\"10\" step=\"1\">\r\n                        <\/div>\r\n                        <div class=\"setting-group\">\r\n                            <label for=\"supportEnabled\">Enable Supports<\/label>\r\n                            <select id=\"supportEnabled\">\r\n                                <option value=\"false\">Disabled<\/option>\r\n                                <option value=\"true\">Enabled<\/option>\r\n                                <option value=\"touching\">Touching Buildplate<\/option>\r\n                            <\/select>\r\n                        <\/div>\r\n                        <div class=\"setting-group\">\r\n                            <label for=\"supportDensity\">Support Density (%)<\/label>\r\n                            <input type=\"number\" id=\"supportDensity\" value=\"15\" min=\"5\" max=\"50\" step=\"5\">\r\n                        <\/div>\r\n                        <div class=\"setting-group\">\r\n                            <label for=\"retraction\">Retraction (mm)<\/label>\r\n                            <input type=\"number\" id=\"retraction\" value=\"4.5\" min=\"0\" max=\"10\" step=\"0.1\">\r\n                        <\/div>\r\n                        <div class=\"setting-group\">\r\n                            <label for=\"travelSpeed\">Travel Speed (mm\/s)<\/label>\r\n                            <input type=\"number\" id=\"travelSpeed\" value=\"120\" min=\"50\" max=\"300\" step=\"10\">\r\n                        <\/div>\r\n                    <\/div>\r\n                <\/div>\r\n\r\n                <!-- Material Settings -->\r\n                <div class=\"tab-content\" id=\"material\">\r\n                    <div class=\"setting-group\">\r\n                        <label for=\"material\">Material<\/label>\r\n                        <select id=\"material\">\r\n                            <option value=\"pla\">PLA<\/option>\r\n                            <option value=\"abs\">ABS<\/option>\r\n                            <option value=\"petg\">PETG<\/option>\r\n                            <option value=\"tpu\">TPU<\/option>\r\n                            <option value=\"custom\">Custom<\/option>\r\n                        <\/select>\r\n                    <\/div>\r\n                    <div class=\"setting-group\">\r\n                        <label for=\"nozzleTemp\">Nozzle Temperature (\u00b0C)<\/label>\r\n                        <input type=\"number\" id=\"nozzleTemp\" value=\"200\" min=\"150\" max=\"300\" step=\"5\">\r\n                    <\/div>\r\n                    <div class=\"setting-group\">\r\n                        <label for=\"bedTemp\">Bed Temperature (\u00b0C)<\/label>\r\n                        <input type=\"number\" id=\"bedTemp\" value=\"60\" min=\"0\" max=\"120\" step=\"5\">\r\n                    <\/div>\r\n                    <div class=\"setting-group\">\r\n                        <label for=\"fanSpeed\">Fan Speed (%)<\/label>\r\n                        <input type=\"number\" id=\"fanSpeed\" value=\"100\" min=\"0\" max=\"100\" step=\"5\">\r\n                    <\/div>\r\n                <\/div>\r\n\r\n                <!-- Action Buttons -->\r\n                <div style=\"margin-top: 20px;position:fixed;position: sticky;background-color: #ffffff;\r\n    bottom: 0;\">\r\n                    <button class=\"btn btn-primary\" id=\"sliceBtn\" disabled style=\"width: 100%; margin-bottom: 10px;\">\r\n                        \ud83d\ude80 Slice Model\r\n                    <\/button>\r\n                    <button class=\"btn btn-success\" id=\"downloadBtn\" disabled style=\"width: 100%;\">\r\n                        \ud83d\udcbe Download G-code\r\n                    <\/button>\r\n                <\/div>\r\n\r\n                <!-- Progress -->\r\n                <div class=\"progress-container\" id=\"progressContainer\">\r\n                    <div class=\"progress-bar\">\r\n                        <div class=\"progress-fill\" id=\"progressFill\"><\/div>\r\n                    <\/div>\r\n                    <div class=\"progress-text\" id=\"progressText\">Processing...<\/div>\r\n                <\/div>\r\n\r\n                <!-- Log Area -->\r\n                <div class=\"log-area\" id=\"logArea\"><\/div>\r\n            <\/div>\r\n        <\/div>\r\n\r\n        <div class=\"main-content\">\r\n            <!-- Toolbar -->\r\n            <div class=\"toolbar\">\r\n                <div class=\"toolbar-group\">\r\n                    <button class=\"toolbar-btn active\" id=\"viewSolid\">\ud83e\uddca Solid<\/button>\r\n                    <button class=\"toolbar-btn\" id=\"viewWireframe\">\ud83d\udcd0 Wireframe<\/button>\r\n                    <button class=\"toolbar-btn\" id=\"viewLayers\">\ud83d\udcc4 Layers<\/button>\r\n                    <button class=\"toolbar-btn\" id=\"viewGcode\">\ud83d\udee3\ufe0f G-code<\/button>\r\n                    <button class=\"toolbar-btn\" id=\"viewSupports\">\ud83c\udfd7\ufe0f Supports<\/button>\r\n                <\/div>\r\n                <div class=\"toolbar-group\">\r\n                    <button class=\"toolbar-btn\" id=\"resetView\">\ud83c\udfaf Reset View<\/button>\r\n                    <button class=\"toolbar-btn\" id=\"fitView\">\ud83d\udccf Fit to View<\/button>\r\n                    <button class=\"toolbar-btn\" id=\"toggleAxes\">\u26a1 Axes<\/button>\r\n                <\/div>\r\n            <\/div>\r\n\r\n            <!-- 3D Viewer -->\r\n            <div class=\"viewer-area\">\r\n                <div id=\"viewer3d\"><\/div>\r\n                \r\n                <!-- Model Info Panel -->\r\n                <div class=\"model-info\" id=\"modelInfo\">\r\n                    <h4>Model Information<\/h4>\r\n                    <div>Vertices: <span id=\"vertexCount\">0<\/span><\/div>\r\n                    <div>Faces: <span id=\"faceCount\">0<\/span><\/div>\r\n                    <div>Size: <span id=\"modelSize\">0\u00d70\u00d70 mm<\/span><\/div>\r\n                    <div>Volume: <span id=\"modelVolume\">0 cm\u00b3<\/span><\/div>\r\n                    <div>Estimated Print Time: <span id=\"printTime\">--<\/span><\/div>\r\n                    <div>Material Usage: <span id=\"materialUsage\">-- g<\/span><\/div>\r\n                    <div>Line Width: <span id=\"lineWidth\">-- mm<\/span><\/div>\r\n                <\/div>\r\n\r\n                <!-- Layer Preview -->\r\n                <div class=\"layer-preview\" id=\"layerPreview\">\r\n                    <h4>Layer Preview<\/h4>\r\n                    <div>Layer: <span id=\"currentLayer\">0<\/span> \/ <span id=\"totalLayers\">0<\/span><\/div>\r\n                    <input type=\"range\" class=\"layer-slider\" id=\"layerSlider\" min=\"0\" max=\"0\" value=\"0\">\r\n                    <div>Height: <span id=\"layerHeight\">0.00<\/span> mm<\/div>\r\n                <\/div>\r\n\r\n                <!-- G-code Viewer Controls -->\r\n                <div class=\"layer-preview\" id=\"gcodeViewer\" style=\"display: none;\">\r\n                    <h4>G-code Viewer<\/h4>\r\n                    <div style=\"display: flex; gap: 10px; margin-bottom: 10px;\">\r\n                        <button class=\"toolbar-btn\" id=\"playGcode\">\u25b6\ufe0f Play<\/button>\r\n                        <button class=\"toolbar-btn\" id=\"pauseGcode\">\u23f8\ufe0f Pause<\/button>\r\n                        <button class=\"toolbar-btn\" id=\"resetGcode\">\u23f9\ufe0f Reset<\/button>\r\n                    <\/div>\r\n                    <div>Layer: <span id=\"gcodeCurrentLayer\">0<\/span> \/ <span id=\"gcodeTotalLayers\">0<\/span><\/div>\r\n                    <input type=\"range\" class=\"layer-slider\" id=\"gcodeLayerSlider\" min=\"0\" max=\"0\" value=\"0\">\r\n                    <div>Progress: <span id=\"gcodeProgress\">0<\/span>%<\/div>\r\n                    <div>Speed: \r\n                        <select id=\"playbackSpeed\" style=\"width: 60px; padding: 2px;\">\r\n                            <option value=\"0.25\">0.25x<\/option>\r\n                            <option value=\"0.5\">0.5x<\/option>\r\n                            <option value=\"1\" selected>1x<\/option>\r\n                            <option value=\"2\">2x<\/option>\r\n                            <option value=\"5\">5x<\/option>\r\n                        <\/select>\r\n                    <\/div>\r\n                <\/div>\r\n            <\/div>\r\n\r\n            <!-- Status Bar -->\r\n            <div class=\"status-bar\">\r\n                <div id=\"statusLeft\">Ready<\/div>\r\n                <div id=\"statusRight\">No model loaded<\/div>\r\n            <\/div>\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/three.js\/r128\/three.min.js\"><\/script>\r\n    <script>\r\n        class SlicerApp {\r\n            constructor() {\r\n                this.scene = null;\r\n                this.camera = null;\r\n                this.renderer = null;\r\n                this.currentModel = null;\r\n                this.currentMesh = null;\r\n                this.settings = this.getDefaultSettings();\r\n                this.gcodeData = null;\r\n                this.layers = [];\r\n                this.axesHelper = null;\r\n                this.buildPlate = null;\r\n                this.mouseX = 0;\r\n                this.mouseY = 0;\r\n                this.isMouseDown = false;\r\n                this.viewMode = 'solid';\r\n                this.gcodeViewer = null;\r\n                this.gcodeLines = [];\r\n                this.gcodeProgress = 0;\r\n                this.isPlaying = false;\r\n                this.playbackSpeed = 1;\r\n                \r\n                this.init();\r\n                this.setupEventListeners();\r\n                this.log('Application initialized', 'success');\r\n                \r\n                \/\/ Auto-load torus demo model on startup\r\n                setTimeout(() => {\r\n                    this.loadDemoModel('torus');\r\n                    this.log('Auto-loaded demo torus model', 'info');\r\n                }, 500);\r\n            }\r\n\r\n            init() {\r\n                this.setupThreeJS();\r\n                this.setupLighting();\r\n                this.setupBuildPlate();\r\n                this.setupAxes();\r\n                this.animate();\r\n            }\r\n\r\n            getDefaultSettings() {\r\n                return {\r\n                    nozzleDiameter: 0.4,\r\n                    layerHeight: 0.2,\r\n                    infillDensity: 20,\r\n                    printSpeed: 60,\r\n                    wallThickness: 1.2,\r\n                    topBottomLayers: 3,\r\n                    supportEnabled: false,\r\n                    supportDensity: 15,\r\n                    retraction: 4.5,\r\n                    travelSpeed: 120,\r\n                    nozzleTemp: 200,\r\n                    bedTemp: 60,\r\n                    fanSpeed: 100,\r\n                    infillPattern: 'grid',\r\n                    material: 'pla'\r\n                };\r\n            }\r\n\r\n            setupThreeJS() {\r\n                const container = document.getElementById('viewer3d');\r\n                \r\n                \/\/ Scene\r\n                this.scene = new THREE.Scene();\r\n                this.scene.background = new THREE.Color(0x1a1a2e);\r\n\r\n                \/\/ Camera\r\n                this.camera = new THREE.PerspectiveCamera(\r\n                    75,\r\n                    container.clientWidth \/ container.clientHeight,\r\n                    0.1,\r\n                    1000\r\n                );\r\n                this.camera.position.set(50, 50, 50);\r\n                this.camera.lookAt(0, 0, 0);\r\n\r\n                \/\/ Renderer\r\n                this.renderer = new THREE.WebGLRenderer({ antialias: true });\r\n                this.renderer.setSize(container.clientWidth, container.clientHeight);\r\n                this.renderer.shadowMap.enabled = true;\r\n                this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;\r\n                container.appendChild(this.renderer.domElement);\r\n\r\n                \/\/ Controls\r\n                this.setupMouseControls();\r\n\r\n                \/\/ Handle resize\r\n                window.addEventListener('resize', () => this.onWindowResize());\r\n            }\r\n\r\n            setupMouseControls() {\r\n                const canvas = this.renderer.domElement;\r\n\r\n                canvas.addEventListener('mousedown', (e) => {\r\n                    this.isMouseDown = true;\r\n                    this.mouseX = e.clientX;\r\n                    this.mouseY = e.clientY;\r\n                });\r\n\r\n                canvas.addEventListener('mousemove', (e) => {\r\n                    if (!this.isMouseDown) return;\r\n                    \r\n                    const deltaX = e.clientX - this.mouseX;\r\n                    const deltaY = e.clientY - this.mouseY;\r\n                    \r\n                    \/\/ Rotate camera around origin\r\n                    const spherical = new THREE.Spherical();\r\n                    spherical.setFromVector3(this.camera.position);\r\n                    spherical.theta -= deltaX * 0.01;\r\n                    spherical.phi += deltaY * 0.01;\r\n                    spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi));\r\n                    \r\n                    this.camera.position.setFromSpherical(spherical);\r\n                    this.camera.lookAt(0, 0, 0);\r\n                    \r\n                    this.mouseX = e.clientX;\r\n                    this.mouseY = e.clientY;\r\n                });\r\n\r\n                document.addEventListener('mouseup', () => {\r\n                    this.isMouseDown = false;\r\n                });\r\n\r\n                canvas.addEventListener('wheel', (e) => {\r\n                    e.preventDefault();\r\n                    const scale = e.deltaY > 0 ? 1.1 : 0.9;\r\n                    this.camera.position.multiplyScalar(scale);\r\n                    \r\n                    \/\/ Limit zoom\r\n                    const distance = this.camera.position.length();\r\n                    if (distance < 10) this.camera.position.normalize().multiplyScalar(10);\r\n                    if (distance > 200) this.camera.position.normalize().multiplyScalar(200);\r\n                });\r\n            }\r\n\r\n            setupLighting() {\r\n                \/\/ Ambient light\r\n                const ambientLight = new THREE.AmbientLight(0x404040, 0.6);\r\n                this.scene.add(ambientLight);\r\n\r\n                \/\/ Main directional light\r\n                const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);\r\n                directionalLight.position.set(50, 50, 50);\r\n                directionalLight.castShadow = true;\r\n                directionalLight.shadow.mapSize.width = 2048;\r\n                directionalLight.shadow.mapSize.height = 2048;\r\n                directionalLight.shadow.camera.near = 0.5;\r\n                directionalLight.shadow.camera.far = 500;\r\n                directionalLight.shadow.camera.left = -50;\r\n                directionalLight.shadow.camera.right = 50;\r\n                directionalLight.shadow.camera.top = 50;\r\n                directionalLight.shadow.camera.bottom = -50;\r\n                this.scene.add(directionalLight);\r\n\r\n                \/\/ Fill light\r\n                const fillLight = new THREE.DirectionalLight(0x4facfe, 0.3);\r\n                fillLight.position.set(-50, 30, -50);\r\n                this.scene.add(fillLight);\r\n            }\r\n\r\n            setupBuildPlate() {\r\n                const plateGeometry = new THREE.PlaneGeometry(200, 200);\r\n                const plateMaterial = new THREE.MeshLambertMaterial({ \r\n                    color: 0x333333,\r\n                    transparent: true,\r\n                    opacity: 0.8\r\n                });\r\n                this.buildPlate = new THREE.Mesh(plateGeometry, plateMaterial);\r\n                this.buildPlate.rotation.x = -Math.PI \/ 2;\r\n                this.buildPlate.receiveShadow = true;\r\n                this.scene.add(this.buildPlate);\r\n\r\n                \/\/ Grid lines\r\n                const gridHelper = new THREE.GridHelper(200, 20, 0x666666, 0x444444);\r\n                this.scene.add(gridHelper);\r\n            }\r\n\r\n            setupAxes() {\r\n                this.axesHelper = new THREE.AxesHelper(20);\r\n                this.axesHelper.visible = false;\r\n                this.scene.add(this.axesHelper);\r\n            }\r\n\r\n            setupEventListeners() {\r\n                \/\/ File upload\r\n                const fileInput = document.getElementById('fileInput');\r\n                const fileUpload = document.getElementById('fileUpload');\r\n\r\n                fileUpload.addEventListener('click', () => fileInput.click());\r\n                fileInput.addEventListener('change', (e) => this.handleFileUpload(e));\r\n\r\n                \/\/ Drag and drop\r\n                fileUpload.addEventListener('dragover', (e) => {\r\n                    e.preventDefault();\r\n                    fileUpload.classList.add('dragover');\r\n                });\r\n\r\n                fileUpload.addEventListener('dragleave', () => {\r\n                    fileUpload.classList.remove('dragover');\r\n                });\r\n\r\n                fileUpload.addEventListener('drop', (e) => {\r\n                    e.preventDefault();\r\n                    fileUpload.classList.remove('dragover');\r\n                    this.handleFileUpload(e);\r\n                });\r\n\r\n                \/\/ Demo models\r\n                document.querySelectorAll('.demo-btn').forEach(btn => {\r\n                    btn.addEventListener('click', () => this.loadDemoModel(btn.dataset.shape));\r\n                });\r\n\r\n                \/\/ Preset buttons\r\n                document.querySelectorAll('.preset-btn').forEach(btn => {\r\n                    btn.addEventListener('click', () => this.selectPreset(btn.dataset.preset));\r\n                });\r\n\r\n                \/\/ Tab buttons\r\n                document.querySelectorAll('.tab-btn').forEach(btn => {\r\n                    btn.addEventListener('click', () => this.switchTab(btn.dataset.tab));\r\n                });\r\n\r\n                \/\/ Toolbar buttons\r\n                document.getElementById('viewSolid').addEventListener('click', () => this.setViewMode('solid'));\r\n                document.getElementById('viewWireframe').addEventListener('click', () => this.setViewMode('wireframe'));\r\n                document.getElementById('viewLayers').addEventListener('click', () => this.setViewMode('layers'));\r\n                document.getElementById('viewGcode').addEventListener('click', () => this.setViewMode('gcode'));\r\n                document.getElementById('viewSupports').addEventListener('click', () => this.setViewMode('supports'));\r\n                document.getElementById('resetView').addEventListener('click', () => this.resetView());\r\n                document.getElementById('fitView').addEventListener('click', () => this.fitToView());\r\n                document.getElementById('toggleAxes').addEventListener('click', () => this.toggleAxes());\r\n\r\n                \/\/ G-code viewer controls\r\n                document.getElementById('playGcode').addEventListener('click', () => this.playGcode());\r\n                document.getElementById('pauseGcode').addEventListener('click', () => this.pauseGcode());\r\n                document.getElementById('resetGcode').addEventListener('click', () => this.resetGcode());\r\n                \r\n                \/\/ Add event listener for G-code layer slider with error handling\r\n                const gcodeSlider = document.getElementById('gcodeLayerSlider');\r\n                if (gcodeSlider) {\r\n                    gcodeSlider.addEventListener('input', (e) => {\r\n                        const layerIndex = parseInt(e.target.value);\r\n                        this.showGcodeLayer(layerIndex);\r\n                    });\r\n                }\r\n                \r\n                \/\/ Add event listener for playback speed\r\n                const speedSelect = document.getElementById('playbackSpeed');\r\n                if (speedSelect) {\r\n                    speedSelect.addEventListener('change', (e) => {\r\n                        this.playbackSpeed = parseFloat(e.target.value);\r\n                        this.log(`Playback speed set to ${this.playbackSpeed}x`, 'info');\r\n                    });\r\n                }\r\n\r\n                \/\/ Action buttons\r\n                document.getElementById('sliceBtn').addEventListener('click', () => this.sliceModel());\r\n                document.getElementById('downloadBtn').addEventListener('click', () => this.downloadGcode());\r\n\r\n                \/\/ Material preset changes\r\n                document.getElementById('material').addEventListener('change', (e) => this.updateMaterialSettings(e.target.value));\r\n\r\n                \/\/ Settings changes - include nozzle diameter and bed size\r\n                ['nozzleDiameter', 'layerHeight', 'infillDensity', 'printSpeed', 'wallThickness', 'BedSizeX', 'BedSizeY', 'BedSizeZ'].forEach(id => {\r\n                    const element = document.getElementById(id);\r\n                    if (element) {\r\n                        element.addEventListener('change', () => this.updateEstimates());\r\n                        element.addEventListener('input', () => this.updateEstimates());\r\n                    }\r\n                });\r\n            }\r\n\r\n            loadDemoModel(shape) {\r\n                this.log(`Loading demo model: ${shape}`, 'info');\r\n                \r\n                let geometry;\r\n                switch(shape) {\r\n                    case 'cube':\r\n                        geometry = new THREE.BoxGeometry(20, 20, 20);\r\n                        break;\r\n                    case 'sphere':\r\n                        geometry = new THREE.SphereGeometry(12, 32, 16);\r\n                        break;\r\n                    case 'cylinder':\r\n                        geometry = new THREE.CylinderGeometry(10, 10, 25, 32);\r\n                        break;\r\n                    case 'torus':\r\n                        geometry = new THREE.TorusGeometry(12, 4, 16, 100);\r\n                        break;\r\n                    default:\r\n                        return;\r\n                }\r\n\r\n                this.displayModel(geometry, `Demo ${shape}`);\r\n                this.updateFileInfo({ name: `demo_${shape}.stl`, size: 1024 });\r\n                \r\n                document.getElementById('sliceBtn').disabled = false;\r\n                this.log(`Demo model loaded: ${shape}`, 'success');\r\n            }\r\n\r\n            handleFileUpload(event) {\r\n                const files = event.dataTransfer ? event.dataTransfer.files : event.target.files;\r\n                \r\n                for (let file of files) {\r\n                    if (this.isValidFile(file)) {\r\n                        this.loadFile(file);\r\n                    } else {\r\n                        this.showAlert(`Unsupported file type: ${file.name}`, 'error');\r\n                    }\r\n                }\r\n            }\r\n\r\n            isValidFile(file) {\r\n                const validExtensions = ['.stl', '.obj', '.3mf', '.gcode', '.g'];\r\n                return validExtensions.some(ext => file.name.toLowerCase().endsWith(ext));\r\n            }\r\n\r\n            async loadFile(file) {\r\n                this.log(`Loading file: ${file.name}`, 'info');\r\n                \r\n                try {\r\n                    let geometry;\r\n                    \r\n                    if (file.name.toLowerCase().endsWith('.gcode') || file.name.toLowerCase().endsWith('.g')) {\r\n                        \/\/ Load G-code file directly\r\n                        const text = await file.text();\r\n                        this.loadGcodeFile(text, file.name);\r\n                        return;\r\n                    } else if (file.name.toLowerCase().endsWith('.stl')) {\r\n                        const buffer = await file.arrayBuffer();\r\n                        geometry = this.parseSTL(buffer);\r\n                    } else if (file.name.toLowerCase().endsWith('.obj')) {\r\n                        const text = await file.text();\r\n                        geometry = this.parseOBJ(text);\r\n                    } else {\r\n                        throw new Error('Unsupported file format');\r\n                    }\r\n                    \r\n                    if (geometry) {\r\n                        this.displayModel(geometry, file.name);\r\n                        this.updateFileInfo(file);\r\n                        document.getElementById('sliceBtn').disabled = false;\r\n                        this.log(`File loaded successfully: ${file.name}`, 'success');\r\n                    }\r\n                } catch (error) {\r\n                    this.log(`Error loading file: ${error.message}`, 'error');\r\n                    this.showAlert(`Failed to load ${file.name}: ${error.message}`, 'error');\r\n                }\r\n            }\r\n\r\n            loadGcodeFile(gcodeText, filename) {\r\n                this.log(`Loading G-code file: ${filename}`, 'info');\r\n                \r\n                \/\/ Store the G-code data\r\n                this.gcodeData = gcodeText;\r\n                \r\n                \/\/ Parse for visualization\r\n                this.parseGcodeForVisualization();\r\n                \r\n                \/\/ Update UI\r\n                this.updateFileInfo({ name: filename, size: gcodeText.length });\r\n                document.getElementById('sliceBtn').disabled = true; \/\/ No need to slice G-code\r\n                document.getElementById('downloadBtn').disabled = false; \/\/ Can download existing G-code\r\n                \r\n                \/\/ Automatically switch to G-code view\r\n                this.setViewMode('gcode');\r\n                \r\n                \/\/ Hide model info panel since we don't have a 3D model\r\n                document.getElementById('modelInfo').style.display = 'none';\r\n                \r\n                this.showAlert(`G-code file loaded successfully! \ud83c\udf89\\nFound ${this.gcodeLines.length} layers`, 'success');\r\n                this.log(`G-code file loaded: ${this.gcodeLines.length} layers parsed`, 'success');\r\n                \r\n                \/\/ Update status\r\n                document.getElementById('statusRight').textContent = `G-code loaded: ${filename}`;\r\n            }\r\n\r\n            parseSTL(buffer) {\r\n                const dataView = new DataView(buffer);\r\n                \r\n                \/\/ Check if it's binary STL\r\n                if (buffer.byteLength > 80) {\r\n                    \/\/ Read potential triangle count from binary header\r\n                    const triangleCount = dataView.getUint32(80, true);\r\n                    const expectedSize = 80 + 4 + (triangleCount * 50);\r\n                    \r\n                    \/\/ If size matches binary format, parse as binary\r\n                    if (Math.abs(buffer.byteLength - expectedSize) < 100) {\r\n                        return this.parseBinarySTL(dataView, triangleCount);\r\n                    }\r\n                }\r\n                \r\n                \/\/ Try parsing as ASCII STL\r\n                try {\r\n                    const text = new TextDecoder().decode(buffer);\r\n                    return this.parseASCIISTL(text);\r\n                } catch (error) {\r\n                    throw new Error('Invalid STL file format');\r\n                }\r\n            }\r\n\r\n            parseBinarySTL(dataView, triangleCount) {\r\n                this.log(`Parsing binary STL with ${triangleCount} triangles`, 'info');\r\n                \r\n                const vertices = [];\r\n                const normals = [];\r\n                \r\n                let offset = 84; \/\/ Skip 80-byte header + 4-byte triangle count\r\n                \r\n                for (let i = 0; i < triangleCount; i++) {\r\n                    try {\r\n                        \/\/ Read normal vector (3 floats)\r\n                        const nx = dataView.getFloat32(offset, true);\r\n                        const ny = dataView.getFloat32(offset + 4, true);\r\n                        const nz = dataView.getFloat32(offset + 8, true);\r\n                        offset += 12;\r\n                        \r\n                        \/\/ Read 3 vertices (9 floats total)\r\n                        for (let j = 0; j < 3; j++) {\r\n                            const x = dataView.getFloat32(offset, true);\r\n                            const y = dataView.getFloat32(offset + 4, true);\r\n                            const z = dataView.getFloat32(offset + 8, true);\r\n                            offset += 12;\r\n                            \r\n                            vertices.push(x, y, z);\r\n                            normals.push(nx, ny, nz);\r\n                        }\r\n                        \r\n                        \/\/ Skip attribute byte count (2 bytes)\r\n                        offset += 2;\r\n                        \r\n                    } catch (error) {\r\n                        this.log(`Error reading triangle ${i}: ${error.message}`, 'warning');\r\n                        break;\r\n                    }\r\n                }\r\n                \r\n                if (vertices.length === 0) {\r\n                    throw new Error('No valid triangles found in binary STL');\r\n                }\r\n                \r\n                const geometry = new THREE.BufferGeometry();\r\n                geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));\r\n                geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));\r\n                \r\n                this.log(`Loaded ${vertices.length \/ 3} vertices from binary STL`, 'success');\r\n                return geometry;\r\n            }\r\n\r\n            parseASCIISTL(text) {\r\n                this.log('Parsing ASCII STL', 'info');\r\n                \r\n                const lines = text.split('\\n');\r\n                const vertices = [];\r\n                const normals = [];\r\n                \r\n                let currentNormal = null;\r\n                let inFacet = false;\r\n                \r\n                for (let line of lines) {\r\n                    line = line.trim().toLowerCase();\r\n                    \r\n                    if (line.startsWith('facet normal')) {\r\n                        const parts = line.split(\/\\s+\/);\r\n                        if (parts.length >= 5) {\r\n                            currentNormal = [\r\n                                parseFloat(parts[2]) || 0,\r\n                                parseFloat(parts[3]) || 0,\r\n                                parseFloat(parts[4]) || 0\r\n                            ];\r\n                            inFacet = true;\r\n                        }\r\n                    } else if (line.startsWith('vertex') && inFacet) {\r\n                        const parts = line.split(\/\\s+\/);\r\n                        if (parts.length >= 4) {\r\n                            const x = parseFloat(parts[1]) || 0;\r\n                            const y = parseFloat(parts[2]) || 0;\r\n                            const z = parseFloat(parts[3]) || 0;\r\n                            \r\n                            vertices.push(x, y, z);\r\n                            if (currentNormal) {\r\n                                normals.push(...currentNormal);\r\n                            }\r\n                        }\r\n                    } else if (line.startsWith('endfacet')) {\r\n                        inFacet = false;\r\n                    }\r\n                }\r\n                \r\n                if (vertices.length === 0) {\r\n                    throw new Error('No valid geometry found in ASCII STL file');\r\n                }\r\n                \r\n                const geometry = new THREE.BufferGeometry();\r\n                geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));\r\n                \r\n                if (normals.length === vertices.length) {\r\n                    geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));\r\n                } else {\r\n                    geometry.computeVertexNormals();\r\n                }\r\n                \r\n                this.log(`Loaded ${vertices.length \/ 3} vertices from ASCII STL`, 'success');\r\n                return geometry;\r\n            }\r\n\r\n            parseOBJ(text) {\r\n                \/\/ Simple OBJ parser\r\n                const lines = text.split('\\n');\r\n                const vertices = [];\r\n                const faces = [];\r\n                \r\n                for (let line of lines) {\r\n                    line = line.trim();\r\n                    \r\n                    if (line.startsWith('v ')) {\r\n                        const parts = line.split(' ');\r\n                        vertices.push([\r\n                            parseFloat(parts[1]),\r\n                            parseFloat(parts[2]),\r\n                            parseFloat(parts[3])\r\n                        ]);\r\n                    } else if (line.startsWith('f ')) {\r\n                        const parts = line.split(' ');\r\n                        const face = [];\r\n                        for (let i = 1; i < parts.length; i++) {\r\n                            face.push(parseInt(parts[i].split('\/')[0]) - 1);\r\n                        }\r\n                        faces.push(face);\r\n                    }\r\n                }\r\n                \r\n                if (vertices.length === 0 || faces.length === 0) {\r\n                    throw new Error('No valid geometry found in OBJ file');\r\n                }\r\n                \r\n                \/\/ Convert to BufferGeometry\r\n                const positions = [];\r\n                for (let face of faces) {\r\n                    if (face.length >= 3) {\r\n                        \/\/ Triangulate face\r\n                        for (let i = 1; i < face.length - 1; i++) {\r\n                            positions.push(...vertices[face[0]]);\r\n                            positions.push(...vertices[face[i]]);\r\n                            positions.push(...vertices[face[i + 1]]);\r\n                        }\r\n                    }\r\n                }\r\n                \r\n                const geometry = new THREE.BufferGeometry();\r\n                geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));\r\n                geometry.computeVertexNormals();\r\n                \r\n                return geometry;\r\n            }\r\n\r\n            displayModel(geometry, filename) {\r\n                \/\/ Remove existing model\r\n                if (this.currentMesh) {\r\n                    this.scene.remove(this.currentMesh);\r\n                }\r\n                \r\n                \/\/ Create material based on view mode\r\n                const material = this.createMaterial();\r\n                \r\n                \/\/ Create mesh\r\n                this.currentMesh = new THREE.Mesh(geometry, material);\r\n                this.currentMesh.castShadow = true;\r\n                this.currentMesh.receiveShadow = true;\r\n                \r\n                \/\/ Center the model\r\n                geometry.computeBoundingBox();\r\n                const box = geometry.boundingBox;\r\n                const center = box.getCenter(new THREE.Vector3());\r\n                geometry.translate(-center.x, -center.y, -box.min.z);\r\n                \r\n                this.scene.add(this.currentMesh);\r\n                this.currentModel = { geometry, filename };\r\n                \r\n                \/\/ Update model info\r\n                this.updateModelInfo(geometry);\r\n                \r\n                \/\/ Fit to view\r\n                this.fitToView();\r\n                \r\n                \/\/ Update status\r\n                document.getElementById('statusRight').textContent = `Model loaded: ${filename}`;\r\n                document.getElementById('modelInfo').style.display = 'block';\r\n            }\r\n\r\n            createMaterial() {\r\n                switch(this.viewMode) {\r\n                    case 'wireframe':\r\n                        return new THREE.MeshBasicMaterial({ \r\n                            color: 0x4facfe, \r\n                            wireframe: true \r\n                        });\r\n                    case 'layers':\r\n                        return new THREE.MeshLambertMaterial({ \r\n                            color: 0x43e97b,\r\n                            transparent: true,\r\n                            opacity: 0.8\r\n                        });\r\n                    case 'supports':\r\n                        return new THREE.MeshLambertMaterial({ \r\n                            color: 0xff6b6b,\r\n                            transparent: true,\r\n                            opacity: 0.7\r\n                        });\r\n                    default: \/\/ solid\r\n                        return new THREE.MeshLambertMaterial({ \r\n                            color: 0x4facfe,\r\n                            side: THREE.DoubleSide\r\n                        });\r\n                }\r\n            }\r\n\r\n            updateModelInfo(geometry) {\r\n                const positions = geometry.attributes.position;\r\n                const vertexCount = positions.count;\r\n                const faceCount = vertexCount \/ 3;\r\n                \r\n                geometry.computeBoundingBox();\r\n                const box = geometry.boundingBox;\r\n                const size = box.getSize(new THREE.Vector3());\r\n                \r\n                \/\/ Calculate volume (approximation)\r\n                const volume = (size.x * size.y * size.z) \/ 1000; \/\/ cm\u00b3\r\n                \r\n                document.getElementById('vertexCount').textContent = vertexCount.toLocaleString();\r\n                document.getElementById('faceCount').textContent = Math.floor(faceCount).toLocaleString();\r\n                document.getElementById('modelSize').textContent = \r\n                    `${size.x.toFixed(1)}\u00d7${size.y.toFixed(1)}\u00d7${size.z.toFixed(1)} mm`;\r\n                document.getElementById('modelVolume').textContent = volume.toFixed(2);\r\n                \r\n                this.updateEstimates();\r\n            }\r\n\r\n            updateEstimates() {\r\n                if (!this.currentModel) return;\r\n                \r\n                const nozzleDiameter = parseFloat(document.getElementById('nozzleDiameter').value) || 0.4;\r\n                const layerHeight = parseFloat(document.getElementById('layerHeight').value) || 0.2;\r\n                const printSpeed = parseFloat(document.getElementById('printSpeed').value) || 60;\r\n                const infillDensity = parseFloat(document.getElementById('infillDensity').value) || 20;\r\n                const wallThickness = parseFloat(document.getElementById('wallThickness').value) || 1.2;\r\n                \r\n                \/\/ Get bed size settings\r\n                const bedSizeX = parseFloat(document.getElementById('BedSizeX').value) || 300;\r\n                const bedSizeY = parseFloat(document.getElementById('BedSizeY').value) || 300;\r\n                const bedSizeZ = parseFloat(document.getElementById('BedSizeZ').value) || 300;\r\n                \r\n                const geometry = this.currentModel.geometry;\r\n                geometry.computeBoundingBox();\r\n                const size = geometry.boundingBox.getSize(new THREE.Vector3());\r\n                \r\n                \/\/ Check if model fits on bed\r\n                const bedFitStatus = this.checkBedFit(size, bedSizeX, bedSizeY, bedSizeZ);\r\n                \r\n                \/\/ Calculate line width based on nozzle diameter\r\n                const lineWidth = nozzleDiameter * 1.2; \/\/ Typical line width is 120% of nozzle diameter\r\n                \r\n                \/\/ Calculate wall lines needed\r\n                const wallLines = Math.ceil(wallThickness \/ lineWidth);\r\n                \r\n                \/\/ Estimate print time (improved calculation with nozzle diameter)\r\n                const layerCount = Math.ceil(size.z \/ layerHeight);\r\n                const perimeterLength = 2 * (size.x + size.y) * wallLines; \/\/ Approximate perimeter per layer\r\n                const infillArea = (size.x * size.y) * (infillDensity \/ 100);\r\n                const infillLength = infillArea \/ lineWidth; \/\/ Length of infill lines\r\n                \r\n                const totalLineLength = (perimeterLength + infillLength) * layerCount;\r\n                const timePerLayer = (perimeterLength + infillLength) \/ (printSpeed * 60); \/\/ hours per layer\r\n                const totalTime = timePerLayer * layerCount;\r\n                \r\n                \/\/ Estimate material usage (improved with line width)\r\n                const volume = (size.x * size.y * size.z) \/ 1000; \/\/ cm\u00b3\r\n                const fillVolume = volume * (infillDensity \/ 100);\r\n                const wallVolume = (perimeterLength * layerCount * lineWidth * layerHeight) \/ 1000; \/\/ cm\u00b3\r\n                const totalVolume = fillVolume + wallVolume;\r\n                const materialWeight = totalVolume * 1.24; \/\/ PLA density\r\n                \r\n                \/\/ Update UI\r\n                document.getElementById('printTime').textContent = \r\n                    totalTime > 1 ? `${totalTime.toFixed(1)}h` : `${(totalTime * 60).toFixed(0)}m`;\r\n                document.getElementById('materialUsage').textContent = `${materialWeight.toFixed(1)} g`;\r\n                document.getElementById('lineWidth').textContent = `${lineWidth.toFixed(2)} mm`;\r\n                \r\n                \/\/ Update model info with bed fit status\r\n                const modelSizeElement = document.getElementById('modelSize');\r\n                if (modelSizeElement) {\r\n                    const sizeText = `${size.x.toFixed(1)}\u00d7${size.y.toFixed(1)}\u00d7${size.z.toFixed(1)} mm`;\r\n                    const statusIcon = bedFitStatus.fits ? '\u2705' : '\u26a0\ufe0f';\r\n                    modelSizeElement.innerHTML = `${sizeText} ${statusIcon}`;\r\n                    \r\n                    if (!bedFitStatus.fits) {\r\n                        modelSizeElement.style.color = '#ff6b6b';\r\n                        modelSizeElement.title = bedFitStatus.message;\r\n                    } else {\r\n                        modelSizeElement.style.color = '';\r\n                        modelSizeElement.title = `Model fits within bed: ${bedSizeX}\u00d7${bedSizeY}\u00d7${bedSizeZ}mm`;\r\n                    }\r\n                }\r\n                \r\n                this.log(`Updated estimates: Line width ${lineWidth.toFixed(2)}mm, ${wallLines} wall lines, Time: ${totalTime.toFixed(1)}h, Bed fit: ${bedFitStatus.fits ? 'Yes' : 'No'}`, bedFitStatus.fits ? 'info' : 'warning');\r\n            }\r\n\r\n            checkBedFit(modelSize, bedX, bedY, bedZ) {\r\n                const tolerance = 5; \/\/ 5mm safety margin\r\n                const fits = modelSize.x <= (bedX - tolerance) && \r\n                            modelSize.y <= (bedY - tolerance) && \r\n                            modelSize.z <= (bedZ - tolerance);\r\n                \r\n                let message = '';\r\n                if (!fits) {\r\n                    const issues = [];\r\n                    if (modelSize.x > (bedX - tolerance)) issues.push(`X: ${modelSize.x.toFixed(1)} > ${bedX - tolerance}`);\r\n                    if (modelSize.y > (bedY - tolerance)) issues.push(`Y: ${modelSize.y.toFixed(1)} > ${bedY - tolerance}`);\r\n                    if (modelSize.z > (bedZ - tolerance)) issues.push(`Z: ${modelSize.z.toFixed(1)} > ${bedZ - tolerance}`);\r\n                    message = `Model too large: ${issues.join(', ')}`;\r\n                }\r\n                \r\n                return { fits, message };\r\n            }\r\n\r\n            updateFileInfo(file) {\r\n                const fileInfo = document.getElementById('fileInfo');\r\n                fileInfo.innerHTML = `\r\n                    <div class=\"alert alert-info\">\r\n                        <strong>File:<\/strong> ${file.name}<br>\r\n                        <strong>Size:<\/strong> ${(file.size \/ 1024).toFixed(1)} KB\r\n                    <\/div>\r\n                `;\r\n            }\r\n\r\n            selectPreset(preset) {\r\n                \/\/ Update active preset button\r\n                document.querySelectorAll('.preset-btn').forEach(btn => {\r\n                    btn.classList.toggle('active', btn.dataset.preset === preset);\r\n                });\r\n                \r\n                const presets = {\r\n                    draft: { \r\n                        nozzleDiameter: 0.4,\r\n                        layerHeight: 0.3, \r\n                        infillDensity: 10, \r\n                        printSpeed: 80,\r\n                        wallThickness: 0.8\r\n                    },\r\n                    normal: { \r\n                        nozzleDiameter: 0.4,\r\n                        layerHeight: 0.2, \r\n                        infillDensity: 20, \r\n                        printSpeed: 60,\r\n                        wallThickness: 1.2\r\n                    },\r\n                    fine: { \r\n                        nozzleDiameter: 0.4,\r\n                        layerHeight: 0.1, \r\n                        infillDensity: 30, \r\n                        printSpeed: 40,\r\n                        wallThickness: 1.2\r\n                    },\r\n                    custom: {} \/\/ Keep current values\r\n                };\r\n                \r\n                const settings = presets[preset];\r\n                if (settings) {\r\n                    Object.keys(settings).forEach(key => {\r\n                        const element = document.getElementById(key);\r\n                        if (element) element.value = settings[key];\r\n                    });\r\n                    this.updateEstimates();\r\n                    this.log(`Applied ${preset} preset with nozzle diameter ${settings.nozzleDiameter || 'current'}mm`, 'info');\r\n                }\r\n            }\r\n\r\n            switchTab(tabName) {\r\n                \/\/ Update active tab button\r\n                document.querySelectorAll('.tab-btn').forEach(btn => {\r\n                    btn.classList.toggle('active', btn.dataset.tab === tabName);\r\n                });\r\n                \r\n                \/\/ Show\/hide tab content\r\n                document.querySelectorAll('.tab-content').forEach(content => {\r\n                    content.classList.toggle('active', content.id === tabName);\r\n                });\r\n            }\r\n\r\n            setViewMode(mode) {\r\n                this.viewMode = mode;\r\n                \r\n                \/\/ Update toolbar buttons\r\n                document.querySelectorAll('.toolbar-btn').forEach(btn => {\r\n                    btn.classList.remove('active');\r\n                });\r\n                \r\n                let activeButtonId;\r\n                switch(mode) {\r\n                    case 'solid':\r\n                        activeButtonId = 'viewSolid';\r\n                        break;\r\n                    case 'wireframe':\r\n                        activeButtonId = 'viewWireframe';\r\n                        break;\r\n                    case 'layers':\r\n                        activeButtonId = 'viewLayers';\r\n                        break;\r\n                    case 'gcode':\r\n                        activeButtonId = 'viewGcode';\r\n                        break;\r\n                    case 'supports':\r\n                        activeButtonId = 'viewSupports';\r\n                        break;\r\n                }\r\n                \r\n                if (activeButtonId) {\r\n                    document.getElementById(activeButtonId).classList.add('active');\r\n                }\r\n                \r\n                \/\/ Show\/hide appropriate panels\r\n                document.getElementById('layerPreview').style.display = \r\n                    (mode === 'layers') ? 'block' : 'none';\r\n                document.getElementById('gcodeViewer').style.display = \r\n                    (mode === 'gcode') ? 'block' : 'none';\r\n                \r\n                \/\/ Update 3D view\r\n                if (mode === 'gcode') {\r\n                    this.showGcodeVisualization();\r\n                } else {\r\n                    this.hideGcodeVisualization();\r\n                    \/\/ Update material if model exists\r\n                    if (this.currentMesh) {\r\n                        this.currentMesh.material = this.createMaterial();\r\n                    }\r\n                }\r\n                \r\n                this.log(`View mode: ${mode}`, 'info');\r\n            }\r\n\r\n            resetView() {\r\n                this.camera.position.set(50, 50, 50);\r\n                this.camera.lookAt(0, 0, 0);\r\n            }\r\n\r\n            fitToView() {\r\n                if (!this.currentModel) return;\r\n                \r\n                const geometry = this.currentModel.geometry;\r\n                geometry.computeBoundingBox();\r\n                const box = geometry.boundingBox;\r\n                const size = box.getSize(new THREE.Vector3());\r\n                const maxDim = Math.max(size.x, size.y, size.z);\r\n                \r\n                const distance = maxDim * 2;\r\n                this.camera.position.normalize().multiplyScalar(distance);\r\n                this.camera.lookAt(0, 0, 0);\r\n            }\r\n\r\n            toggleAxes() {\r\n                this.axesHelper.visible = !this.axesHelper.visible;\r\n                document.getElementById('toggleAxes').classList.toggle('active', this.axesHelper.visible);\r\n            }\r\n\r\n            updateMaterialSettings(material) {\r\n                const materials = {\r\n                    pla: { nozzleTemp: 200, bedTemp: 60 },\r\n                    abs: { nozzleTemp: 240, bedTemp: 100 },\r\n                    petg: { nozzleTemp: 230, bedTemp: 80 },\r\n                    tpu: { nozzleTemp: 220, bedTemp: 40 }\r\n                };\r\n                \r\n                const settings = materials[material];\r\n                if (settings) {\r\n                    document.getElementById('nozzleTemp').value = settings.nozzleTemp;\r\n                    document.getElementById('bedTemp').value = settings.bedTemp;\r\n                }\r\n            }\r\n\r\n            async sliceModel() {\r\n                if (!this.currentModel) {\r\n                    this.showAlert('No model loaded', 'error');\r\n                    return;\r\n                }\r\n                \r\n                this.log('Starting slicing process...', 'info');\r\n                this.showProgress();\r\n                \r\n                try {\r\n                    \/\/ Get current settings including nozzle diameter\r\n                    const nozzleDiameter = parseFloat(document.getElementById('nozzleDiameter').value);\r\n                    this.log(`Using nozzle diameter: ${nozzleDiameter}mm for slicing`, 'info');\r\n                    \r\n                    \/\/ Simulate slicing process with proper async handling\r\n                    const steps = [\r\n                        'Analyzing geometry...',\r\n                        `Configuring ${nozzleDiameter}mm nozzle settings...`,\r\n                        'Generating layers...',\r\n                        'Calculating toolpaths...',\r\n                        'Generating supports...',\r\n                        'Creating G-code...'\r\n                    ];\r\n                    \r\n                    for (let i = 0; i < steps.length; i++) {\r\n                        this.updateProgress((i \/ steps.length) * 100, steps[i]);\r\n                        this.log(steps[i], 'info');\r\n                        \r\n                        \/\/ Use setTimeout wrapped in Promise for proper async delay\r\n                        await new Promise(resolve => setTimeout(resolve, 600));\r\n                    }\r\n                    \r\n                    \/\/ Final progress update\r\n                    this.updateProgress(100, 'Finalizing...');\r\n                    await new Promise(resolve => setTimeout(resolve, 300));\r\n                    \r\n                    \/\/ Generate G-code with nozzle diameter\r\n                    this.gcodeData = this.generateMockGcode();\r\n                    this.generateLayers();\r\n                    this.parseGcodeForVisualization();\r\n                    \r\n                    \/\/ Success state\r\n                    this.hideProgress();\r\n                    document.getElementById('downloadBtn').disabled = false;\r\n                    document.getElementById('layerPreview').style.display = 'block';\r\n                    this.setupLayerSlider();\r\n                    \r\n                    this.showAlert('Model sliced successfully! \ud83c\udf89', 'success');\r\n                    this.log(`Slicing completed successfully with ${nozzleDiameter}mm nozzle`, 'success');\r\n                    \r\n                } catch (error) {\r\n                    this.hideProgress();\r\n                    this.log(`Slicing failed: ${error.message}`, 'error');\r\n                    this.showAlert('Slicing failed. Please try again.', 'error');\r\n                }\r\n            }\r\n\r\n            generateLayers() {\r\n                if (!this.currentModel) return;\r\n                \r\n                const layerHeight = parseFloat(document.getElementById('layerHeight').value);\r\n                const geometry = this.currentModel.geometry;\r\n                geometry.computeBoundingBox();\r\n                const size = geometry.boundingBox.getSize(new THREE.Vector3());\r\n                \r\n                const layerCount = Math.ceil(size.z \/ layerHeight);\r\n                this.layers = [];\r\n                \r\n                \/\/ Generate mock layer data\r\n                for (let i = 0; i < layerCount; i++) {\r\n                    this.layers.push({\r\n                        number: i + 1,\r\n                        height: (i + 1) * layerHeight,\r\n                        paths: this.generateLayerPaths(i, layerCount)\r\n                    });\r\n                }\r\n                \r\n                document.getElementById('totalLayers').textContent = layerCount;\r\n                document.getElementById('currentLayer').textContent = '1';\r\n            }\r\n\r\n            generateLayerPaths(layerIndex, totalLayers) {\r\n                \/\/ Generate some mock toolpaths for visualization\r\n                const paths = [];\r\n                const centerX = 0;\r\n                const centerY = 0;\r\n                const radius = 15 - (layerIndex \/ totalLayers) * 5; \/\/ Tapered shape\r\n                \r\n                \/\/ Outer perimeter\r\n                for (let angle = 0; angle < Math.PI * 2; angle += 0.1) {\r\n                    paths.push({\r\n                        x: centerX + Math.cos(angle) * radius,\r\n                        y: centerY + Math.sin(angle) * radius,\r\n                        type: 'perimeter'\r\n                    });\r\n                }\r\n                \r\n                \/\/ Infill lines\r\n                for (let x = -radius; x < radius; x += 2) {\r\n                    paths.push({\r\n                        x: centerX + x,\r\n                        y: centerY - radius * 0.8,\r\n                        type: 'infill'\r\n                    });\r\n                    paths.push({\r\n                        x: centerX + x,\r\n                        y: centerY + radius * 0.8,\r\n                        type: 'infill'\r\n                    });\r\n                }\r\n                \r\n                return paths;\r\n            }\r\n\r\n            setupLayerSlider() {\r\n                const slider = document.getElementById('layerSlider');\r\n                slider.max = this.layers.length - 1;\r\n                slider.value = 0;\r\n                \r\n                slider.oninput = (e) => {\r\n                    const layerIndex = parseInt(e.target.value);\r\n                    this.showLayer(layerIndex);\r\n                };\r\n                \r\n                \/\/ Show first layer\r\n                this.showLayer(0);\r\n            }\r\n\r\n            showLayer(layerIndex) {\r\n                if (!this.layers[layerIndex]) return;\r\n                \r\n                const layer = this.layers[layerIndex];\r\n                document.getElementById('currentLayer').textContent = layer.number;\r\n                document.getElementById('layerHeight').textContent = layer.height.toFixed(2);\r\n                \r\n                \/\/ In a real implementation, this would visualize the actual layer\r\n                this.log(`Viewing layer ${layer.number} at height ${layer.height.toFixed(2)}mm`, 'info');\r\n            }\r\n\r\n            generateMockGcode() {\r\n                const nozzleDiameter = parseFloat(document.getElementById('nozzleDiameter').value) || 0.4;\r\n                const layerHeight = parseFloat(document.getElementById('layerHeight').value) || 0.2;\r\n                const nozzleTemp = parseFloat(document.getElementById('nozzleTemp').value) || 200;\r\n                const bedTemp = parseFloat(document.getElementById('bedTemp').value) || 60;\r\n                const printSpeed = parseFloat(document.getElementById('printSpeed').value) * 60; \/\/ mm\/min\r\n                const wallThickness = parseFloat(document.getElementById('wallThickness').value) || 1.2;\r\n                const infillDensity = parseFloat(document.getElementById('infillDensity').value) || 20;\r\n                \r\n                \/\/ Get bed size settings\r\n                const bedSizeX = parseFloat(document.getElementById('BedSizeX').value) || 300;\r\n                const bedSizeY = parseFloat(document.getElementById('BedSizeY').value) || 300;\r\n                const bedSizeZ = parseFloat(document.getElementById('BedSizeZ').value) || 300;\r\n                \r\n                \/\/ Calculate line width based on nozzle diameter\r\n                const lineWidth = nozzleDiameter * 1.2;\r\n                const wallLines = Math.ceil(wallThickness \/ lineWidth);\r\n                \r\n                let gcode = `;Generated by 3D Slicer Pro\r\n;Printer bed size: ${bedSizeX}x${bedSizeY}x${bedSizeZ}mm\r\n;Print area: X(0-${bedSizeX}) Y(0-${bedSizeY}) Z(0-${bedSizeZ})\r\n;Nozzle diameter: ${nozzleDiameter}mm\r\n;Line width: ${lineWidth.toFixed(2)}mm\r\n;Wall lines: ${wallLines}\r\n;Layer height: ${layerHeight}mm\r\n;Nozzle temperature: ${nozzleTemp}\u00b0C\r\n;Bed temperature: ${bedTemp}\u00b0C\r\n;Print speed: ${printSpeed} mm\/min\r\n\r\n; Start G-code\r\nG28 ; Home all axes\r\nM190 S${bedTemp} ; Set bed temperature and wait\r\nM109 S${nozzleTemp} ; Set nozzle temperature and wait\r\n\r\n; Set bed size limits for safety\r\nM211 S1 ; Enable software endstops\r\nG92 E0 ; Reset extruder position\r\n\r\n; Prime nozzle at bed edge\r\nG1 X5 Y5 Z0.3 F3000 ; Move to prime position\r\nG1 X${Math.min(bedSizeX - 5, 50)} E5 F300 ; Prime line 1\r\nG1 Y7 ; Move Y slightly\r\nG1 X5 E10 F300 ; Prime line 2\r\nG92 E0 ; Reset extruder\r\nG1 Z2 F3000 ; Lift nozzle\r\n\r\nG1 F${printSpeed} ; Set feedrate\r\n\r\n`;\r\n\r\n                \/\/ Generate layers based on current model\r\n                if (this.currentModel) {\r\n                    const geometry = this.currentModel.geometry;\r\n                    geometry.computeBoundingBox();\r\n                    const size = geometry.boundingBox.getSize(new THREE.Vector3());\r\n                    const layerCount = Math.ceil(size.z \/ layerHeight);\r\n                    \r\n                    \/\/ Check if model fits within bed bounds\r\n                    const modelCenterX = bedSizeX \/ 2;\r\n                    const modelCenterY = bedSizeY \/ 2;\r\n                    \r\n                    if (size.x > bedSizeX || size.y > bedSizeY || size.z > bedSizeZ) {\r\n                        gcode += `; WARNING: Model dimensions (${size.x.toFixed(1)}x${size.y.toFixed(1)}x${size.z.toFixed(1)}mm) exceed bed size!\\n`;\r\n                        this.log(`Warning: Model may not fit on bed (${bedSizeX}x${bedSizeY}x${bedSizeZ}mm)`, 'warning');\r\n                    }\r\n                    \r\n                    gcode += `; Model centered at X${modelCenterX} Y${modelCenterY}\\n`;\r\n                    gcode += `; Model size: ${size.x.toFixed(1)}x${size.y.toFixed(1)}x${size.z.toFixed(1)}mm\\n`;\r\n                    \r\n                    for (let layer = 0; layer < layerCount; layer++) {\r\n                        const z = layer * layerHeight;\r\n                        \r\n                        \/\/ Safety check for Z height\r\n                        if (z > bedSizeZ) {\r\n                            gcode += `; WARNING: Layer ${layer + 1} at Z${z.toFixed(2)} exceeds bed height ${bedSizeZ}mm\\n`;\r\n                            break;\r\n                        }\r\n                        \r\n                        gcode += `\\n; Layer ${layer + 1} at Z=${z.toFixed(2)}mm\\n`;\r\n                        gcode += `G1 Z${z.toFixed(2)} F300\\n`;\r\n                        \r\n                        \/\/ Generate perimeter with multiple wall lines\r\n                        const radius = Math.max(5, 20 - (layer \/ layerCount) * 10);\r\n                        const centerX = modelCenterX;\r\n                        const centerY = modelCenterY;\r\n                        \r\n                        \/\/ Ensure the generated paths stay within bed bounds\r\n                        const maxRadius = Math.min(radius, \r\n                            Math.min(modelCenterX - 5, bedSizeX - modelCenterX - 5),\r\n                            Math.min(modelCenterY - 5, bedSizeY - modelCenterY - 5)\r\n                        );\r\n                        \r\n                        \/\/ Multiple wall lines based on wall thickness and nozzle diameter\r\n                        for (let wallLine = 0; wallLine < wallLines; wallLine++) {\r\n                            const wallRadius = maxRadius - (wallLine * lineWidth);\r\n                            if (wallRadius <= 0) break;\r\n                            \r\n                            gcode += `; Wall line ${wallLine + 1}\\n`;\r\n                            \r\n                            \/\/ Outer perimeter\r\n                            for (let angle = 0; angle <= Math.PI * 2; angle += 0.3) {\r\n                                const x = centerX + Math.cos(angle) * wallRadius;\r\n                                const y = centerY + Math.sin(angle) * wallRadius;\r\n                                \r\n                                \/\/ Safety bounds check\r\n                                const safeX = Math.max(5, Math.min(bedSizeX - 5, x));\r\n                                const safeY = Math.max(5, Math.min(bedSizeY - 5, y));\r\n                                \r\n                                const e = (layer * 50) + (wallLine * 25) + (angle \/ (Math.PI * 2)) * 25;\r\n                                \r\n                                if (angle === 0) {\r\n                                    gcode += `G1 X${safeX.toFixed(2)} Y${safeY.toFixed(2)} F3000\\n`;\r\n                                } else {\r\n                                    gcode += `G1 X${safeX.toFixed(2)} Y${safeY.toFixed(2)} E${e.toFixed(2)}\\n`;\r\n                                }\r\n                            }\r\n                        }\r\n                        \r\n                        \/\/ Inner infill with spacing based on nozzle diameter\r\n                        if (infillDensity > 0) {\r\n                            const infillSpacing = lineWidth * (100 \/ infillDensity);\r\n                            const innerRadius = maxRadius - (wallLines * lineWidth);\r\n                            \r\n                            gcode += `; Infill (${infillDensity}%, spacing: ${infillSpacing.toFixed(2)}mm)\\n`;\r\n                            \r\n                            for (let x = -innerRadius + infillSpacing; x < innerRadius; x += infillSpacing) {\r\n                                const maxY = Math.sqrt(Math.max(0, innerRadius * innerRadius - x * x));\r\n                                if (maxY > 0) {\r\n                                    const y1 = centerY - maxY;\r\n                                    const y2 = centerY + maxY;\r\n                                    \r\n                                    \/\/ Safety bounds check for infill\r\n                                    const safeX = Math.max(5, Math.min(bedSizeX - 5, centerX + x));\r\n                                    const safeY1 = Math.max(5, Math.min(bedSizeY - 5, y1));\r\n                                    const safeY2 = Math.max(5, Math.min(bedSizeY - 5, y2));\r\n                                    \r\n                                    const e1 = (layer * 50) + (wallLines * 25) + (x + innerRadius) * 2;\r\n                                    const e2 = e1 + Math.abs(safeY2 - safeY1) * 0.1;\r\n                                    \r\n                                    gcode += `G1 X${safeX.toFixed(2)} Y${safeY1.toFixed(2)} F3000\\n`;\r\n                                    gcode += `G1 X${safeX.toFixed(2)} Y${safeY2.toFixed(2)} E${e2.toFixed(2)}\\n`;\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                \r\n                gcode += `\r\n; End G-code with bed size safety\r\nG1 E-5 F300 ; Retract\r\nG1 Z${Math.min(bedSizeZ - 10, 50)} F3000 ; Safe Z height\r\nG1 X5 Y5 F3000 ; Move to safe position\r\nM104 S0 ; Turn off nozzle\r\nM140 S0 ; Turn off bed\r\nM84 ; Disable motors\r\n; Print completed within bed bounds: ${bedSizeX}x${bedSizeY}x${bedSizeZ}mm\r\n`;\r\n                \r\n                return gcode;\r\n            }\r\n\r\n            parseGcodeForVisualization() {\r\n                if (!this.gcodeData) {\r\n                    this.log('No G-code data to parse', 'warning');\r\n                    return;\r\n                }\r\n                \r\n                this.log('Starting G-code parsing for visualization...', 'info');\r\n                this.gcodeLines = [];\r\n                const lines = this.gcodeData.split('\\n');\r\n                let currentPos = { x: 0, y: 0, z: 0, e: 0 };\r\n                let currentLayer = 0;\r\n                let layerPaths = [];\r\n                let totalMoves = 0;\r\n                \r\n                for (let lineNum = 0; lineNum < lines.length; lineNum++) {\r\n                    let line = lines[lineNum].trim();\r\n                    if (line.startsWith(';') || line === '') continue;\r\n                    \r\n                    \/\/ Parse G1 moves\r\n                    if (line.startsWith('G1')) {\r\n                        const newPos = { ...currentPos };\r\n                        let hasMove = false;\r\n                        let hasExtrusion = false;\r\n                        \r\n                        \/\/ Parse coordinates\r\n                        const xMatch = line.match(\/X([-\\d.]+)\/i);\r\n                        const yMatch = line.match(\/Y([-\\d.]+)\/i);\r\n                        const zMatch = line.match(\/Z([-\\d.]+)\/i);\r\n                        const eMatch = line.match(\/E([-\\d.]+)\/i);\r\n                        \r\n                        if (xMatch) { \r\n                            newPos.x = parseFloat(xMatch[1]); \r\n                            hasMove = true; \r\n                        }\r\n                        if (yMatch) { \r\n                            newPos.y = parseFloat(yMatch[1]); \r\n                            hasMove = true; \r\n                        }\r\n                        if (zMatch) { \r\n                            const newZ = parseFloat(zMatch[1]);\r\n                            if (newZ > currentPos.z + 0.01) { \/\/ Layer change detected\r\n                                if (layerPaths.length > 0) {\r\n                                    this.gcodeLines.push({\r\n                                        layer: currentLayer,\r\n                                        paths: [...layerPaths],\r\n                                        z: currentPos.z\r\n                                    });\r\n                                    this.log(`Layer ${currentLayer} completed with ${layerPaths.length} paths`, 'info');\r\n                                }\r\n                                currentLayer++;\r\n                                layerPaths = [];\r\n                            }\r\n                            newPos.z = newZ;\r\n                        }\r\n                        if (eMatch) { \r\n                            const newE = parseFloat(eMatch[1]);\r\n                            hasExtrusion = newE > currentPos.e;\r\n                            newPos.e = newE;\r\n                        }\r\n                        \r\n                        \/\/ Add path if there's XY movement\r\n                        if (hasMove && (Math.abs(newPos.x - currentPos.x) > 0.01 || Math.abs(newPos.y - currentPos.y) > 0.01)) {\r\n                            const pathType = hasExtrusion ? 'extrude' : 'travel';\r\n                            layerPaths.push({\r\n                                from: { ...currentPos },\r\n                                to: { ...newPos },\r\n                                type: pathType\r\n                            });\r\n                            totalMoves++;\r\n                        }\r\n                        \r\n                        currentPos = newPos;\r\n                    }\r\n                }\r\n                \r\n                \/\/ Add final layer\r\n                if (layerPaths.length > 0) {\r\n                    this.gcodeLines.push({\r\n                        layer: currentLayer,\r\n                        paths: layerPaths,\r\n                        z: currentPos.z\r\n                    });\r\n                    this.log(`Final layer ${currentLayer} completed with ${layerPaths.length} paths`, 'info');\r\n                }\r\n                \r\n                this.log(`G-code parsing completed: ${this.gcodeLines.length} layers, ${totalMoves} total moves`, 'success');\r\n                this.setupGcodeViewer();\r\n            }\r\n\r\n            setupGcodeViewer() {\r\n                this.log('Setting up G-code viewer controls...', 'info');\r\n                \r\n                const slider = document.getElementById('gcodeLayerSlider');\r\n                const totalLayers = this.gcodeLines.length;\r\n                \r\n                if (slider) {\r\n                    slider.max = Math.max(0, totalLayers - 1);\r\n                    slider.value = 0;\r\n                    this.log(`G-code slider configured: 0 to ${slider.max}`, 'info');\r\n                }\r\n                \r\n                \/\/ Update UI elements\r\n                const totalLayersSpan = document.getElementById('gcodeTotalLayers');\r\n                const currentLayerSpan = document.getElementById('gcodeCurrentLayer');\r\n                const progressSpan = document.getElementById('gcodeProgress');\r\n                \r\n                if (totalLayersSpan) totalLayersSpan.textContent = totalLayers;\r\n                if (currentLayerSpan) currentLayerSpan.textContent = totalLayers > 0 ? '1' : '0';\r\n                if (progressSpan) progressSpan.textContent = '0';\r\n                \r\n                this.log(`G-code viewer setup complete: ${totalLayers} layers ready`, 'success');\r\n            }\r\n\r\n            showGcodeVisualization() {\r\n                this.log('Attempting to show G-code visualization...', 'info');\r\n                \r\n                if (!this.gcodeLines || this.gcodeLines.length === 0) {\r\n                    this.log('No G-code data available for visualization', 'warning');\r\n                    this.showAlert('No G-code data available. Please slice the model first.', 'error');\r\n                    return;\r\n                }\r\n                \r\n                \/\/ Hide original model\r\n                if (this.currentMesh) {\r\n                    this.currentMesh.visible = false;\r\n                    this.log('Hidden original model mesh', 'info');\r\n                }\r\n                \r\n                \/\/ Create G-code visualization\r\n                this.createGcodeVisualization();\r\n                this.log(`G-code visualization activated with ${this.gcodeLines.length} layers`, 'success');\r\n            }\r\n\r\n            hideGcodeVisualization() {\r\n                this.log('Hiding G-code visualization...', 'info');\r\n                \r\n                \/\/ Show original model\r\n                if (this.currentMesh) {\r\n                    this.currentMesh.visible = true;\r\n                    this.log('Restored original model mesh', 'info');\r\n                }\r\n                \r\n                \/\/ Remove G-code visualization\r\n                if (this.gcodeViewer) {\r\n                    this.scene.remove(this.gcodeViewer);\r\n                    this.gcodeViewer = null;\r\n                    this.log('Removed G-code viewer from scene', 'info');\r\n                }\r\n            }\r\n\r\n            createGcodeVisualization() {\r\n                this.log('Creating G-code visualization...', 'info');\r\n                \r\n                if (this.gcodeViewer) {\r\n                    this.scene.remove(this.gcodeViewer);\r\n                }\r\n                \r\n                this.gcodeViewer = new THREE.Group();\r\n                let totalPaths = 0;\r\n                \r\n                \/\/ Get nozzle diameter for line width visualization\r\n                const nozzleDiameter = parseFloat(document.getElementById('nozzleDiameter').value) || 0.4;\r\n                const lineWidth = nozzleDiameter * 1.2;\r\n                \r\n                \/\/ Create visualization for all layers\r\n                this.gcodeLines.forEach((layerData, layerIndex) => {\r\n                    const layerGroup = new THREE.Group();\r\n                    layerGroup.userData.layerIndex = layerIndex;\r\n                    layerGroup.name = `Layer_${layerIndex}`;\r\n                    \r\n                    \/\/ Create paths for this layer\r\n                    layerData.paths.forEach(path => {\r\n                        const geometry = new THREE.BufferGeometry();\r\n                        const positions = [\r\n                            path.from.x, path.from.y, path.from.z,\r\n                            path.to.x, path.to.y, path.to.z\r\n                        ];\r\n                        geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));\r\n                        \r\n                        \/\/ Use different colors and widths based on path type and nozzle diameter\r\n                        const material = new THREE.LineBasicMaterial({\r\n                            color: path.type === 'extrude' ? 0x00ff00 : 0xff6666,\r\n                            linewidth: path.type === 'extrude' ? Math.max(1, lineWidth) : 1,\r\n                            transparent: true,\r\n                            opacity: path.type === 'extrude' ? 1.0 : 0.5\r\n                        });\r\n                        \r\n                        const line = new THREE.Line(geometry, material);\r\n                        layerGroup.add(line);\r\n                        totalPaths++;\r\n                    });\r\n                    \r\n                    \/\/ Initially hide all layers except first\r\n                    layerGroup.visible = layerIndex === 0;\r\n                    this.gcodeViewer.add(layerGroup);\r\n                });\r\n                \r\n                this.scene.add(this.gcodeViewer);\r\n                this.log(`Created ${totalPaths} toolpath lines with ${lineWidth.toFixed(2)}mm line width across ${this.gcodeLines.length} layers`, 'success');\r\n                \r\n                \/\/ Show first layer\r\n                this.showGcodeLayer(0);\r\n            }\r\n\r\n            showGcodeLayer(layerIndex) {\r\n                if (!this.gcodeViewer || !this.gcodeLines || layerIndex >= this.gcodeLines.length) {\r\n                    this.log(`Cannot show layer ${layerIndex}: invalid index or no viewer`, 'warning');\r\n                    return;\r\n                }\r\n                \r\n                \/\/ Hide all layers first\r\n                this.gcodeViewer.children.forEach(child => {\r\n                    child.visible = false;\r\n                });\r\n                \r\n                \/\/ Show layers up to and including selected index\r\n                for (let i = 0; i <= layerIndex; i++) {\r\n                    if (this.gcodeViewer.children[i]) {\r\n                        this.gcodeViewer.children[i].visible = true;\r\n                    }\r\n                }\r\n                \r\n                \/\/ Update UI elements safely\r\n                const currentLayerSpan = document.getElementById('gcodeCurrentLayer');\r\n                const progressSpan = document.getElementById('gcodeProgress');\r\n                \r\n                if (currentLayerSpan) {\r\n                    currentLayerSpan.textContent = layerIndex + 1;\r\n                }\r\n                \r\n                if (progressSpan) {\r\n                    const progress = ((layerIndex + 1) \/ this.gcodeLines.length * 100).toFixed(1);\r\n                    progressSpan.textContent = progress;\r\n                }\r\n                \r\n                this.log(`Viewing G-code layers 1-${layerIndex + 1} (${((layerIndex + 1) \/ this.gcodeLines.length * 100).toFixed(1)}%)`, 'info');\r\n            }\r\n\r\n            playGcode() {\r\n                if (!this.gcodeLines || this.gcodeLines.length === 0) {\r\n                    this.showAlert('No G-code data available for playback', 'error');\r\n                    return;\r\n                }\r\n                \r\n                this.isPlaying = true;\r\n                const playBtn = document.getElementById('playGcode');\r\n                if (playBtn) {\r\n                    playBtn.style.opacity = '0.5';\r\n                    playBtn.textContent = '\u23f8\ufe0f Playing...';\r\n                }\r\n                \r\n                this.log(`Starting G-code playback at ${this.playbackSpeed}x speed`, 'info');\r\n                \r\n                const animate = () => {\r\n                    if (!this.isPlaying) return;\r\n                    \r\n                    const slider = document.getElementById('gcodeLayerSlider');\r\n                    if (!slider) return;\r\n                    \r\n                    let currentLayer = parseInt(slider.value);\r\n                    \r\n                    if (currentLayer < this.gcodeLines.length - 1) {\r\n                        currentLayer++;\r\n                        slider.value = currentLayer;\r\n                        this.showGcodeLayer(currentLayer);\r\n                        \r\n                        setTimeout(animate, Math.max(100, 1000 \/ this.playbackSpeed));\r\n                    } else {\r\n                        this.isPlaying = false;\r\n                        if (playBtn) {\r\n                            playBtn.style.opacity = '1';\r\n                            playBtn.textContent = '\u25b6\ufe0f Play';\r\n                        }\r\n                        this.log('G-code playback completed', 'success');\r\n                    }\r\n                };\r\n                \r\n                setTimeout(animate, Math.max(100, 1000 \/ this.playbackSpeed));\r\n            }\r\n\r\n            pauseGcode() {\r\n                this.isPlaying = false;\r\n                const playBtn = document.getElementById('playGcode');\r\n                if (playBtn) {\r\n                    playBtn.style.opacity = '1';\r\n                    playBtn.textContent = '\u25b6\ufe0f Play';\r\n                }\r\n                this.log('G-code playback paused', 'info');\r\n            }\r\n\r\n            resetGcode() {\r\n                this.isPlaying = false;\r\n                const playBtn = document.getElementById('playGcode');\r\n                const slider = document.getElementById('gcodeLayerSlider');\r\n                \r\n                if (playBtn) {\r\n                    playBtn.style.opacity = '1';\r\n                    playBtn.textContent = '\u25b6\ufe0f Play';\r\n                }\r\n                \r\n                if (slider) {\r\n                    slider.value = 0;\r\n                    this.showGcodeLayer(0);\r\n                }\r\n                \r\n                this.log('G-code playback reset to layer 1', 'info');\r\n            }\r\n\r\n            downloadGcode() {\r\n                if (!this.gcodeData) {\r\n                    this.showAlert('No G-code available. Please slice the model first or load a G-code file.', 'error');\r\n                    return;\r\n                }\r\n                \r\n                \/\/ Determine filename\r\n                let filename;\r\n                if (this.currentModel && this.currentModel.filename) {\r\n                    filename = this.currentModel.filename.replace(\/\\.[^\/.]+$\/, \"\") + '.gcode';\r\n                } else {\r\n                    filename = 'sliced_model.gcode';\r\n                }\r\n                \r\n                const blob = new Blob([this.gcodeData], { type: 'text\/plain' });\r\n                const url = URL.createObjectURL(blob);\r\n                \r\n                const a = document.createElement('a');\r\n                a.href = url;\r\n                a.download = filename;\r\n                a.click();\r\n                \r\n                URL.revokeObjectURL(url);\r\n                this.log(`G-code downloaded: ${filename}`, 'success');\r\n            }\r\n\r\n            showProgress() {\r\n                const progressContainer = document.getElementById('progressContainer');\r\n                const sliceBtn = document.getElementById('sliceBtn');\r\n                \r\n                progressContainer.style.display = 'block';\r\n                sliceBtn.disabled = true;\r\n                sliceBtn.textContent = '\u23f3 Slicing...';\r\n                \r\n                \/\/ Reset progress bar\r\n                this.updateProgress(0, 'Initializing...');\r\n            }\r\n\r\n            hideProgress() {\r\n                const progressContainer = document.getElementById('progressContainer');\r\n                const sliceBtn = document.getElementById('sliceBtn');\r\n                \r\n                progressContainer.style.display = 'none';\r\n                sliceBtn.disabled = false;\r\n                sliceBtn.textContent = '\ud83d\ude80 Slice Model';\r\n            }\r\n\r\n            updateProgress(percent, text) {\r\n                const progressFill = document.getElementById('progressFill');\r\n                const progressText = document.getElementById('progressText');\r\n                \r\n                if (progressFill && progressText) {\r\n                    progressFill.style.width = Math.min(percent, 100) + '%';\r\n                    progressText.textContent = text || `${Math.round(percent)}%`;\r\n                }\r\n            }\r\n\r\n            showAlert(message, type) {\r\n                const alertDiv = document.createElement('div');\r\n                alertDiv.className = `alert alert-${type}`;\r\n                alertDiv.textContent = message;\r\n                \r\n                const fileInfo = document.getElementById('fileInfo');\r\n                fileInfo.appendChild(alertDiv);\r\n                \r\n                setTimeout(() => {\r\n                    if (alertDiv.parentNode) {\r\n                        alertDiv.parentNode.removeChild(alertDiv);\r\n                    }\r\n                }, 5000);\r\n            }\r\n\r\n            log(message, type = 'info') {\r\n                const logArea = document.getElementById('logArea');\r\n                const timestamp = new Date().toLocaleTimeString();\r\n                const logEntry = document.createElement('div');\r\n                logEntry.className = `log-entry ${type}`;\r\n                logEntry.textContent = `[${timestamp}] ${message}`;\r\n                \r\n                logArea.appendChild(logEntry);\r\n                logArea.scrollTop = logArea.scrollHeight;\r\n                \r\n                \/\/ Update status\r\n                document.getElementById('statusLeft').textContent = message;\r\n            }\r\n\r\n            sleep(ms) {\r\n                return new Promise(resolve => setTimeout(resolve, ms));\r\n            }\r\n\r\n            onWindowResize() {\r\n                const container = document.getElementById('viewer3d');\r\n                if (container && this.camera && this.renderer) {\r\n                    this.camera.aspect = container.clientWidth \/ container.clientHeight;\r\n                    this.camera.updateProjectionMatrix();\r\n                    this.renderer.setSize(container.clientWidth, container.clientHeight);\r\n                }\r\n            }\r\n\r\n            animate() {\r\n                requestAnimationFrame(() => this.animate());\r\n                \r\n                \/\/ Rotate model slightly if no user interaction\r\n                if (this.currentMesh && !this.isMouseDown) {\r\n                    this.currentMesh.rotation.z += 0.002;\r\n                }\r\n                \r\n                this.renderer.render(this.scene, this.camera);\r\n            }\r\n        }\r\n\r\n        \/\/ Initialize the application\r\n        const app = new SlicerApp();\r\n    <\/script>\r\n<\/body>\r\n<\/html>\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-1548","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.3.1 (Yoast SEO v25.3.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Stl to Gcode - 3dgarage<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Stl to Gcode\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/\" \/>\n<meta property=\"og:site_name\" content=\"3dgarage\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-22T13:14:56+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/\",\"url\":\"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/\",\"name\":\"Stl to Gcode - 3dgarage\",\"isPartOf\":{\"@id\":\"https:\/\/www.shotland.co.il\/3dgarage\/#website\"},\"datePublished\":\"2025-06-02T17:28:52+00:00\",\"dateModified\":\"2025-06-22T13:14:56+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.shotland.co.il\/3dgarage\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Stl to Gcode\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.shotland.co.il\/3dgarage\/#website\",\"url\":\"https:\/\/www.shotland.co.il\/3dgarage\/\",\"name\":\"3dgarage\",\"description\":\"Practical 3d model designs that solve real-world problems.\",\"publisher\":{\"@id\":\"https:\/\/www.shotland.co.il\/3dgarage\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.shotland.co.il\/3dgarage\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.shotland.co.il\/3dgarage\/#organization\",\"name\":\"3dgarage\",\"url\":\"https:\/\/www.shotland.co.il\/3dgarage\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.shotland.co.il\/3dgarage\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.shotland.co.il\/3dgarage\/wp-content\/uploads\/2024\/03\/3dgarage-3.png\",\"contentUrl\":\"https:\/\/www.shotland.co.il\/3dgarage\/wp-content\/uploads\/2024\/03\/3dgarage-3.png\",\"width\":257,\"height\":47,\"caption\":\"3dgarage\"},\"image\":{\"@id\":\"https:\/\/www.shotland.co.il\/3dgarage\/#\/schema\/logo\/image\/\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Stl to Gcode - 3dgarage","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/","og_locale":"en_US","og_type":"article","og_title":"Stl to Gcode","og_url":"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/","og_site_name":"3dgarage","article_modified_time":"2025-06-22T13:14:56+00:00","twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/","url":"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/","name":"Stl to Gcode - 3dgarage","isPartOf":{"@id":"https:\/\/www.shotland.co.il\/3dgarage\/#website"},"datePublished":"2025-06-02T17:28:52+00:00","dateModified":"2025-06-22T13:14:56+00:00","breadcrumb":{"@id":"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.shotland.co.il\/3dgarage\/stl-to-gcode\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.shotland.co.il\/3dgarage\/"},{"@type":"ListItem","position":2,"name":"Stl to Gcode"}]},{"@type":"WebSite","@id":"https:\/\/www.shotland.co.il\/3dgarage\/#website","url":"https:\/\/www.shotland.co.il\/3dgarage\/","name":"3dgarage","description":"Practical 3d model designs that solve real-world problems.","publisher":{"@id":"https:\/\/www.shotland.co.il\/3dgarage\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.shotland.co.il\/3dgarage\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.shotland.co.il\/3dgarage\/#organization","name":"3dgarage","url":"https:\/\/www.shotland.co.il\/3dgarage\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.shotland.co.il\/3dgarage\/#\/schema\/logo\/image\/","url":"https:\/\/www.shotland.co.il\/3dgarage\/wp-content\/uploads\/2024\/03\/3dgarage-3.png","contentUrl":"https:\/\/www.shotland.co.il\/3dgarage\/wp-content\/uploads\/2024\/03\/3dgarage-3.png","width":257,"height":47,"caption":"3dgarage"},"image":{"@id":"https:\/\/www.shotland.co.il\/3dgarage\/#\/schema\/logo\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/www.shotland.co.il\/3dgarage\/wp-json\/wp\/v2\/pages\/1548","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.shotland.co.il\/3dgarage\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.shotland.co.il\/3dgarage\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.shotland.co.il\/3dgarage\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.shotland.co.il\/3dgarage\/wp-json\/wp\/v2\/comments?post=1548"}],"version-history":[{"count":27,"href":"https:\/\/www.shotland.co.il\/3dgarage\/wp-json\/wp\/v2\/pages\/1548\/revisions"}],"predecessor-version":[{"id":1645,"href":"https:\/\/www.shotland.co.il\/3dgarage\/wp-json\/wp\/v2\/pages\/1548\/revisions\/1645"}],"wp:attachment":[{"href":"https:\/\/www.shotland.co.il\/3dgarage\/wp-json\/wp\/v2\/media?parent=1548"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}