Я пытаюсь создать головоломку в FreeCad. Если вы вставите первый блок кода в интерпретатор Python FreeCad v1.1, он создаст сетку блоков. Это демо-пример среды. Цель состоит в том, чтобы соединить каждый квадрат сетки.
Затем, когда я вставляю второй блок кода, он создает формы «ласточкин хвост», которые будут вырезаны из женских блоков и слиты с мужскими блоками.
Это все правильно и выглядит идеально.
Но проблема в третьем блоке. Все «ласточкины хвосты» срастаются правильно, но режутся неправильно. Лишь некоторые из них режут правильно. Один или два будут обрезаны правильно, но остальные - неудачно.
Я не уверен, это мой код или проблема с FreeCAD... Есть идеи, это известная ошибка или я делаю что-то не так?
Спасибо! Первый код - создайте демонстрационную среду
import FreeCAD as App
import Part
# Create a new document
doc = App.activeDocument()
if not doc:
doc = App.newDocument("GridDocument")
# Dimensions of each box
size = 10.0
# Grid settings: 3 columns, 2 rows (total 6 boxes)
columns = 3
rows = 3
for i in range(columns):
for j in range(rows):
# Create the box shape
box_shape = Part.makeBox(size, size, size)
# Create a document object to hold the shape
box_obj = doc.addObject("Part::Feature", f"Box_{i}_{j}")
box_obj.Shape = box_shape
# Calculate position (all touching)
x_pos = i * size
y_pos = j * size
# Apply the position
box_obj.Placement = App.Placement(App.Vector(x_pos, y_pos, 0), App.Rotation(0, 0, 0))
# Refresh the 3D view
doc.recompute()
```py
import FreeCAD as App
import Part
def get_absolute_placement(obj):
"""Calculates global placement to handle nested containers."""
global_plm = obj.Placement
current_obj = obj
while True:
parents = current_obj.InList
if not parents:
break
found_parent = False
for parent in parents:
if hasattr(parent, "Placement"):
global_plm = parent.Placement.multiply(global_plm)
current_obj = parent
found_parent = True
break
if not found_parent:
break
return global_plm
def get_global_center(obj):
"""Fast approximation of global center using BoundingBox center."""
if not hasattr(obj, "Shape"):
return App.Vector(0,0,0)
# Get global placement
plm = get_absolute_placement(obj)
# Get local center
bb = obj.Shape.BoundBox
local_center = bb.Center
# Transform to global
return plm.multVec(local_center)
def apply_existing_tool(tool_obj, name_a, name_b):
doc = App.activeDocument()
# 1. Find the actual panel objects
obj_a = doc.getObject(name_a)
obj_b = doc.getObject(name_b)
if not obj_a or not obj_b:
print(f"Skipping {tool_obj.Name}: Could not find panels '{name_a}' or '{name_b}'")
return False
# 2. Determine Orientation (Who is Male/Pin? Who is Female/Hole?)
# We re-evaluate the relative positions to ensure consistency with the standard logic:
# Left = Male, Right = Female
# Bottom = Male, Top = Female
pos_a = get_global_center(obj_a)
pos_b = get_global_center(obj_b)
dx = pos_b.x - pos_a.x
dy = pos_b.y - pos_a.y
male_obj = None
female_obj = None
if abs(dx) > abs(dy):
# Horizontal Interface
if dx > 0: # B is right of A
male_obj, female_obj = obj_a, obj_b
else: # A is right of B
male_obj, female_obj = obj_b, obj_a
else:
# Vertical Interface
if dy > 0: # B is above A
male_obj, female_obj = obj_a, obj_b
else: # A is above B
male_obj, female_obj = obj_b, obj_a
# 3. Apply the Tool
# The Tool is already in Global Space. We must transform it to Local Space.
tool_shape_global = tool_obj.Shape.copy()
# --- Fuse to Male ---
male_global_plm = get_absolute_placement(male_obj)
to_male_local = male_global_plm.inverse().toMatrix()
tool_for_male = tool_shape_global.copy()
tool_for_male.transformShape(to_male_local)
male_obj.Shape = male_obj.Shape.fuse(tool_for_male)
# --- Cut from Female ---
female_global_plm = get_absolute_placement(female_obj)
to_female_local = female_global_plm.inverse().toMatrix()
tool_for_female = tool_shape_global.copy()
tool_for_female.transformShape(to_female_local)
female_obj.Shape = female_obj.Shape.cut(tool_for_female)
return True
def main():
doc = App.activeDocument()
if not doc:
print("No active document.")
return
# Find all preview tools
preview_tools = []
for obj in doc.Objects:
if obj.Name.startswith("Preview_Tool_"):
preview_tools.append(obj)
if not preview_tools:
print("No 'Preview_Tool' objects found. Run the preview script first.")
return
print(f"Found {len(preview_tools)} preview tools. Applying operations...")
doc.openTransaction("Apply Preview Tools")
processed_count = 0
for tool in preview_tools:
# Parse name: Preview_Tool_{NameA}_vs_{NameB}
# We split by "_vs_" to separate the two panels
# The prefix is "Preview_Tool_" (length 13)
raw_name = tool.Name[13:] # Strip "Preview_Tool_"
if "_vs_" not in raw_name:
print(f"Skipping {tool.Name}: Naming format incorrect.")
continue
parts = raw_name.split("_vs_")
if len(parts) != 2:
print(f"Skipping {tool.Name}: Could not parse panel names.")
continue
name_a = parts[0]
name_b = parts[1]
success = apply_existing_tool(tool, name_a, name_b)
if success:
processed_count += 1
# Delete the tool after use to clean up
doc.removeObject(tool.Name)
doc.commitTransaction()
doc.recompute()
print(f"Done. Applied {processed_count} joints and removed preview tools.")
if __name__ == "__main__":
main()
После запуска третьего кода я получаю следующее:
Но вы увидите, что если я начну скрывать блоки, большинства слотов там не будет. Некоторые из них есть, но большинства нет. Здесь я скрываю нижний левый угол, и вы видите, что в соседних блоках отсутствуют слоты.
Я пытаюсь создать головоломку в FreeCad. Если вы вставите первый блок кода в интерпретатор Python FreeCad v1.1, он создаст сетку блоков. Это демо-пример среды. Цель состоит в том, чтобы соединить каждый квадрат сетки. Затем, когда я вставляю второй блок кода, он создает формы «ласточкин хвост», которые будут вырезаны из женских блоков и слиты с мужскими блоками. Это все правильно и выглядит идеально. Но проблема в третьем блоке. Все «ласточкины хвосты» срастаются правильно, но режутся неправильно. Лишь некоторые из них режут правильно. Один или два будут обрезаны правильно, но остальные - неудачно. Я не уверен, это мой код или проблема с FreeCAD... Есть идеи, это известная ошибка или я делаю что-то не так? Спасибо! [b]Первый код - создайте демонстрационную среду[/b] [code]import FreeCAD as App import Part
# Create a new document doc = App.activeDocument() if not doc: doc = App.newDocument("GridDocument")
for i in range(columns): for j in range(rows): # Create the box shape box_shape = Part.makeBox(size, size, size)
# Create a document object to hold the shape box_obj = doc.addObject("Part::Feature", f"Box_{i}_{j}") box_obj.Shape = box_shape
# Calculate position (all touching) x_pos = i * size y_pos = j * size
# Apply the position box_obj.Placement = App.Placement(App.Vector(x_pos, y_pos, 0), App.Rotation(0, 0, 0))
# Refresh the 3D view doc.recompute() [/code] [b]Второй код — создает формы «ласточкин хвост»[/b] [code]import FreeCAD as App import FreeCADGui as Gui import Part
def get_absolute_placement(obj): """Recursive calculation to handle nested containers/LinkGroups.""" global_plm = obj.Placement current_obj = obj while True: parents = current_obj.InList if not parents: break found_parent = False for parent in parents: if hasattr(parent, "Placement"): global_plm = parent.Placement.multiply(global_plm) current_obj = parent found_parent = True break if not found_parent: break return global_plm
def get_global_corners(obj): """ Returns a SET of rounded global corner tuples for an object. """ # 1. Determine Global Placement is_link_related = False if obj.isDerivedFrom("App::Link") or obj.isDerivedFrom("App::LinkGroup"): is_link_related = True else: for parent in obj.InList: if parent.isDerivedFrom("App::LinkGroup"): is_link_related = True break
# 2. Center the tool vertically (Z-axis) # Move it down by half the oversize so it extends slightly above and below solid.translate(App.Vector(0, 0, -(oversize/2.0)))
# 3. Rotate if vertical orientation is required if is_vertical: solid.rotate(App.Vector(0,0,0), App.Vector(0,0,1), 90)
# 4. Move to global position solid.translate(center_point)
return solid
def create_preview_object(doc, tool_shape, name_a, name_b): """ Creates a visual object in the document instead of performing boolean ops. """ label = f"Preview_Tool_{name_a}_vs_{name_b}" obj = doc.addObject("Part::Feature", label) obj.Shape = tool_shape
# Visual properties: Red and 50% transparent obj.ViewObject.ShapeColor = (1.0, 0.0, 0.0) obj.ViewObject.Transparency = 50 return obj
# ============================================================================== # PART 3: MAIN EXECUTION # ==============================================================================
def main(): selection = Gui.Selection.getSelection() if not selection or len(selection) < 2: print("Error: Select at least 2 panels.") return
doc = App.activeDocument() print(f"Generating PREVIEW tools for {len(selection)} panels...")
panel_data = {}
for obj in selection: c_set = get_global_corners(obj) if c_set: # Convert to list for indexing coordinates c_list = list(c_set) xs = [p[0] for p in c_list] ys = [p[1] for p in c_list] zs = [p[2] for p in c_list]
# Determine orientation and generate tool if abs(dx) > abs(dy): # Neighbors are left/right of each other (Horizontal difference) # Joint runs along Y, so interface length is Y size (sy) ref_size = min(data_a["sy"], data_b["sy"]) tool = make_dovetail_tool(joint_center, False, h_val, ref_size) create_preview_object(doc, tool, name_a, name_b) tool_count += 1 else: # Neighbors are above/below each other (Vertical difference) # Joint runs along X, so interface length is X size (sx) ref_size = min(data_a["sx"], data_b["sx"]) # is_vertical=True rotates the tool to face Y tool = make_dovetail_tool(joint_center, True, h_val, ref_size) create_preview_object(doc, tool, name_a, name_b) tool_count += 1
if __name__ == "__main__": main() [/code] [b]Третий код — соедините и разрежьте фигуры «ласточкин хвост»[/b] [code] ```py
import FreeCAD as App import Part
def get_absolute_placement(obj): """Calculates global placement to handle nested containers.""" global_plm = obj.Placement current_obj = obj while True: parents = current_obj.InList if not parents: break found_parent = False for parent in parents: if hasattr(parent, "Placement"): global_plm = parent.Placement.multiply(global_plm) current_obj = parent found_parent = True break if not found_parent: break return global_plm
def get_global_center(obj): """Fast approximation of global center using BoundingBox center.""" if not hasattr(obj, "Shape"): return App.Vector(0,0,0)
# Get global placement plm = get_absolute_placement(obj)
# Get local center bb = obj.Shape.BoundBox local_center = bb.Center
# Transform to global return plm.multVec(local_center)
# 1. Find the actual panel objects obj_a = doc.getObject(name_a) obj_b = doc.getObject(name_b)
if not obj_a or not obj_b: print(f"Skipping {tool_obj.Name}: Could not find panels '{name_a}' or '{name_b}'") return False
# 2. Determine Orientation (Who is Male/Pin? Who is Female/Hole?) # We re-evaluate the relative positions to ensure consistency with the standard logic: # Left = Male, Right = Female # Bottom = Male, Top = Female
if abs(dx) > abs(dy): # Horizontal Interface if dx > 0: # B is right of A male_obj, female_obj = obj_a, obj_b else: # A is right of B male_obj, female_obj = obj_b, obj_a else: # Vertical Interface if dy > 0: # B is above A male_obj, female_obj = obj_a, obj_b else: # A is above B male_obj, female_obj = obj_b, obj_a
# 3. Apply the Tool # The Tool is already in Global Space. We must transform it to Local Space.
tool_shape_global = tool_obj.Shape.copy()
# --- Fuse to Male --- male_global_plm = get_absolute_placement(male_obj) to_male_local = male_global_plm.inverse().toMatrix()
for tool in preview_tools: # Parse name: Preview_Tool_{NameA}_vs_{NameB} # We split by "_vs_" to separate the two panels # The prefix is "Preview_Tool_" (length 13)
raw_name = tool.Name[13:] # Strip "Preview_Tool_" if "_vs_" not in raw_name: print(f"Skipping {tool.Name}: Naming format incorrect.") continue
parts = raw_name.split("_vs_") if len(parts) != 2: print(f"Skipping {tool.Name}: Could not parse panel names.") continue
name_a = parts[0] name_b = parts[1]
success = apply_existing_tool(tool, name_a, name_b) if success: processed_count += 1 # Delete the tool after use to clean up doc.removeObject(tool.Name)
doc.commitTransaction() doc.recompute() print(f"Done. Applied {processed_count} joints and removed preview tools.")
if __name__ == "__main__": main() [/code] После запуска третьего кода я получаю следующее: [img]https://i.sstatic.net/McyfiXpB.png[/img] Но вы увидите, что если я начну скрывать блоки, большинства слотов там не будет. Некоторые из них есть, но большинства нет. Здесь я скрываю нижний левый угол, и вы видите, что в соседних блоках отсутствуют слоты. [img]https://i.sstatic.net/XI0XJnCc.png[/img]