All right - I DO need help. I’ve been banging my head against this wall for the past day and I cannot get it fixed. I really need some help.
I am still at the camera problem. I cannot find a stable way of telling the script either:
Go to standard-view top, zoom at component _Callout_Container, thank you!
or
place a camera at x,y, z 0,-5000-,10000, LOOK DOWN! thank you
What I want to achieve is this: I have a few components selected (that MIGHT be nested in a different component). I run the script. It “calls out” all these components by copying them into the CallOut_Container (a component I created for this purpose). It then creates a scene for each of the components, so I can go into Layout from there.
There is a bit of magic to the side, like checking if the CallOut_Container is actually in the model, if it is placed correctly, if the necessary style is there, etc. all of that works. But the camera I cannot get to do what I want it to do. Could somebody please help?
Here is what I have so far…
# Name: Callout Selected Components (With Wrapper)
# Intent: Creates sectioned callouts for multiple selected components. Each callout is placed in a unique wrapper component for manual data entry, and remains linked to the original.
require 'sketchup.rb'
# 1) Get the selection and filter for component instances only
model = Sketchup.active_model
selection = model.selection.grep(Sketchup::ComponentInstance)
view = model.active_view
camera = view.camera
layers = model.layers
layer_folders = layers.respond_to?(:folders) ? layers.folders : []
if selection.empty?
UI.messagebox("The selection must contain at least one component.")
else
# --- SETUP: ONE-TIME OPERATIONS ---
definitions = model.definitions
tags = model.layers
pages = model.pages
container_def_name = "_Callouts-Container"
# 2) NEW, INTEGRATED LOGIC: Ensure the definition exists and the instance is correctly placed.
# --- PRE-CHECK: ENSURE THE COMPONENT DEFINITION EXISTS ---
callouts_container_def = definitions[container_def_name]
if callouts_container_def.nil?
# If the definition is not found in the model, show an error and stop.
UI.messagebox("Cannot find component _Callouts-Container in the model. Please insert it from the library and try again.")
else
# --- The component definition exists, so proceed with the main logic. ---
model.start_operation('Callout Selected Components with Wrappers', true)
# --- Calculate the final target transformation for the container ---
drawing_axes_transformation = model.axes.transformation
local_target_point = Geom::Point3d.new(0.mm, -5000.mm, 0.mm)
local_translation = Geom::Transformation.translation(local_target_point)
final_world_transformation = drawing_axes_transformation * local_translation
# --- Find all existing instances in the model's root ---
all_instances = model.entities.grep(Sketchup::ComponentInstance).select do |inst|
!inst.deleted? && inst.definition == callouts_container_def
end
# Declare a variable for the final instance, needed later for the camera
callouts_container_instance = nil
# --- Apply conditional logic based on the number of instances found ---
case all_instances.length
when 0
# Create a new instance
callouts_container_instance = model.entities.add_instance(callouts_container_def, final_world_transformation)
when 1
# Move the existing instance
instance_to_move = all_instances.first
instance_to_move.transformation = final_world_transformation
callouts_container_instance = instance_to_move
else
# Clean up all instances and create a new one
model.entities.erase_entities(all_instances)
callouts_container_instance = model.entities.add_instance(callouts_container_def, final_world_transformation)
end
# 3) Check if the required generic tags (layers) exist
tag_names = [
"@Sec CallOut Bd", "@Sec CallOut Bu", "@Sec CallOut Container",
"@Sec CallOut G<", "@Sec CallOut G>", "@Sec CallOut R<",
"@Sec CallOut R>"
]
tag_names.each do |name|
unless tags[name]
new_tag = tags.add(name)
pages.each { |page| page.set_visibility(new_tag, false) }
end
end
# Lists for the final report
created_count = 0
skipped_names = []
# Set Style
style_name = 'CallOut Color'
styles = model.styles
created_scenes = []
callout_style = styles[style_name]
if !callout_style
UI.messagebox("Style '#{style_name}' not found.\n\nPlease import the required style into your model and run the script again.")
return
end
styles.selected_style = callout_style
model.active_view.camera.perspective = false
model.start_operation('Create and Sort Scenes', true)
# --- KAMERA ANPASSEN ---
# --- Define the DEFINITION name of the component to focus on ---
callouts_container_definition_name = "_Callouts-Container"
# callouts_container_instance = nil
# Find the first component instance by its DEFINITION name
model.entities.each do |entity|
if entity.is_a?(Sketchup::ComponentInstance) && entity.definition.name == callouts_container_definition_name
callouts_container_instance = entity
break # Exit the loop once the first matching instance is found
end
end
# Check if a matching component instance was found
if callouts_container_instance.nil?
UI.messagebox("Component with definition name '#{callouts_container_definition_name}' not found.")
else
# Get the active drawing axes
drawing_axes = model.axes
# Get the bounding box of the specific component instance that was found
bounds = callouts_container_instance.bounds
# Calculate the center of the bounding box
center = bounds.center
# Determine a suitable distance for the camera eye based on the component's size
distance = bounds.diagonal * 1.5
# Get the Z-axis (top) and Y-axis (up) from the active drawing axes
top_vector = drawing_axes.zaxis
up_vector = drawing_axes.yaxis
# Calculate the new camera position by moving from the center along the active Z-axis
eye = center.offset(top_vector, distance)
# Set the camera eye, target, and up vector
camera.set(eye, center, up_vector)
# Set the camera to orthographic projection for a true 2D top view
view.camera.perspective = true
# Zoom to the selected component instance
view.zoom(callouts_container_instance)
end
# --- NEU: Step 3: Set Tag and Folder Visibility ---
# Define which folders and tags to show or hide
folders_to_hide = ["3. EBENEN", "2. STANDORT"]
tags_to_hide = ["!Einzelteile-Matrix", "!Hauptmodell", "#hidden", "#2d_graphik", "!CallOut Backdrop", "@Sec Raster-Schnitte"]
folders_to_show = ["@ SECCUTS", "@ CALLOUTS", "!CallOut Container"]
tags_to_show = ["!Positionierungshilfe", "#kanten_gestrichelt", "#kanten_mitte"]
# Hide specified folders
folders_to_hide.each do |folder_name|
folder = layer_folders.find { |f| f.name == folder_name }
folder.visible = false if folder
end
# Hide specified tags
tags_to_hide.each do |tag_name|
tag = layers.find { |l| l.name == tag_name }
tag.visible = false if tag
end
# Show specified folders
folders_to_show.each do |folder_name|
folder = layer_folders.find { |f| f.name == folder_name }
folder.visible = true if folder
end
# Show specified tags
tags_to_show.each do |tag_name|
tag = layers.find { |l| l.name == tag_name }
tag.visible = true if tag
end
# --- MAIN LOOP: PROCESS EACH SELECTED COMPONENT ---
selection.each do |original_instance|
original_definition = original_instance.definition
component_name = original_definition.name
# --- MODIFIED LOGIC ---
# Check if a callout already exists for this component by searching the wrapper components.
callout_exists = false
# IMPORTANT: We search the *definition* of the container, as that's where wrappers are added.
callouts_container_def.entities.grep(Sketchup::ComponentInstance).each do |wrapper_instance|
wrapper_definition = wrapper_instance.definition
if wrapper_definition.entities.grep(Sketchup::ComponentInstance).any? { |inner_inst| inner_inst.definition == original_definition }
callout_exists = true
break # Found, no need to search further
end
end
if callout_exists
skipped_names << component_name
else
# --- No callout exists for this component, so create one inside a wrapper ---
# 4) Create a new, unique wrapper component definition
wrapper_def_name = "_Wrapper " + component_name
unique_wrapper_def_name = definitions.unique_name(wrapper_def_name)
wrapper_def = definitions.add(unique_wrapper_def_name)
# 5) Place an instance of the original component into the new wrapper definition
wrapper_def.entities.add_instance(original_definition, Geom::Transformation.new)
# 6) Place an instance of the wrapper component inside the "_Callouts-Container"
wrapper_instance = callouts_container_def.entities.add_instance(wrapper_def, Geom::Transformation.new)
# 7) Create the specific tag for the callout
callout_tag_name = "@CallOut " + component_name
callout_tag = tags[callout_tag_name] || tags.add(callout_tag_name)
pages.each { |page| page.set_visibility(callout_tag, false) }
# 8) Assign the new tag to the WRAPPER instance
wrapper_instance.layer = callout_tag
# 9) Set up tag visibility and create the scene
tags.each do |tag|
if tag.name.start_with?("@CallOut")
tag.visible = false
end
end
callout_tag.visible = true
scene_name = "@CallOut " + component_name
new_scene = pages.add(scene_name)
new_scene.update(2) if new_scene
created_count += 1
end
end # End of the main loop
# --- FINALIZE ---
# --- NEW: ORGANIZE CALLOUT TAGS INTO A FOLDER ---
# This block will find all tags starting with "@CallOut" and place them
# into a folder named "@ CALLOUTS". This requires SketchUp 2021 or newer.
if layers.respond_to?(:folders)
folder_name = "@ CALLOUTS"
# Find the folder or create it if it doesn't exist
callouts_folder = layers.folders.find { |f| f.name == folder_name }
unless callouts_folder
callouts_folder = layers.add_folder(folder_name)
end
# If the folder was successfully found or created, move the tags
if callouts_folder
layers.each do |tag|
if tag.name.start_with?("@CallOut")
tag.folder = callouts_folder
end
end
end
end
# Exit any open component editing contexts
Sketchup.active_model.active_path = nil
model.commit_operation
# Final report to the user
message = "Operation complete.\n"
message += "Successfully created #{created_count} callout(s), each in a unique wrapper.\n"
unless skipped_names.empty?
message += "Skipped because they already exist (#{skipped_names.length}): #{skipped_names.join(', ')}"
end
UI.messagebox(message)
end
end
[CallOut Test 2025-11-16.skp|attachment](upload://b2FpJMz2BCe8i9OhQ07RTi37HOG.skp) (13.7 MB)
I was also dabbeling with this, but it feels even more wrong:
# --- NEW: KAMERA ANPASSEN (CAMERA ADJUSTMENT) ---
# This block explicitly sets the camera's position to create a precise top-down view.
# The rotation has been adjusted by changing the "up_vector".
puts "Setting camera to a fixed, rotated position."
view = model.active_view
camera = view.camera
# 1. Ensure the view is in Parallel Projection (Orthographic).
camera.perspective = false
# 2. Define the camera vectors.
target_point = Geom::Point3d.new(0.mm, -2500.mm, 0.mm)
eye_point = Geom::Point3d.new(0.mm, -2500.mm, 5000.mm)
# FIXED: The "up" direction for the camera has been changed.
# By using the red axis (1,0,0) as the up vector instead of the green axis (0,1,0),
# the camera view is rotated by 90 degrees.
up_vector = Geom::Vector3d.new(1, 0, 0)
# 3. Set the camera.
camera.set(eye_point, target_point, up_vector)
# 4. Adjust the zoom level for consistency.
# view.zoom_extents
# view.camera.height = 2000.mm # A smaller number zooms in. Adjust as needed.
Here is my testing file and a screencast of what is happening at the moment…
CallOut Test 2025-11-16.skp (13.7 MB)