-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Add math test #555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add math test #555
Conversation
Codecov Report
@@ Coverage Diff @@
## main #555 +/- ##
==========================================
+ Coverage 32.40% 37.29% +4.88%
==========================================
Files 27 27
Lines 3357 3357
Branches 756 756
==========================================
+ Hits 1088 1252 +164
+ Misses 2173 1989 -184
- Partials 96 116 +20
Flags with carried forward coverage won't be shown. Click here to find out more. |
| AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }} | ||
| OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} | ||
| run: | | ||
| coverage run -a -m pytest test/agentchat/contrib/test_math_user_proxy_agent.py |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the output of coverage run will overwrite the output of the former step. In the end, only one step of coverage will be uploaded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add different names to the output of different steps and combine them at the upload step?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The coverage run -a will append outputs to the same file(.coverage), so we are good with this. Not very sure but this is what I find.
|
|
||
| jobs: | ||
| RetrieveChatTest: | ||
| OpenAI4ContribTests: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One problem with this approach is that the previous installed dependencies will remain in the later steps. That makes the test environment not clean for later steps. Is it possible to reset the environment for later steps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't found any good way to clear up the environment. A possible solution is to use virtual env, but different os needs different syntax, which makes it complex. The other way is just putting them in different jobs.
I guess there are two problems to consider here:
- Is it ok to NOT having a clean env? Will there be a case that user does
pip install autogen[retrievechat, mathchat], and they expect things just work? - I suggested that separating the jobs would be tedious to look in github actions, which is why I change this in the first place. But we might need to revisit the problem: do you think it is indeed a issue, or we can bear with it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's pretty important for the automatic jobs to test different contributed agents in different environments, each with just its own dependencies, whether this is done through separate virtual envs, different jobs, or some other mechanism. Wish I had more experience in setting up such tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
coverage run -a -m pytest test/test_retrieve_utils.py test/agentchat/contrib
will test all files in the contrib folder, while only dependencies for Retrievechat is installed. This will result in error in the future if a contrib agent requires other packages to be installed.
https://github.com/microsoft/autogen/blob/fda7a39dd9ede1c115d37c2a454f55b7f22c8193/.github/workflows/contrib-tests.yml#L52C38-L52C38
Здесь сделай чтоб присадку делал согласно локальных координат компонентов, так как сейчас на повёрнутых под определённый градус угла компонентах не делает присадок потому что делает присадку согласно мировых координат что не допустимо. Пиши полный код целиком.
require 'sketchup.rb'
require 'extensions.rb'
module ABFStyleDrill
# Настройки по умолчанию
DEFAULTS = {
:cam_diameter => 8.0, # Диаметр отверстия в пласть (Cam)
:screw_diameter => 5.0, # Диаметр отверстия в торец (Screw)
:offset => 50.0, # Отступ от края детали для присадки
:hole_count => 2, # Количество присадочных отверстий
:marker_length => 15.0, # Длина проекции (маркера) для торцевого отверстия
# --- НОВЫЕ НАСТРОЙКИ ДЛЯ ПЕТЕЛЬ ---
:hinge_diameter => 35.0, # Диаметр отверстия под чашку петли (35мм)
:hinge_offset => 100.0, # Отступ от края детали для первой петли (100мм) - Смещение по длине
:hinge_count => 2, # Количество петель
:hinge_side_offset => 21.5, # Смещение от бокового края компонента (21.5мм)
:hinge_screw_diameter => 5.0, # Диаметр отверстий под саморезы (5мм)
:hinge_screw_spacing => 45.0, # Расстояние между центрами отверстий под саморезы (45мм)
:hinge_screw_vertical_offset => 9.5, # НОВОЕ: Смещение отверстий под саморезы от центра чашки (9.5мм)
# --- НОВЫЕ НАСТРОЙКИ ДЛЯ ПЛАНКИ ---
:strip_diameter => 5.0, # Диаметр отверстия под планку (5мм)
:strip_spacing => 32.0, # Межосевое расстояние (32мм)
:strip_side_offset => 37.0, # Смещение от края боковины (37мм)
:strip_offset => 100.0 # НОВОЕ: Отступ от короткого края (100мм)
}
# Константа для зазора между деталями при раскладке
GAP = 500.0.mm
# Переменная для отслеживания состояния аннотаций
@annotations_visible = false
# --- МЕТОД: Очистка ранее созданных аннотаций (размеров) ---
def self.cleanup_annotations(entities)
entities_to_remove = []
entities.each do |e|
if e.is_a?(Sketchup::DimensionLinear) && e.get_attribute('ABFDrill', 'Type') == 'Dimension'
entities_to_remove << e
end
end
entities.erase_entities(entities_to_remove) if entities_to_remove.any?
end
# --- МЕТОД: Очистка только стандартных присадочных отверстий и маркеров ---
def self.cleanup_standard_drill_geometry(entity)
defn = entity.respond_to?(:definition) ? entity.definition : entity.entities.parent
entities_to_remove = []
defn.entities.each do |e|
# Удаляем всё, что помечено как DrillHole
type = e.get_attribute('ABFDrill', 'Type')
if type == 'DrillHole'
entities_to_remove << e
end
end
defn.entities.erase_entities(entities_to_remove) if entities_to_remove.any?
end
# --- МЕТОД: Очистка только отверстий под петли ---
def self.cleanup_hinge_drill_geometry(entity)
defn = entity.respond_to?(:definition) ? entity.definition : entity.entities.parent
entities_to_remove = []
defn.entities.each do |e|
# Удаляем всё, что помечено как HingeHole
type = e.get_attribute('ABFDrill', 'Type')
if type == 'HingeHole'
entities_to_remove << e
end
end
defn.entities.erase_entities(entities_to_remove) if entities_to_remove.any?
end
# --- НОВЫЙ МЕТОД: Очистка только отверстий под планку ---
def self.cleanup_strip_drill_geometry(entity)
defn = entity.respond_to?(:definition) ? entity.definition : entity.entities.parent
entities_to_remove = []
defn.entities.each do |e|
# Удаляем всё, что помечено как StripHole (включая новую точку)
type = e.get_attribute('ABFDrill', 'Type')
if type == 'StripHole'
entities_to_remove << e
end
end
defn.entities.erase_entities(entities_to_remove) if entities_to_remove.any?
end
# --- МЕТОД: Отображение диалогового окна (Обновлено для передачи параметров петель в планку) ---
def self.show_dialog
dialog = UI::HtmlDialog.new(
{
:dialog_title => "ABF Style Drill & Hinges",
:preferences_key => "com.abfstyle.drill",
:scrollable => true,
:resizable => true,
:width => 350,
:height => 850, # Увеличена высота для новых настроек
:style => UI::HtmlDialog::STYLE_DIALOG
}
)
html_content = <<-HTML
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: 'Segoe UI', sans-serif; padding: 20px; background-color: microsoft#333; color: white; }
h3 { margin-top: 20px; color: #ff9800; border-bottom: 1px solid microsoft#555; padding-bottom: 10px; }
.control { margin-bottom: 15px; }
label { display: flex; justify-content: space-between; align-items: center; font-size: 14px; }
input { width: 80px; padding: 5px; border-radius: 4px; border: none; text-align: center; }
button {
width: 100%; padding: 12px; margin-top: 10px;
background-color: #ff9800; color: white; border: none;
border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 16px;
transition: background 0.3s;
}
button:hover { background-color: #e68900; }
.hint { font-size: 11px; color: #aaa; margin-top: 5px; }
.secondary-button { background-color: microsoft#555; }
.secondary-button:hover { background-color: microsoft#777; }
.annotation-button { background-color: #00bcd4; }
.annotation-button:hover { background-color: #0097a7; }
.hinge-button { background-color: #4CAF50; }
.hinge-button:hover { background-color: #45a049; }
/* НОВЫЙ СТИЛЬ ДЛЯ КНОПКИ ПЛАНКИ */
.strip-button { background-color: #9c27b0; }
.strip-button:hover { background-color: #7b1fa2; }
</style>
</head>
<body>
<!-- --- НОВАЯ СЕКЦИЯ ДЛЯ ПЛАНКИ (ПЕРЕМЕЩЕНА ВЫШЕ) --- -->
<h3>Присадка планки</h3>
<div class="control">
<label>Диаметр отверстий (мм) <input type="number" id="strip_dia" value="#{DEFAULTS[:strip_diameter]}" step="0.5"></label>
</div>
<div class="control">
<label>Межосевое (мм) <input type="number" id="strip_spacing" value="#{DEFAULTS[:strip_spacing]}" step="1.0"></label>
</div>
<div class="control">
<label>Смещение от края (мм) <input type="number" id="strip_side_off" value="#{DEFAULTS[:strip_side_offset]}" step="1.0"></label>
</div>
<!-- НОВЫЙ ИНПУТ ДЛЯ СМЕЩЕНИЯ ПЛАНКИ -->
<div class="control">
<label>Смещение по длине (мм) <input type="number" id="strip_off" value="#{DEFAULTS[:strip_offset]}" step="1.0"></label>
<div class="hint">Отступ первой планки от короткого края.</div>
</div>
<button class="strip-button" onclick="run_strip_drill()">ПРИСАДКА ПЛАНКИ</button>
<!-- --- КОНЕЦ НОВОЙ СЕКЦИИ --- -->
<h3>Настройки присадки (Minifix)</h3>
<div class="control">
<label>Количество отверстий <input type="number" id="hole_count" value="#{DEFAULTS[:hole_count]}" step="1" min="1" oninput="update_model_realtime()"></label>
<div class="hint">Общее количество на стык.</div>
</div>
<div class="control">
<label>Диаметр (Пласть/Cam) <input type="number" id="cam_dia" value="#{DEFAULTS[:cam_diameter]}" step="0.5" oninput="update_model_realtime()"></label>
</div>
<div class="control">
<label>Диаметр (Торец/Screw) <input type="number" id="screw_dia" value="#{DEFAULTS[:screw_diameter]}" step="0.5" oninput="update_model_realtime()"></label>
</div>
<div class="control">
<label>Длина маркера (мм) <input type="number" id="marker_len" value="#{DEFAULTS[:marker_length]}" step="1.0" min="0" oninput="update_model_realtime()"></label>
<div class="hint">Длина проекции торцевого отверстия.</div>
</div>
<div class="control">
<label>Отступ от края (мм) <input type="number" id="off" value="#{DEFAULTS[:offset]}" step="1" oninput="update_model_realtime()"></label>
</div>
<button onclick="run()">ПРИСАДИТЬ (Auto)</button>
<!-- --- НОВАЯ СЕКЦИЯ ДЛЯ ПЕТЕЛЬ --- -->
<h3>Настройки петель</h3>
<div class="control">
<label>Количество кругов <input type="number" id="hinge_count" value="#{DEFAULTS[:hinge_count]}" step="1" min="1"></label>
</div>
<div class="control">
<label>Диаметр чашки (мм) <input type="number" id="hinge_dia" value="#{DEFAULTS[:hinge_diameter]}" step="1.0"></label>
</div>
<!-- НОВЫЕ ИНПУТЫ ДЛЯ ОТВЕРСТИЙ ПОД САМОРЕЗЫ -->
<div class="control">
<label>Диаметр саморезов (мм) <input type="number" id="hinge_screw_dia" value="#{DEFAULTS[:hinge_screw_diameter]}" step="0.5"></label>
</div>
<div class="control">
<label>Расстояние между (мм) <input type="number" id="hinge_screw_spacing" value="#{DEFAULTS[:hinge_screw_spacing]}" step="1.0"></label>
<div class="hint">Расстояние между центрами отверстий под саморезы (45мм).</div>
</div>
<!-- НОВЫЙ ИНПУТ ДЛЯ СМЕЩЕНИЯ САМОРЕЗОВ ОТНОСИТЕЛЬНО ЧАШКИ -->
<div class="control">
<label>Смещение саморезов (мм) <input type="number" id="hinge_screw_vert_off" value="#{DEFAULTS[:hinge_screw_vertical_offset]}" step="0.5"></label>
<div class="hint">Смещение отверстий от центра чашки (вдоль 21.5мм оси).</div>
</div>
<!-- КОНЕЦ НОВЫХ ИНПУТОВ -->
<div class="control">
<label>Смещение по длине (мм) <input type="number" id="hinge_off" value="#{DEFAULTS[:hinge_offset]}" step="1.0"></label>
<div class="hint">Отступ первой петли от короткого края.</div>
</div>
<div class="control">
<label>Смещение от края компонента (мм) <input type="number" id="hinge_side_off" value="#{DEFAULTS[:hinge_side_offset]}" step="0.5"></label>
<div class="hint">Смещение центра чашки от бокового края (21.5мм).</div>
</div>
<button class="hinge-button" onclick="run_hinges()">ПЕТЛИ</button>
<!-- --- КОНЕЦ НОВОЙ СЕКЦИИ --- -->
<button class="secondary-button" onclick="line_up()">РАЗЛОЖИТЬ</button>
<button class="annotation-button" onclick="show_annotations()">АННОТАЦИИ</button>
<script>
function debounce(func, timeout = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
function get_input_values() {
return {
cam_dia: parseFloat(document.getElementById('cam_dia').value),
screw_dia: parseFloat(document.getElementById('screw_dia').value),
off: parseFloat(document.getElementById('off').value),
hole_count: parseInt(document.getElementById('hole_count').value),
marker_len: parseFloat(document.getElementById('marker_len').value)
};
}
// --- НОВАЯ ФУНКЦИЯ ДЛЯ ПОЛУЧЕНИЯ ЗНАЧЕНИЙ ПЕТЕЛЬ (ОБНОВЛЕНО) ---
function get_hinge_values() {
return {
hinge_count: parseInt(document.getElementById('hinge_count').value),
hinge_dia: parseFloat(document.getElementById('hinge_dia').value),
hinge_off: parseFloat(document.getElementById('hinge_off').value),
hinge_side_off: parseFloat(document.getElementById('hinge_side_off').value),
// НОВЫЕ ЗНАЧЕНИЯ
hinge_screw_dia: parseFloat(document.getElementById('hinge_screw_dia').value),
hinge_screw_spacing: parseFloat(document.getElementById('hinge_screw_spacing').value),
hinge_screw_vertical_offset: parseFloat(document.getElementById('hinge_screw_vert_off').value)
};
}
// --- НОВАЯ ФУНКЦИЯ ДЛЯ ПОЛУЧЕНИЯ ЗНАЧЕНИЙ ПЛАНКИ (ОБНОВЛЕНО) ---
function get_strip_values() {
return {
strip_dia: parseFloat(document.getElementById('strip_dia').value),
strip_spacing: parseFloat(document.getElementById('strip_spacing').value),
strip_side_off: parseFloat(document.getElementById('strip_side_off').value),
strip_offset: parseFloat(document.getElementById('strip_off').value), // ДОБАВЛЕНО
// ДОБАВЛЕНЫ ЗНАЧЕНИЯ ПЕТЕЛЬ ДЛЯ ПОЗИЦИОНИРОВАНИЯ ПЛАНКИ
hinge_count: parseInt(document.getElementById('hinge_count').value)
};
}
function run() {
var values = get_input_values();
sketchup.do_drill(values.cam_dia, values.screw_dia, values.off, values.hole_count, values.marker_len);
}
// --- НОВАЯ ФУНКЦИЯ ДЛЯ ЗАПУСКА ПЕТЕЛЬ (ОБНОВЛЕНО) ---
function run_hinges() {
var values = get_hinge_values();
// Передаем новые значения
sketchup.do_hinges(
values.hinge_count,
values.hinge_dia,
values.hinge_off,
values.hinge_side_off,
values.hinge_screw_dia,
values.hinge_screw_spacing,
values.hinge_screw_vertical_offset
);
}
// --- НОВАЯ ФУНКЦИЯ ДЛЯ ЗАПУСКА ПЛАНКИ (ОБНОВЛЕНО) ---
function run_strip_drill() {
var values = get_strip_values();
sketchup.do_strip_drill(
values.strip_dia,
values.strip_spacing,
values.strip_side_off,
// ПЕРЕДАЕМ ПАРАМЕТРЫ ДЛЯ ПОЗИЦИОНИРОВАНИЯ
values.hinge_count,
values.strip_offset // ИСПОЛЬЗУЕМ НОВОЕ СМЕЩЕНИЕ
);
}
const debounced_update_callback = debounce(function() {
var values = get_input_values();
sketchup.update_drill_realtime(values.cam_dia, values.screw_dia, values.off, values.hole_count, values.marker_len);
}, 300);
function update_model_realtime() { debounced_update_callback(); }
function line_up() { sketchup.line_up_components(); }
function show_annotations() { sketchup.show_drill_annotations(); }
window.onload = function() { update_model_realtime(); };
</script>
</body>
</html>
HTML
dialog.set_html(html_content)
dialog.center
dialog.add_action_callback("do_drill") do |ctx, cam_dia, screw_dia, off, hole_count, marker_len|
self.process_model(cam_dia, screw_dia, off, hole_count, marker_len)
end
dialog.add_action_callback("update_drill_realtime") do |ctx, cam_dia, screw_dia, off, hole_count, marker_len|
self.process_model(cam_dia, screw_dia, off, hole_count, marker_len)
end
# --- CALLBACK ДЛЯ ПЕТЕЛЬ ---
dialog.add_action_callback("do_hinges") do |ctx, count, diameter, length_offset, side_offset, screw_diameter, screw_spacing, screw_vert_offset|
self.process_hinge_holes(count, diameter, length_offset, side_offset, screw_diameter, screw_spacing, screw_vert_offset)
end
# --- НОВЫЙ CALLBACK ДЛЯ ПЛАНКИ (ОБНОВЛЕНО) ---
# Изменено: hinge_offset заменен на length_offset (который теперь strip_offset)
dialog.add_action_callback("do_strip_drill") do |ctx, diameter, spacing, side_offset, count, length_offset|
self.process_strip_holes(diameter, spacing, side_offset, count, length_offset)
end
dialog.add_action_callback("line_up_components") { |ctx| self.line_up_components }
dialog.add_action_callback("show_drill_annotations") { |ctx| self.show_drill_annotations }
dialog.show
end
# --- ГЛАВНЫЙ ПРОЦЕСС ПРИСАДКИ (Minifix) ---
def self.process_model(cam_diameter_mm, screw_diameter_mm, offset_mm, hole_count, marker_length_mm)
model = Sketchup.active_model
selection = model.selection
items = selection.grep(Sketchup::ComponentInstance) + selection.grep(Sketchup::Group)
# Если выделено меньше 2-х, очищаем присадки только на выделенных и выходим
if items.length < 2
model.start_operation("ABF Drill Cleanup", true)
items.each { |ent| self.cleanup_standard_drill_geometry(ent) }
model.commit_operation
return
end
model.start_operation("ABF Style Drill Update", true)
# Очищаем только стандартные присадки на выделенных компонентах
items.each { |ent| self.cleanup_standard_drill_geometry(ent) }
processed_pairs = 0
(0...items.length).each do |i|
((i + 1)...items.length).each do |j|
ent1 = items[i]
ent2 = items[j]
# Проверяем пересечение
if ent1.bounds.intersect(ent2.bounds).valid?
if self.apply_drill_to_pair(ent1, ent2, cam_diameter_mm.mm, screw_diameter_mm.mm, offset_mm.mm, hole_count, marker_length_mm.mm)
processed_pairs += 1
end
end
end
end
model.commit_operation
end
# ... (get_diameter остается без изменений)
def self.get_diameter(entity, global_drill_axis, cam_dia, screw_dia)
trans_inv = entity.transformation.inverse
local_drill_axis = global_drill_axis.transform(trans_inv)
bb_local = entity.bounds
w, h, d = bb_local.width, bb_local.height, bb_local.depth
dims = { w => Geom::Vector3d.new(1, 0, 0), h => Geom::Vector3d.new(0, 1, 0), d => Geom::Vector3d.new(0, 0, 1) }
min_dim = dims.keys.min
local_thickness_axis = dims[min_dim]
dot_product = local_drill_axis.dot(local_thickness_axis).abs
return (dot_product > 0.999) ? cam_dia : screw_dia
end
# --- ОСНОВНАЯ ЛОГИКА ПРИСАДКИ И ГЕОМЕТРИИ (Minifix) ---
def self.apply_drill_to_pair(ent1, ent2, cam_diameter, screw_diameter, offset, hole_count, marker_length)
bb1 = ent1.bounds
bb2 = ent2.bounds
intersect = bb1.intersect(bb2)
return false if intersect.empty?
w, h, d = intersect.width, intersect.height, intersect.depth
dims = [w, h, d]
min_dim = dims.min
return false if dims.max < 10.mm
drill_axis = Geom::Vector3d.new(0,0,1)
long_axis_vec = Geom::Vector3d.new(1,0,0)
center = intersect.center
if min_dim == w
drill_axis = Geom::Vector3d.new(1, 0, 0)
long_axis_vec = (h > d) ? Geom::Vector3d.new(0, 1, 0) : Geom::Vector3d.new(0, 0, 1)
len = [h, d].max
elsif min_dim == h
drill_axis = Geom::Vector3d.new(0, 1, 0)
long_axis_vec = (w > d) ? Geom::Vector3d.new(1, 0, 0) : Geom::Vector3d.new(0, 0, 1)
len = [w, d].max
else
drill_axis = Geom::Vector3d.new(0, 0, 1)
long_axis_vec = (w > h) ? Geom::Vector3d.new(1, 0, 0) : Geom::Vector3d.new(0, 1, 0)
len = [w, h].max
end
drill_points = []
hole_count = [1, hole_count.to_i].max
max_drill_length = len - (offset * 2)
if hole_count == 1 || max_drill_length < 0.mm
drill_points << center
else
spacing = max_drill_length / (hole_count - 1).to_f
start_dist_from_center = (len / 2.0) - offset
start_point = center - long_axis_vec.clone.tap { |v| v.length = start_dist_from_center }
(0...hole_count).each do |i|
current_offset = long_axis_vec.clone.tap { |v| v.length = i * spacing }
drill_points << start_point + current_offset
end
end
[ent1, ent2].each do |ent|
defn = ent.respond_to?(:definition) ? ent.definition : ent.entities.parent
trans_inv = ent.transformation.inverse
current_diameter = self.get_diameter(ent, drill_axis, cam_diameter, screw_diameter)
drill_points.each do |global_pt|
local_pt = global_pt.transform(trans_inv)
local_axis = drill_axis.transform(trans_inv)
# Вынос на внешнюю сторону для отверстий в пласть
if current_diameter == cam_diameter
bounds = defn.bounds
bounds_center = bounds.center
axis_idx = local_axis.to_a.map(&:abs).each_with_index.max[1]
dir_val = bounds_center[axis_idx] - local_pt[axis_idx]
dir_val = 1.0 if dir_val.abs < 0.0001
target_coordinate = (dir_val > 0) ? bounds.max[axis_idx] : bounds.min[axis_idx]
local_pt[axis_idx] = target_coordinate
end
# 1. Создаем само отверстие (круг)
circle = defn.entities.add_circle(local_pt, local_axis, current_diameter / 2.0)
cpoint = defn.entities.add_cpoint(local_pt)
circle.each { |e| e.set_attribute('ABFDrill', 'Type', 'DrillHole') }
cpoint.set_attribute('ABFDrill', 'Type', 'DrillHole')
# 2. Логика для рисования прямоугольника-проекции (только линии)
if current_diameter == screw_diameter && marker_length > 0.0.mm
bounds = defn.bounds
# Определяем ось толщины (самая короткая сторона)
b_dims = [bounds.width, bounds.height, bounds.depth]
min_b_dim = b_dims.min
thick_axis_idx = b_dims.index(min_b_dim)
# Вектор нормали к грани (ось толщины)
face_normal = Geom::Vector3d.new(0,0,0)
face_normal[thick_axis_idx] = 1
# Вектор "ширины" прямоугольника
width_vec = local_axis.cross(face_normal)
if width_vec.length > 0.001
width_vec.length = current_diameter / 2.0
# Вектор "длины" маркера, используем значение из инпута
marker_len_vec = local_axis.clone
# Направление: Внутрь детали
vec_to_center = bounds.center - local_pt
if vec_to_center.dot(marker_len_vec) < 0
marker_len_vec.reverse!
end
marker_len_vec.length = marker_length
# Рисуем прямоугольники на обеих плоскостях
faces_coords = [bounds.min[thick_axis_idx], bounds.max[thick_axis_idx]]
faces_coords.each do |z_coord|
pt_on_face = local_pt.clone
pt_on_face[thick_axis_idx] = z_coord
# 4 точки прямоугольника
p1 = pt_on_face + width_vec
p2 = pt_on_face - width_vec
p3 = p2 + marker_len_vec
p4 = p1 + marker_len_vec
# Создаем группу для маркера
m_group = defn.entities.add_group
# Создаем Face, чтобы потом его удалить, но сохранить Edges
m_face = m_group.entities.add_face(p1, p2, p3, p4)
if m_face
# УДАЛЯЕМ ГРАНЬ, ОСТАВЛЯЯ ТОЛЬКО ЛИНИИ (Edges)
m_face.erase!
# Метим группу как DrillHole для очистки
m_group.set_attribute('ABFDrill', 'Type', 'DrillHole')
else
# Если грань не создалась, удаляем пустую группу
m_group.erase!
end
end
end
end
# --- КОНЕЦ ЛОГИКИ ПРЯМОУГОЛЬНИКА ---
end
end
return true
end
# --- ИСПРАВЛЕННЫЙ МЕТОД: Рисование компенсированного круга ---
# Этот метод создает круг, который останется кругом даже после не-униформного масштабирования
def self.draw_drill_hole_geometry(entity, center_pt, radius, normal_axis, type_tag)
defn_entities = entity.respond_to?(:definition) ? entity.definition.entities : entity.entities
trans = entity.transformation
# 1. Получаем обратную трансформацию для компенсации масштабирования
inv_trans = trans.inverse
# 2. Получаем мировые оси, преобразованные в локальные координаты определения
world_axes = [
Geom::Vector3d.new(1, 0, 0),
Geom::Vector3d.new(0, 1, 0),
Geom::Vector3d.new(0, 0, 1)
]
# 3. Преобразуем нормаль в мировые координаты
world_normal = normal_axis.transform(trans)
# 4. Находим две оси, ортогональные нормали (в мировых координатах)
world_axes_in_plane = world_axes.reject { |axis| axis.parallel?(world_normal) }
if world_axes_in_plane.length < 2
# Если не можем найти оси, используем альтернативный метод
world_axes_in_plane = [
world_normal.axes[0],
world_normal.axes[1]
]
end
axis1 = world_axes_in_plane[0]
axis2 = world_axes_in_plane[1]
# 5. Преобразуем оси обратно в локальные координаты определения
local_axis1 = axis1.transform(inv_trans)
local_axis2 = axis2.transform(inv_trans)
# 6. Нормализуем оси
local_axis1.normalize!
local_axis2.normalize!
# 7. Убеждаемся, что оси ортогональны
if local_axis1.dot(local_axis2) > 0.001
# Делаем axis2 ортогональным axis1
local_axis2 = local_axis2 - local_axis1 * local_axis1.dot(local_axis2)
local_axis2.normalize!
end
# 8. Создаем круг с использованием add_circle (который Sketchup корректно масштабирует)
# Вместо попытки компенсировать масштабирование, мы рисуем круг в определении,
# и Sketchup сам правильно масштабирует его при любом трансформировании экземпляра
circle = defn_entities.add_circle(center_pt, normal_axis, radius)
circle.each { |e| e.set_attribute('ABFDrill', 'Type', type_tag) }
# 9. Добавляем CPoint
cpoint = defn_entities.add_cpoint(center_pt)
cpoint.set_attribute('ABFDrill', 'Type', type_tag)
return true
end
# --- ГЛАВНЫЙ ПРОЦЕСС ДЛЯ ПЕТЕЛЬ ---
def self.process_hinge_holes(count, diameter_mm, length_offset_mm, side_offset_mm, screw_diameter_mm, screw_spacing_mm, screw_vert_offset_mm)
model = Sketchup.active_model
selection = model.selection
items = selection.grep(Sketchup::ComponentInstance) + selection.grep(Sketchup::Group)
if items.empty?
UI.messagebox("Выберите один или несколько компонентов для установки петель.")
return
end
model.start_operation("ABF Style Add Hinges", true)
# Очищаем только петли на выделенных компонентах
items.each { |ent| self.cleanup_hinge_drill_geometry(ent) }
items.each do |entity|
self.apply_hinge_holes(entity, count.to_i, diameter_mm.mm, length_offset_mm.mm, side_offset_mm.mm, screw_diameter_mm.mm, screw_spacing_mm.mm, screw_vert_offset_mm.mm)
end
model.commit_operation
rescue => e
UI.messagebox("Ошибка при создании петель: #{e.message}")
model.abort_operation
end
# --- ЛОГИКА ДЛЯ СОЗДАНИЯ ОТВЕРСТИЙ ПОД ПЕТЛИ ---
def self.apply_hinge_holes(entity, count, diameter, length_offset, side_offset, screw_diameter, screw_spacing, screw_vert_offset)
defn = entity.respond_to?(:definition) ? entity.definition : entity.entities.parent
bounds = defn.bounds
# 1. Определяем оси компонента в локальных координатах
dims = [bounds.width, bounds.height, bounds.depth]
# Ось толщины (самая короткая) - Нормаль круга
min_dim = dims.min
thick_axis_idx = dims.index(min_dim)
thick_axis = Geom::Vector3d.new(0,0,0)
thick_axis[thick_axis_idx] = 1.0
# Ось длины (самая длинная) - Ось для распределения петель
max_dim = dims.max
long_axis_idx = dims.index(max_dim)
long_axis = Geom::Vector3d.new(0,0,0)
long_axis[long_axis_idx] = 1.0
# Ось ширины (средняя) - Ось для смещения 21.5мм и смещения саморезов
mid_dim = dims.sort[1]
mid_axis_idx = dims.index(mid_dim)
mid_axis = Geom::Vector3d.new(0,0,0)
mid_axis[mid_axis_idx] = 1.0
# 2. Определяем плоскость для отверстий (по центру толщины)
center_point = bounds.center.clone
# 3. Определяем начальную точку и направление (Вдоль длинной оси)
# Начальная координата для смещения по длине
start_coord_length = bounds.min[long_axis_idx]
offset_vec_length = long_axis.clone
offset_vec_length.length = length_offset
start_point_length = center_point.clone
start_point_length[long_axis_idx] = start_coord_length
start_point_length = start_point_length + offset_vec_length
# 4. Рассчитываем точки для всех петель (Вдоль длинной оси)
hole_points_length = []
length = max_dim
count = [1, count].max
if count == 1
hole_points_length << center_point
else
# Расстояние между центрами петель
remaining_length = length - (length_offset * 2)
if remaining_length < 0.mm
hole_points_length << center_point
else
spacing = remaining_length / (count - 1).to_f
(0...count).each do |i|
current_point = start_point_length + long_axis.clone.tap { |v| v.length = i * spacing }
hole_points_length << current_point
end
end
end
# 5. Применяем смещение по боковой оси (side_offset)
# Начальная координата по средней оси (от которой отсчитываем 21.5мм)
start_coord_side = bounds.min[mid_axis_idx]
# Вектор смещения по средней оси
side_offset_vec = mid_axis.clone
side_offset_vec.length = side_offset
# Точка, смещенная от края по средней оси
side_offset_point = center_point.clone
side_offset_point[mid_axis_idx] = start_coord_side
side_offset_point = side_offset_point + side_offset_vec
# Координата поверхности (max координата по оси толщины)
surface_coord = bounds.max[thick_axis_idx]
hole_points_length.each do |local_pt_length|
final_pt = local_pt_length.clone
# Устанавливаем координату по средней оси (смещение 21.5мм)
final_pt[mid_axis_idx] = side_offset_point[mid_axis_idx]
# Устанавливаем координату по оси толщины (на поверхность)
final_pt[thick_axis_idx] = surface_coord
# 1. Создаем отверстие под чашку (круг 35мм) - ИСПОЛЬЗУЕМ КОМПЕНСАЦИЮ
self.draw_drill_hole_geometry(entity, final_pt, diameter / 2.0, thick_axis, 'HingeHole')
# --- ЛОГИКА ДЛЯ ОТВЕРСТИЙ ПОД САМОРЕЗЫ ---
half_spacing = screw_spacing / 2.0
# Вектор смещения "выше чашки" (вдоль mid_axis)
shift_vec = mid_axis.clone
shift_vec.length = screw_vert_offset
# Базовые точки вдоль длинной оси (long_axis), центрированные на final_pt
pt1_base = final_pt - long_axis.clone.tap { |v| v.length = half_spacing }
pt2_base = final_pt + long_axis.clone.tap { |v| v.length = half_spacing }
# Применяем вертикальное смещение (shift_vec) к обеим точкам
pt1 = pt1_base + shift_vec
pt2 = pt2_base + shift_vec
[pt1, pt2].each do |screw_pt|
# Создаем отверстие под саморез (круг) - ИСПОЛЬЗУЕМ КОМПЕНСАЦИЮ
self.draw_drill_hole_geometry(entity, screw_pt, screw_diameter / 2.0, thick_axis, 'HingeHole')
end
# --- КОНЕЦ ЛОГИКИ САМОРЕЗОВ ---
end
end
# --- НОВЫЙ ГЛАВНЫЙ ПРОЦЕСС ДЛЯ ПЛАНКИ (ОБНОВЛЕНО) ---
# Изменено: hinge_count и hinge_offset_mm заменены на count и length_offset_mm
def self.process_strip_holes(diameter_mm, spacing_mm, side_offset_mm, count, length_offset_mm)
model = Sketchup.active_model
selection = model.selection
items = selection.grep(Sketchup::ComponentInstance) + selection.grep(Sketchup::Group)
if items.length != 1
UI.messagebox("Выберите ОДИН компонент для установки отверстий под планку.")
# Очищаем на всех выделенных, если их больше одного, или если ничего не выбрано
model.start_operation("ABF Strip Drill Cleanup", true)
items.each { |ent| self.cleanup_strip_drill_geometry(ent) }
model.commit_operation
return
end
entity = items.first
model.start_operation("ABF Style Add Strip Holes", true)
self.cleanup_strip_drill_geometry(entity) # Очистка перед созданием
# Передаем параметры для позиционирования
self.apply_strip_holes(entity, diameter_mm.mm, spacing_mm.mm, side_offset_mm.mm, count.to_i, length_offset_mm.mm)
model.commit_operation
rescue => e
UI.messagebox("Ошибка при создании отверстий под планку: #{e.message}")
model.abort_operation
end
# --- НОВАЯ ЛОГИКА ДЛЯ СОЗДАНИЯ ОТВЕРСТИЙ ПОД ПЛАНКУ (ОБНОВЛЕНО) ---
# Изменено: hinge_count и hinge_offset заменены на count и length_offset
def self.apply_strip_holes(entity, diameter, spacing, side_offset, count, length_offset)
defn = entity.respond_to?(:definition) ? entity.definition : entity.entities.parent
bounds = defn.bounds
# 1. Определяем оси компонента в локальных координатах
dims = [bounds.width, bounds.height, bounds.depth]
# Ось толщины (самая короткая) - Ось сверления (Normal)
min_dim = dims.min
thick_axis_idx = dims.index(min_dim)
thick_axis = Geom::Vector3d.new(0,0,0)
thick_axis[thick_axis_idx] = 1.0
# Ось длины (самая длинная) - Ось для распределения петель/планок
max_dim = dims.max
long_axis_idx = dims.index(max_dim)
long_axis = Geom::Vector3d.new(0,0,0)
long_axis[long_axis_idx] = 1.0
# Ось ширины (средняя) - Ось для смещения от края (Side Offset)
mid_dim = dims.sort[1]
mid_axis_idx = dims.index(mid_dim)
mid_axis = Geom::Vector3d.new(0,0,0)
mid_axis[mid_axis_idx] = 1.0
# 2. Расчет позиций центров планок вдоль длинной оси (long_axis)
center_point = bounds.center.clone
length = max_dim
count = [1, count].max
hole_points_length = [] # Это будут центры пар отверстий планки
if count == 1
# Если одна планка, то центр планки совпадает с центром компонента по длине
hole_points_length << center_point
else
# Начальная координата для смещения по длине (от короткого края)
start_coord_length = bounds.min[long_axis_idx]
offset_vec_length = long_axis.clone
offset_vec_length.length = length_offset # Используем новое смещение
start_point_length = center_point.clone
start_point_length[long_axis_idx] = start_coord_length
start_point_length = start_point_length + offset_vec_length
# Расстояние между центрами планок
remaining_length = length - (length_offset * 2) # Используем новое смещение
if remaining_length < 0.mm
hole_points_length << center_point
else
spacing_strip = remaining_length / (count - 1).to_f
(0...count).each do |i|
# current_point - это центр пары планки по длинной оси
current_point = start_point_length + long_axis.clone.tap { |v| v.length = i * spacing_strip }
hole_points_length << current_point
end
end
end
# 3. Определяем координаты для смещения 37мм (Side Offset)
# Начальная координата по средней оси (от которой отсчитываем 37мм)
start_coord_side = bounds.min[mid_axis_idx]
# Координата центра отверстий по средней оси (37мм от края)
side_coord = start_coord_side + side_offset
# 4. Определяем координату поверхности (по оси толщины)
# Отверстия сверлятся в пласть, поэтому берем max координату по оси толщины
surface_coord = bounds.max[thick_axis_idx]
# 5. Создаем точки и отверстия
half_spacing = spacing / 2.0 # Половина межосевого расстояния планки (32/2 = 16мм)
# hole_points_length - это центры пар планок
hole_points_length.each do |center_pt_long_axis|
# Координата центра пары отверстий по длинной оси
center_coord_length = center_pt_long_axis[long_axis_idx]
# --- НОВОЕ: Создание точки в центре между отверстиями планки ---
final_center_pt = Geom::Point3d.new(0, 0, 0)
final_center_pt[long_axis_idx] = center_coord_length
final_center_pt[mid_axis_idx] = side_coord
final_center_pt[thick_axis_idx] = surface_coord
cpoint = defn.entities.add_cpoint(final_center_pt)
cpoint.set_attribute('ABFDrill', 'Type', 'StripHole')
# --- КОНЕЦ НОВОГО БЛОКА ---
# Координаты центров отверстий по длинной оси (смещение +/- 16мм)
length_coord_1 = center_coord_length - half_spacing
length_coord_2 = center_coord_length + half_spacing
hole_coords_length = [length_coord_1, length_coord_2]
# Создаем пару отверстий
hole_coords_length.each do |length_coord|
final_pt = Geom::Point3d.new(0, 0, 0)
# Устанавливаем координаты
final_pt[long_axis_idx] = length_coord # Смещение +/- 16мм от центра
final_pt[mid_axis_idx] = side_coord # Смещение 37мм от края
final_pt[thick_axis_idx] = surface_coord # На поверхности
# Создаем отверстие - ИСПОЛЬЗУЕМ КОМПЕНСАЦИЮ
self.draw_drill_hole_geometry(entity, final_pt, diameter / 2.0, thick_axis, 'StripHole')
end
end
end
# --- МЕТОД: Аннотации (обновлен для включения StripHole) ---
def self.show_drill_annotations
model = Sketchup.active_model
entities = model.active_entities
selection = model.selection
model.start_operation('Проставить/Удалить Аннотации Присадок', true)
if @annotations_visible
self.cleanup_annotations(entities)
@annotations_visible = false
model.commit_operation
UI.messagebox("Аннотации размеров удалены.")
return
end
items = selection.grep(Sketchup::ComponentInstance) + selection.grep(Sketchup::Group)
if items.empty?
UI.messagebox("Выберите компоненты.")
model.abort_operation
return
end
self.cleanup_annotations(entities)
base_offset_distance = 50.mm
step_size = 50.mm
items.each do |entity|
bb = entity.bounds
bb_center = bb.center
defn_entities = entity.respond_to?(:definition) ? entity.definition.entities : entity.entities
# Общие размеры
overall_offset_y = -350.mm
start_pt_x = Geom::Point3d.new(bb.min.x, bb.min.y, bb.min.z)
end_pt_x = Geom::Point3d.new(bb.max.x, bb.min.y, bb.min.z)
offset_x = Geom::Vector3d.new(0, overall_offset_y, 0)
dim_overall_x = entities.add_dimension_linear(start_pt_x, end_pt_x, offset_x)
dim_overall_x.set_attribute('ABFDrill', 'Type', 'Dimension')
overall_offset_x = -350.mm
start_pt_y = Geom::Point3d.new(bb.min.x, bb.min.y, bb.min.z)
end_pt_y = Geom::Point3d.new(bb.min.x, bb.max.y, bb.min.z)
offset_y = Geom::Vector3d.new(overall_offset_x, 0, 0)
dim_overall_y = entities.add_dimension_linear(start_pt_y, end_pt_y, offset_y)
dim_overall_y.set_attribute('ABFDrill', 'Type', 'Dimension')
# Размеры до отверстий
# Собираем все точки присадки, петель и планки
drill_cpoints = defn_entities.grep(Sketchup::ConstructionPoint).select do |e|
type = e.get_attribute('ABFDrill', 'Type')
type == 'DrillHole' || type == 'HingeHole' || type == 'StripHole'
end
dimensions_to_place = []
drill_cpoints.each do |cpoint|
hole_pt = cpoint.position.transform(entity.transformation)
# X Dimension
dist_from_min_x = hole_pt.x - bb.min.x
dist_from_max_x = bb.max.x - hole_pt.x
if dist_from_min_x <= dist_from_max_x
start_pt_x = Geom::Point3d.new(bb.min.x, hole_pt.y, hole_pt.z)
value_x = dist_from_min_x
else
start_pt_x = Geom::Point3d.new(bb.max.x, hole_pt.y, hole_pt.z)
value_x = dist_from_max_x
end
direction_x = (hole_pt.y > bb_center.y) ? :Up : :Down
if value_x.abs > 0.001.mm
dimensions_to_place << { :type => :X, :value => value_x, :start_pt => start_pt_x, :end_pt => hole_pt, :direction => direction_x }
end
# Y Dimension
dist_from_min_y = hole_pt.y - bb.min.y
dist_from_max_y = bb.max.y - hole_pt.y
if dist_from_min_y <= dist_from_max_y
start_pt_y = Geom::Point3d.new(hole_pt.x, bb.min.y, hole_pt.z)
value_y = dist_from_min_y
else
start_pt_y = Geom::Point3d.new(hole_pt.x, bb.max.y, hole_pt.z)
value_y = dist_from_max_y
end
direction_y = (hole_pt.x > bb_center.x) ? :Right : :Left
if value_y.abs > 0.001.mm
dimensions_to_place << { :type => :Y, :value => value_y, :start_pt => start_pt_y, :end_pt => hole_pt, :direction => direction_y }
end
end
grouped_dims = dimensions_to_place.group_by { |d| d[:direction] }
step_counters = { :Up => 0, :Down => 0, :Right => 0, :Left => 0 }
grouped_dims.each do |direction, dims|
dims.sort_by! { |d| d[:value] }
dims.each do |d|
step = step_counters[direction]
offset_val = base_offset_distance + step * step_size
offset_vec = case direction
when :Up then Geom::Vector3d.new(0, offset_val, 0)
when :Down then Geom::Vector3d.new(0, -offset_val, 0)
when :Right then Geom::Vector3d.new(offset_val, 0, 0)
when :Left then Geom::Vector3d.new(-offset_val, 0, 0)
end
dim = entities.add_dimension_linear(d[:start_pt], d[:end_pt], offset_vec)
dim.set_attribute('ABFDrill', 'Type', 'Dimension')
step_counters[direction] += 1
end
end
end
@annotations_visible = true
model.commit_operation
UI.messagebox("Аннотации проставлены.")
rescue => e
UI.messagebox("Ошибка: #{e.message}")
model.abort_operation
end
# --- МЕТОД: Раскладка (без изменений) ---
def self.line_up_components
model = Sketchup.active_model
selection = model.selection
entities_to_line_up = selection.grep(Sketchup::ComponentInstance) + selection.grep(Sketchup::Group)
if entities_to_line_up.empty?
UI.messagebox("Выберите компоненты.")
return
end
model.start_operation('Разложить в линию', true)
begin
self.cleanup_annotations(model.active_entities)
@annotations_visible = false
entities_to_line_up.sort_by! do |e|
b = e.bounds
[b.width, b.height, b.depth].max
end
cursor_x = 0.0
gap = GAP
entities_to_line_up.each do |entity|
# Сброс трансформации (включая масштабирование) - это гарантирует,
# что круги, нарисованные в определении, будут выглядеть как круги.
entity.transformation = Geom::Transformation.new
bounds = entity.bounds
dims = [bounds.width, bounds.height, bounds.depth]
smallest_idx = dims.each_with_index.min[1]
rot_flat = Geom::Transformation.new
if smallest_idx == 0
rot_flat = Geom::Transformation.rotation(bounds.center, Geom::Vector3d.new(0, 1, 0), -90.degrees)
elsif smallest_idx == 1
rot_flat = Geom::Transformation.rotation(bounds.center, Geom::Vector3d.new(1, 0, 0), 90.degrees)
end
entity.transform!(rot_flat)
bounds = entity.bounds
rot_orient = Geom::Transformation.new
if bounds.width < bounds.height
rot_orient = Geom::Transformation.rotation(bounds.center, Geom::Vector3d.new(0, 0, 1), 90.degrees)
end
entity.transform!(rot_orient)
bounds = entity.bounds
move_vec = Geom::Vector3d.new(cursor_x - bounds.min.x, -bounds.min.y, -bounds.min.z)
entity.transform!(Geom::Transformation.translation(move_vec))
cursor_x = entity.bounds.max.x + gap
end
model.active_view.zoom(entities_to_line_up)
rescue => e
UI.messagebox("Ошибка: #{e.message}")
ensure
model.commit_operation
end
end
unless file_loaded?(__FILE__)
menu = UI.menu('Plugins')
menu.add_item('ABF Style Drill & Hinges') { self.show_dialog }
file_loaded(__FILE__)
end
end
Why are these changes needed?
Example that previous test fails doesn’t block sequential tests
https://github.com/microsoft/autogen/actions/runs/6759692725/job/18372611806
(Install packages and dependencies for RetrieveChat -> fails
Run “Install packages and dependencies for MathChat” -> success
Run “test MathChat” -> success
Result is still error
)
When previous dependency fails, corresponding testing will be disabled.
https://github.com/microsoft/autogen/actions/runs/6759701280/job/18372633270?pr=555
(Run “ Install packages and dependencies for RetrieveChat” -> fail
Run “”Install packages and dependencies for MathChat ->fail
skipping test MathChat)
Related issue number
Checks