Function bodies 257 total
SupexRuntime::Export.export_scene method · ruby · L17-L34 (18 LOC)runtime/src/supex_runtime/export.rb
def self.export_scene(params)
format = params['format'] || 'skp'
raise "Unsupported export format: #{format}" unless SUPPORTED_FORMATS.include?(format.downcase)
model = Sketchup.active_model
export_path = generate_export_path(format)
case format.downcase
when 'skp'
export_skp(model, export_path)
when 'obj'
export_obj(model, export_path)
when 'stl'
export_stl(model, export_path)
when 'png', 'jpg', 'jpeg'
export_image(model, export_path, format, params)
endSupexRuntime.generate_export_path method · ruby · L46-L55 (10 LOC)runtime/src/supex_runtime/export.rb
def self.generate_export_path(format)
temp_dir = File.join(ENV['TEMP'] || ENV['TMP'] || Dir.tmpdir, 'supex_runtime_exports')
FileUtils.mkdir_p(temp_dir)
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
filename = "supex_runtime_export_#{timestamp}"
extension = format.downcase == 'jpg' ? 'jpeg' : format.downcase
File.join(temp_dir, "#{filename}.#{extension}")
endSupexRuntime.export_obj method · ruby · L67-L75 (9 LOC)runtime/src/supex_runtime/export.rb
def self.export_obj(model, path)
options = {
triangulated_faces: true,
double_sided_faces: true,
edges: false,
texture_maps: true
}
model.export(path, options)
endSupexRuntime.export_image method · ruby · L90-L102 (13 LOC)runtime/src/supex_runtime/export.rb
def self.export_image(model, path, format, params)
view = model.active_view
options = {
filename: path,
width: params['width'] || 1920,
height: params['height'] || 1080,
antialias: true,
transparent: format.downcase == 'png'
}
view.write_image(options)
endSupexRuntime.start method · ruby · L22-L35 (14 LOC)runtime/src/supex_runtime/main.rb
def self.start(repl: nil)
load_stdlib
Utils.console_write("Supex: Version #{VERSION}")
Utils.console_write("Supex: SketchUp #{Sketchup.version} compatibility")
start_bridge_server
# Start REPL server unless explicitly disabled
repl_enabled = repl.nil? ? ENV['SUPEX_REPL_DISABLED'] != '1' : repl
start_repl_server if repl_enabled
add_menu_items
true
endstop_repl_server function · ruby · L85-L94 (10 LOC)runtime/src/supex_runtime/main.rb
def self.stop_repl_server
if @repl_server&.running?
@repl_server.stop
@repl_server = nil
Utils.console_write('Supex: REPL server stopped')
true
else
Utils.console_write('Supex: REPL server is not running')
false
endstop function · ruby · L98-L107 (10 LOC)runtime/src/supex_runtime/main.rb
def self.stop
stopped = false
# Stop REPL server first
if @repl_server&.running?
@repl_server.stop
@repl_server = nil
Utils.console_write('Supex: REPL server stopped')
stopped = true
endRepobility (the analyzer behind this table) · https://repobility.com
server_status function · ruby · L130-L147 (18 LOC)runtime/src/supex_runtime/main.rb
def self.server_status
status = {
version: VERSION,
bridge: {
running: @bridge_server&.running? || false,
port: @bridge_server&.running? ? BridgeServer::DEFAULT_PORT : nil
},
repl: {
running: @repl_server&.running? || false,
port: @repl_server&.running? ? @repl_server.port : nil
}
}
if @bridge_server&.running?
status[:sketchup_version] = Sketchup.version
status[:mcp_version] = MCP_VERSION
status[:required_sketchup] = REQUIRED_SKETCHUP_VERSION
endreload_extension function · ruby · L171-L191 (21 LOC)runtime/src/supex_runtime/main.rb
def self.reload_extension
Utils.console_write('Supex: Reloading extension...')
begin
was_running = @bridge_server&.running?
stop if was_running
remove_extension_constants
unload_extension_files
GC.start
reload_main_extension_file
Utils.console_write('Supex: Extension reloaded successfully')
restart_if_was_running(was_running)
true
rescue StandardError => e
Utils.console_write("Supex: Failed to reload extension: #{e.message}")
Utils.console_write("Supex: #{e.backtrace.join("\n")}")
false
endremove_extension_constants function · ruby · L195-L206 (12 LOC)runtime/src/supex_runtime/main.rb
def self.remove_extension_constants
return unless defined?(SupexRuntime::VERSION)
constants_to_remove = %i[
VERSION REQUIRED_SKETCHUP_VERSION MCP_VERSION
EXTENSION_NAME EXTENSION_DESCRIPTION EXTENSION_CREATOR EXTENSION_COPYRIGHT
]
constants_to_remove.each do |const|
SupexRuntime.send(:remove_const, const)
rescue StandardError
nil
endunload_extension_files function · ruby · L210-L222 (13 LOC)runtime/src/supex_runtime/main.rb
def self.unload_extension_files
files_to_reload = %w[
version.rb utils.rb geometry.rb materials.rb
export.rb joinery.rb batch_screenshot.rb tools.rb
bridge_server.rb repl_server.rb main.rb
]
extension_dir = __dir__
files_to_reload.each do |file|
full_path = File.join(extension_dir, file)
loaded_files = $LOADED_FEATURES.select do |f|
f == full_path || f.end_with?("/supex_runtime/#{file}")
endrestart_if_was_running function · ruby · L240-L245 (6 LOC)runtime/src/supex_runtime/main.rb
def self.restart_if_was_running(was_running)
return unless was_running
sleep(1)
start
endload_stdlib function · ruby · L249-L260 (12 LOC)runtime/src/supex_runtime/main.rb
def self.load_stdlib
stdlib_path = ENV['SUPEX_STDLIB_PATH'] ||
File.expand_path('../../../stdlib/src/supex_stdlib.rb', __dir__)
if File.exist?(stdlib_path)
require stdlib_path
Utils.console_write("Supex: Stdlib v#{SupexStdlib::VERSION} loaded")
true
else
Utils.console_write('Supex: Stdlib not found (optional)')
false
endadd_menu_items function · ruby · L267-L275 (9 LOC)runtime/src/supex_runtime/main.rb
def self.add_menu_items
# Skip if menu already exists (simple check - menu creation will fail if exists)
begin
extensions_menu = UI.menu('Extensions')
supex_runtime_menu = extensions_menu.add_submenu('Supex')
rescue ArgumentError
# Menu already exists, skip creation
return
endshow_server_status function · ruby · L297-L311 (15 LOC)runtime/src/supex_runtime/main.rb
def self.show_server_status
status = server_status
bridge_status = status[:bridge][:running] ? "RUNNING (port #{status[:bridge][:port]})" : 'STOPPED'
repl_status = status[:repl][:running] ? "RUNNING (port #{status[:repl][:port]})" : 'STOPPED'
message = "Supex Server Status\n\n" \
"Version: #{status[:version]}\n\n" \
"Bridge Server: #{bridge_status}\n" \
"REPL Server: #{repl_status}"
if status[:bridge][:running]
message += "\n\nSketchUp: #{status[:sketchup_version]}\n" \
"MCP Version: #{status[:mcp_version]}"
endIf a scraper extracted this row, it came from Repobility (https://repobility.com)
show_about_dialog function · ruby · L317-L326 (10 LOC)runtime/src/supex_runtime/main.rb
def self.show_about_dialog
message = "Supex v#{VERSION}\n\n" \
"A SketchUp Model Context Protocol server.\n" \
"Provides AI-accessible tools for SketchUp automation.\n\n" \
"Supported SketchUp: #{REQUIRED_SKETCHUP_VERSION}+\n" \
"Current SketchUp: #{Sketchup.version}\n" \
"MCP Version: #{MCP_VERSION}"
UI.messagebox(message, MB_OK)
endSupexRuntime::PathPolicy::PathAccessDenied#validate! method · ruby · L22-L30 (9 LOC)runtime/src/supex_runtime/path_policy.rb
def validate!(path, operation: 'access')
return if allow_all?
return unless path
resolved = resolve_path(path)
return if allowed?(resolved)
raise PathAccessDenied, "Path access denied for #{operation}: #{path}"
endSupexRuntime::PathPolicy::PathAccessDenied#allowed_roots method · ruby · L41-L46 (6 LOC)runtime/src/supex_runtime/path_policy.rb
def allowed_roots
roots = ALLOWED_ROOTS.dup
roots << PROJECT_ROOT if PROJECT_ROOT && !PROJECT_ROOT.empty?
roots << DEFAULT_TMP
roots.map { |r| File.expand_path(r) }.uniq
endSupexRuntime#initialize method · ruby · L41-L49 (9 LOC)runtime/src/supex_runtime/repl_server.rb
def initialize(port: DEFAULT_REPL_PORT, host: DEFAULT_REPL_HOST)
@port = Integer(ENV.fetch('SUPEX_REPL_PORT', port))
@host = host
@server = nil
@running = false
@timer_id = nil
@verbose = ENV['SUPEX_VERBOSE'] == '1'
@clients = [] # Active client connections
endSupexRuntime#start method · ruby · L52-L60 (9 LOC)runtime/src/supex_runtime/repl_server.rb
def start
return true if @running
# Check if binding to non-loopback without explicit opt-in
unless loopback_address?(@host)
unless ALLOW_REMOTE
log "ERROR: Binding to non-loopback address '#{@host}' requires SUPEX_ALLOW_REMOTE=1"
return false
endstop function · ruby · L85-L94 (10 LOC)runtime/src/supex_runtime/repl_server.rb
def stop
log 'Stopping REPL server...'
@running = false
stop_timer
close_all_clients
close_server
log 'REPL server stopped'
endstart_request_handler function · ruby · L111-L116 (6 LOC)runtime/src/supex_runtime/repl_server.rb
def start_request_handler
@timer_id = UI.start_timer(REQUEST_CHECK_INTERVAL, true) do
handle_tick if @running
rescue StandardError => e
log "Timer handler error: #{e.message}"
endstop_timer function · ruby · L120-L125 (6 LOC)runtime/src/supex_runtime/repl_server.rb
def stop_timer
return unless @timer_id
UI.stop_timer(@timer_id)
@timer_id = nil
endWant this analysis on your repo? https://repobility.com/scan/
close_all_clients function · ruby · L128-L135 (8 LOC)runtime/src/supex_runtime/repl_server.rb
def close_all_clients
@clients.each do |client|
log_client_closed(client)
begin
client.socket.close
rescue
nil
endclose_server function · ruby · L141-L146 (6 LOC)runtime/src/supex_runtime/repl_server.rb
def close_server
return unless @server
@server.close
@server = nil
endhandle_tick function · ruby · L149-L154 (6 LOC)runtime/src/supex_runtime/repl_server.rb
def handle_tick
return unless @server && @running
accept_new_connections
process_client_data
endaccept_new_connections function · ruby · L157-L173 (17 LOC)runtime/src/supex_runtime/repl_server.rb
def accept_new_connections
loop do
socket = @server.accept_nonblock
client = ClientConnection.new(
socket: socket,
client_info: nil,
session_dir: nil,
snippet_counter: 0
)
@clients << client
log_verbose 'New client connected'
rescue Errno::EWOULDBLOCK, Errno::EAGAIN
break # No more pending connections
rescue StandardError => e
log "Error accepting connection: #{e.message}"
break
endprocess_client function · ruby · L186-L201 (16 LOC)runtime/src/supex_runtime/repl_server.rb
def process_client(client)
# Check if data is available (non-blocking)
# rubocop:disable Lint/IncompatibleIoSelectWithFiberScheduler
ready = IO.select([client.socket], nil, nil, 0)
# rubocop:enable Lint/IncompatibleIoSelectWithFiberScheduler
return nil unless ready
# Try to read a line
line = client.socket.gets
unless line
log_client_closed(client)
begin
client.socket.close
rescue
nil
endprocess_request function · ruby · L230-L239 (10 LOC)runtime/src/supex_runtime/repl_server.rb
def process_request(client, data)
request = JSON.parse(data)
log_verbose "REPL request: #{request['method']}"
response = handle_jsonrpc_request(request, client)
client.socket.write("#{response.to_json}\n")
client.socket.flush
rescue JSON::ParserError => e
send_error_response(client.socket, "Parse error: #{e.message}", -32_700, nil)
endhandle_jsonrpc_request function · ruby · L245-L255 (11 LOC)runtime/src/supex_runtime/repl_server.rb
def handle_jsonrpc_request(request, client)
case request['method']
when 'hello'
handle_hello(request, client)
when 'eval'
return require_hello_error(request) unless client.identified?
handle_eval(request, client)
else
error_response(request, "Method not found: #{request['method']}", -32_601)
endhandle_hello function · ruby · L262-L271 (10 LOC)runtime/src/supex_runtime/repl_server.rb
def handle_hello(request, client)
params = request['params'] || {}
# Token validation (if token is configured)
if AUTH_TOKEN && !AUTH_TOKEN.empty?
token = params['token']
unless token == AUTH_TOKEN
log 'Authentication failed: invalid or missing token'
return error_response(request, 'Authentication failed: invalid or missing token', -32_001)
endRepobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
handle_eval function · ruby · L298-L310 (13 LOC)runtime/src/supex_runtime/repl_server.rb
def handle_eval(request, client)
code = request.dig('params', 'code')
return error_response(request, 'Missing code parameter', -32_602) unless code
client.snippet_counter += 1
snippet_path = File.join(client.session_dir, format('%04d.rb', client.snippet_counter))
File.write(snippet_path, code)
log_verbose "Evaluating snippet #{client.snippet_counter}"
output = eval_code_with_capture(code, snippet_path)
success_response(request, { output: output, success: true })
endeval_code_with_capture function · ruby · L323-L335 (13 LOC)runtime/src/supex_runtime/repl_server.rb
def eval_code_with_capture(code, snippet_path)
output = StringIO.new
with_captured_output(output) do
# rubocop:disable Security/Eval
result = eval(code, TOPLEVEL_BINDING, snippet_path, 1)
# rubocop:enable Security/Eval
output.puts "=> #{result.inspect}"
rescue Exception => e # rubocop:disable Lint/RescueException
output.puts "#<#{e.class}: #{e.message}>"
filtered = filter_backtrace(e.backtrace)
output.puts filtered.first(5).join("\n") if filtered.any?
endfilter_backtrace function · ruby · L344-L351 (8 LOC)runtime/src/supex_runtime/repl_server.rb
def filter_backtrace(backtrace)
return [] unless backtrace
backtrace.reject do |line|
line.include?('repl_server.rb') ||
line.include?("in `eval'") ||
line.include?('supex_runtime/')
endwith_captured_output function · ruby · L356-L365 (10 LOC)runtime/src/supex_runtime/repl_server.rb
def with_captured_output(output)
prev_stdout = $stdout
prev_stderr = $stderr
$stdout = output
$stderr = output
yield
ensure
$stdout = prev_stdout
$stderr = prev_stderr
endlog_client_closed function · ruby · L397-L403 (7 LOC)runtime/src/supex_runtime/repl_server.rb
def log_client_closed(client)
if client.identified?
info = client.client_info
log "Client closed: #{info['name']} [PID:#{info['pid']}]"
else
log_verbose 'Client closed (unidentified)'
endSupexRuntime::Tools#model_info method · ruby · L14-L22 (9 LOC)runtime/src/supex_runtime/tools.rb
def model_info
model = Sketchup.active_model
return { success: false, error: 'No active model' } unless model
build_model_info_response(model)
rescue StandardError => e
log "Error getting model info: #{e.message}"
raise "Failed to get model info: #{e.message}"
endSupexRuntime::Tools#list_entities method · ruby · L27-L40 (14 LOC)runtime/src/supex_runtime/tools.rb
def list_entities(params)
model = Sketchup.active_model
entity_type = params['entity_type'] || 'all'
return { success: false, error: 'No active model' } unless model
entities = filter_entities_by_type(model, entity_type)
entities_data = entities.map { |entity| build_entity_data(entity) }
{ success: true, entity_type: entity_type, count: entities_data.length,
entities: entities_data }
rescue StandardError => e
log "Error listing entities: #{e.message}"
raise "Failed to list entities: #{e.message}"
endSupexRuntime::Tools#selection_info method · ruby · L44-L53 (10 LOC)runtime/src/supex_runtime/tools.rb
def selection_info
model = Sketchup.active_model
return { success: false, error: 'No active model' } unless model
entities_data = model.selection.map { |entity| build_selection_entity_data(entity) }
{ success: true, count: model.selection.count, entities: entities_data }
rescue StandardError => e
log "Error getting selection: #{e.message}"
raise "Failed to get selection: #{e.message}"
endRepobility (the analyzer behind this table) · https://repobility.com
SupexRuntime::Tools#layers_info method · ruby · L57-L63 (7 LOC)runtime/src/supex_runtime/tools.rb
def layers_info
model = Sketchup.active_model
return { success: false, error: 'No active model' } unless model
layers_data = model.layers.map do |layer|
{ name: layer.name, visible: layer.visible?, page_behavior: layer.page_behavior }
endSupexRuntime#materials_info method · ruby · L72-L81 (10 LOC)runtime/src/supex_runtime/tools.rb
def materials_info
model = Sketchup.active_model
return { success: false, error: 'No active model' } unless model
materials_data = model.materials.map { |material| build_material_data(material) }
{ success: true, count: materials_data.length, materials: materials_data }
rescue StandardError => e
log "Error getting materials: #{e.message}"
raise "Failed to get materials: #{e.message}"
endSupexRuntime#camera_info method · ruby · L85-L93 (9 LOC)runtime/src/supex_runtime/tools.rb
def camera_info
model = Sketchup.active_model
return { success: false, error: 'No active model' } unless model
build_camera_info_response(model)
rescue StandardError => e
log "Error getting camera info: #{e.message}"
raise "Failed to get camera info: #{e.message}"
endSupexRuntime#take_screenshot method · ruby · L98-L111 (14 LOC)runtime/src/supex_runtime/tools.rb
def take_screenshot(params)
model = Sketchup.active_model
return { success: false, error: 'No active model' } unless model
# Validate output_path if provided
PathPolicy.validate!(params['output_path'], operation: 'take_screenshot') if params['output_path']
screenshot_path = determine_screenshot_path(params['output_path'])
write_screenshot(model, screenshot_path, params)
rescue StandardError => e
log "Error taking screenshot: #{e.message}"
log e.backtrace.join("\n")
raise "Failed to take screenshot: #{e.message}"
endSupexRuntime#batch_screenshot method · ruby · L117-L123 (7 LOC)runtime/src/supex_runtime/tools.rb
def batch_screenshot(params)
BatchScreenshot.execute(params)
rescue StandardError => e
log "Error taking batch screenshots: #{e.message}"
log e.backtrace.join("\n")
raise "Failed to take batch screenshots: #{e.message}"
endSupexRuntime#open_model method · ruby · L128-L142 (15 LOC)runtime/src/supex_runtime/tools.rb
def open_model(params)
file_path = params['path']
return { success: false, error: 'No file path provided' } unless file_path
PathPolicy.validate!(file_path, operation: 'open_model')
return { success: false, error: "File not found: #{file_path}" } unless File.exist?(file_path)
Sketchup.open_file(file_path)
model = Sketchup.active_model
{ success: true, file_path: file_path, file_name: File.basename(file_path),
title: model.title }
rescue StandardError => e
log "Error opening model: #{e.message}"
raise "Failed to open model: #{e.message}"
endSupexRuntime#save_model method · ruby · L147-L160 (14 LOC)runtime/src/supex_runtime/tools.rb
def save_model(params)
model = Sketchup.active_model
return { success: false, error: 'No active model' } unless model
# Validate path if provided
PathPolicy.validate!(params['path'], operation: 'save_model') if params['path']
saved_path = perform_save(model, params['path'])
{ success: true, file_path: saved_path, file_name: File.basename(saved_path),
title: model.title }
rescue StandardError => e
log "Error saving model: #{e.message}"
raise "Failed to save model: #{e.message}"
endSupexRuntime#build_model_info_response method · ruby · L164-L180 (17 LOC)runtime/src/supex_runtime/tools.rb
def build_model_info_response(model)
units_options = model.options['UnitsOptions']
length_unit = units_options['LengthUnit']
units_map = { 0 => 'inches', 1 => 'feet', 2 => 'millimeters', 3 => 'centimeters',
4 => 'meters' }
{
success: true,
title: model.title.empty? ? 'Untitled' : model.title,
units: units_map[length_unit] || 'unknown',
num_faces: model.entities.grep(Sketchup::Face).count,
num_edges: model.entities.grep(Sketchup::Edge).count,
num_groups: model.entities.grep(Sketchup::Group).count,
num_components: model.entities.grep(Sketchup::ComponentInstance).count,
modified: model.modified?
}
endIf a scraper extracted this row, it came from Repobility (https://repobility.com)
SupexRuntime#filter_entities_by_type method · ruby · L182-L189 (8 LOC)runtime/src/supex_runtime/tools.rb
def filter_entities_by_type(model, entity_type)
case entity_type
when 'faces' then model.entities.grep(Sketchup::Face)
when 'edges' then model.entities.grep(Sketchup::Edge)
when 'groups' then model.entities.grep(Sketchup::Group)
when 'components' then model.entities.grep(Sketchup::ComponentInstance)
else model.entities.to_a
endbuild_entity_data function · ruby · L192-L208 (17 LOC)runtime/src/supex_runtime/tools.rb
def build_entity_data(entity)
data = { type: entity.typename, entity_id: entity.entityID }
case entity
when Sketchup::Group
data[:name] = entity.name.empty? ? '(unnamed)' : entity.name
data[:layer] = entity.layer.name
when Sketchup::ComponentInstance
data[:name] = entity.definition.name
data[:layer] = entity.layer.name
when Sketchup::Face
data[:area] = entity.area
data[:layer] = entity.layer.name
when Sketchup::Edge
data[:length] = entity.length
data[:layer] = entity.layer.name
endbuild_selection_entity_data function · ruby · L213-L227 (15 LOC)runtime/src/supex_runtime/tools.rb
def build_selection_entity_data(entity)
data = { type: entity.typename, entity_id: entity.entityID }
case entity
when Sketchup::Face
data[:area] = entity.area
normal = entity.normal
data[:normal] = [normal.x, normal.y, normal.z]
when Sketchup::Edge
data[:length] = entity.length
when Sketchup::Group
data[:name] = entity.name.empty? ? '(unnamed)' : entity.name
when Sketchup::ComponentInstance
data[:name] = entity.definition.name
end