Export helper object from FreeCAD: grasp poses, part's zero positions, planar (tables, surfaces) and volumetric zones (storage, boxes)

This commit is contained in:
Mark Voltov 2023-06-18 15:55:12 +00:00 committed by Igor Brylyov
parent a38c3bec5a
commit 9fa936cfba
26 changed files with 12259 additions and 2 deletions

View file

@ -0,0 +1,286 @@
"
#Создаем объект, к которому привязываем вспомогательную информацию:
1. Выбираем элемент в модели:
1.1. Поверхность - можем создать призматический объект. Соответствует точке захвата, рабочей зоне, опорной поверхности, поверхности базирования
1.1.1 Выбираем тип объкта, который нам нужно построить.
1.1.2 В зависимости от выбора, предлагается указать объект для ориентации осей.
1.1.3 Указать размеры зоны построения. Для захвата соответствует габаритам пальца, для рабочей зоны - координатам. Полуавтоматическое (ручное???) создание эскиза?
1.1.4 Для захвата выбираем вытягивание объекта на нужную длину до параллельной поверхности
1.1.5 - Результат - построенный параллелограмм с размерами и привязкой к моделям. Его мы экспортируем в json через существующий функционал
1.2. Цилиндрическая поверхность - осесимметричный объект. Захват двухпальцевый, трехпальцевый, четырехпальцевый, цилиндрическая зона установки, отверстия сопряжения
1.2.1 Выбираем тип объекта
1.2.2. Выбираем ориентацию главной оси. //Потенциально, мы можем захватить цилиндрический объект в любой ориентации, нужно ли добавлять конкретное указание?
видимо, только если геометрия обязывает нас придерживаться ее (напр. не дает поставить 4 палец)
указание требуется, если есть фиксаторы, опорные площадки или что-то иное, что приводит к однозначному позиционированию
1.2.3. Вытягиваем зону до привязки. Пальцы ставим как цилиндрический массив( нужно ли??)
1.2.4 Результат - цилиндрическая зона с радиусом, ориентациями осей.
2. К размеченным объектам необходимо привязать метаданные, содержащие связи с конкретными моделями. STEP-файл захвата, step-файл входной детали и/или выходной.
2.1 Нужно ли привязывать захват? Да, но в некоем обобщенном виде. Позиция захвата - характеристика способа воздействия, а не объекта.
2.2 Для станков входы и выходы необходимы.
2.3
"
#!/usr/bin/env python3
# coding: utf-8
#
# LGPL
# Copyright HUBERT Zoltán
#
# newDatumCmd.py
import os
from PySide import QtGui, QtCore
import FreeCADGui as Gui
import FreeCAD as App
from FreeCAD import Console as FCC
import Asm4_libs as Asm4
"""
+-----------------------------------------------+
| a class to create all Datum objects |
+-----------------------------------------------+
"""
class newDatum:
"My tool object"
def __init__(self, datumName):
self.datumName = datumName
# recognised containers (not the same as Asm4.containerTypes !)
self.containers = [ 'App::Part', 'PartDesign::Body', 'App::DocumentObjectGroup']
if self.datumName == 'Point':
self.datumType = 'PartDesign::Point'
self.menutext = "New Point"
self.tooltip = "Create a new Datum Point in a Part"
self.icon = os.path.join( Asm4.iconPath , 'Asm4_Point.svg')
self.datumColor = (0.00,0.00,0.00)
self.datumAlpha = []
elif self.datumName == 'Axis':
self.datumType = 'PartDesign::Line'
self.menutext = "New Axis"
self.tooltip = "Create a new Datum Axis in a Part"
self.icon = os.path.join( Asm4.iconPath , 'Asm4_Axis.svg')
self.datumColor = (0.00,0.00,0.50)
self.datumAlpha = []
elif self.datumName == 'Plane':
self.datumType = 'PartDesign::Plane'
self.menutext = "New Plane"
self.tooltip = "Create a new Datum Plane in a Part"
self.icon = os.path.join( Asm4.iconPath , 'Asm4_Plane.svg')
self.datumColor = (0.50,0.50,0.50)
self.datumAlpha = 80
elif self.datumName == 'LCS':
self.datumType = 'PartDesign::CoordinateSystem'
self.menutext = "New Coordinate System"
self.tooltip = "Create a new Coordinate System in a Part"
self.icon = os.path.join( Asm4.iconPath , 'Asm4_CoordinateSystem.svg')
self.datumColor = []
self.datumAlpha = []
elif self.datumName == 'Sketch':
self.datumType = 'Sketcher::SketchObject'
self.menutext = "New Sketch"
self.tooltip = "Create a new Sketch in a Part"
self.icon = os.path.join( Asm4.iconPath , 'Asm4_Sketch.svg')
self.datumColor = []
self.datumAlpha = []
def GetResources(self):
return {"MenuText": self.menutext,
"ToolTip": self.tooltip,
"Pixmap" : self.icon }
def IsActive(self):
if App.ActiveDocument:
# is something correct selected ?
if self.checkSelection():
return(True)
return(False)
def checkSelection(self):
# if something is selected ...
if Gui.Selection.getSelection():
selectedObj = Gui.Selection.getSelection()[0]
# ... and it's an App::Part or an datum object
selType = selectedObj.TypeId
if selType in self.containers or selType in Asm4.datumTypes or selType=='Sketcher::SketchObject':
return(selectedObj)
# or of nothing is selected ...
elif Asm4.getAssembly():
# ... but there is as assembly:
return Asm4.getAssembly()
# if we're here it's because we didn't find a good reason to not be here
return None
"""
+-----------------------------------------------+
| the real stuff |
+-----------------------------------------------+
"""
def Activated(self):
# check that we have somewhere to put our stuff
selectedObj = self.checkSelection()
# default name increments the datum type's end numeral
proposedName = Asm4.nextInstance( self.datumName, startAtOne=True )
parentContainer = None
# check whether we have selected a container
if selectedObj.TypeId in self.containers:
parentContainer = selectedObj
# if a datum object is selected
elif selectedObj.TypeId in Asm4.datumTypes or selectedObj.TypeId=='Sketcher::SketchObject':
# see whether it's in a container
parent = selectedObj.getParentGeoFeatureGroup()
if parent.TypeId in self.containers:
parentContainer = parent
# if there is an assembly
elif Asm4.getAssembly():
parentContainer = Asm4.getAssembly()
# something went wrong
else:
Asm4.warningBox("I can't create a "+self.datumType+" with the current selections")
# check whether there is already a similar datum, and increment the instance number
# instanceNum = 1
#while App.ActiveDocument.getObject( self.datumName+'_'+str(instanceNum) ):
# instanceNum += 1
#datumName = self.datumName+'_'+str(instanceNum)
if parentContainer:
# input dialog to ask the user the name of the Sketch:
#proposedName = Asm4.nextInstance( self.datumName + '_' + selectedObj.Label, startAtOne=True )
text,ok = QtGui.QInputDialog.getText(None,'Create new '+self.datumName,
'Enter '+self.datumName+' name :'+' '*40, text = proposedName)
if ok and text:
# App.activeDocument().getObject('Model').newObject( 'Sketcher::SketchObject', text )
createdDatum = App.ActiveDocument.addObject( self.datumType, text )
parentContainer.addObject( createdDatum )
createdDatum.Label = text
# automatic resizing of datum Plane sucks, so we set it to manual
if self.datumType=='PartDesign::Plane':
createdDatum.ResizeMode = 'Manual'
createdDatum.Length = 100
createdDatum.Width = 100
elif self.datumType=='PartDesign::Line':
createdDatum.ResizeMode = 'Manual'
createdDatum.Length = 200
# if color or transparency is specified for this datum type
if self.datumColor:
Gui.ActiveDocument.getObject(createdDatum.Name).ShapeColor = self.datumColor
if self.datumAlpha:
Gui.ActiveDocument.getObject(createdDatum.Name).Transparency = self.datumAlpha
# highlight the created datum object
Gui.Selection.clearSelection()
Gui.Selection.addSelection( App.ActiveDocument.Name, parentContainer.Name, createdDatum.Name+'.' )
Gui.runCommand('Part_EditAttachment')
"""
+-----------------------------------------------+
| a class to create an LCS on a hole |
+-----------------------------------------------+
"""
class newHole:
def GetResources(self):
return {"MenuText": "New Hole Axis",
"ToolTip": "Create a Datum Axis attached to a hole",
"Pixmap" : os.path.join( Asm4.iconPath , 'Asm4_Hole.svg')
}
def IsActive(self):
selection = self.getSelectedEdges()
if selection is None:
return False
else:
return True
"""
+-----------------------------------------------+
| the real stuff |
+-----------------------------------------------+
"""
def getSelectedEdges(self):
# check that we have selected only circular edges
selection = None
parent = None
edges = []
# 1 selection means a single parent
if App.ActiveDocument and len(Gui.Selection.getSelection()) == 1:
parent = Gui.Selection.getSelection()[0]
# parse all sub-elemets of the selection
for i in range(len(Gui.Selection.getSelectionEx()[0].SubObjects)):
edgeObj = Gui.Selection.getSelectionEx()[0].SubObjects[i]
edgeName = Gui.Selection.getSelectionEx()[0].SubElementNames[i]
# if the edge is circular
if Asm4.isCircle(edgeObj):
edges.append( [edgeObj,edgeName] )
# if we found circular edges
if len(edges) > 0:
selection = ( parent, edges )
return selection
def Activated(self):
( selectedObj, edges ) = self.getSelectedEdges()
for i in range(len(edges)):
edgeObj = edges[i][0]
edgeName = edges[i][1]
parentPart = selectedObj.getParentGeoFeatureGroup()
# we can create a datum only in a container
if parentPart:
parentDoc = parentPart.Document
# if the solid having the edge is indeed in an App::Part
if parentPart and (parentPart.TypeId=='App::Part' or parentPart.TypeId=='PartDesign::Body'):
# check whether there is already a similar datum, and increment the instance number
instanceNum = 1
while parentDoc.getObject( 'HoleAxis_'+str(instanceNum) ):
instanceNum += 1
axis = parentPart.newObject('PartDesign::Line','HoleAxis_'+str(instanceNum))
axis.Support = [( selectedObj, (edgeName,) )]
axis.MapMode = 'AxisOfCurvature'
axis.MapReversed = False
axis.ResizeMode = 'Manual'
axis.Length = edgeObj.BoundBox.DiagonalLength
axis.ViewObject.ShapeColor = (0.0,0.0,1.0)
axis.ViewObject.Transparency = 50
axis.recompute()
parentPart.recompute()
#
else:
FCC.PrintMessage('Datum objects can only be created inside Part or Body containers')
"""
+-----------------------------------------------+
| add the commands to the workbench |
+-----------------------------------------------+
"""
Gui.addCommand( 'Asm4_newPoint', newDatum('Point') )
Gui.addCommand( 'Asm4_newAxis', newDatum('Axis') )
Gui.addCommand( 'Asm4_newPlane', newDatum('Plane') )
Gui.addCommand( 'Asm4_newLCS', newDatum('LCS') )
Gui.addCommand( 'Asm4_newSketch',newDatum('Sketch'))
Gui.addCommand( 'Asm4_newHole', newHole() )
# defines the drop-down button for Datum objects
createDatumList = [ 'Asm4_newLCS',
'Asm4_newPlane',
'Asm4_newAxis',
'Asm4_newPoint',
'Asm4_newHole' ]
Gui.addCommand( 'Asm4_createDatum', Asm4.dropDownCmd( createDatumList, 'Create Datum Object'))

View file

@ -0,0 +1,110 @@
from helper.is_solid import is_object_solid
import FreeCAD as App
import Spreadsheet
def createSpreadsheet():
if App.ActiveDocument.getObject("BoM_List") == None:
sheet = App.activeDocument().addObject('Spreadsheet::Sheet', 'BoM_List')
sheet.set('A1', 'п.п.')
sheet.set('B1', 'Наименование детали')
sheet.set('C1', 'Количество')
else:
sheet = App.ActiveDocument.getObject("BoM_List")
App.ActiveDocument.BoM_List.clear('A1:ZZ16384')
sheet.set('A1', 'п.п.')
sheet.set('B1', 'Наименование детали')
sheet.set('C1', 'Количество')
return (sheet)
class SolidBodiesParcer:
_asmThere = []
def __init__(self) -> None:
if (self._asmThere.__len__() == 0):
self.initParse()
pass
def initParse(self):
for el in App.ActiveDocument.RootObjects:
if (is_object_solid(el) and hasattr(el, 'Group')):
self.getSubPartsLink(el.Group, el.Label)
def getSubPartsLink(self, group, label):
groupLink = {label: []}
for el in group:
if (is_object_solid(el)):
groupLink[label].append(
{'label': el.Label, 'isGroup': hasattr(el, 'Group'), 'solid': el})
for el in groupLink[label]:
if ('isGroup' in el):
if (el['isGroup'] == False):
self._asmThere.append(el['solid'].Label)
if (el['isGroup']):
self.getSubPartsLink(el['solid'].Group, el['label']),
return groupLink
def uniquePartsSort(labelParts):
uniquePartsLabels = {}
for el in labelParts:
for k in labelParts:
if (App.ActiveDocument.getObjectsByLabel(str(el))[0].Shape.isPartner(App.ActiveDocument.getObjectsByLabel(str(k))[0].Shape)):
if uniquePartsLabels.get(el) == None:
uniquePartsLabels[el] = k
sortedParts = {}
for k, v in uniquePartsLabels.items():
if sortedParts.get(v) == None:
sortedParts[v] = [k]
else:
sortedParts[v].append(k)
return sortedParts
def countForUniques(sortedParts):
countedParts = {}
for k in sortedParts:
countedParts[k] = len(sortedParts[k])
return countedParts
def fillInBoMList(sheet, countedParts):
a = 1
for label, count in countedParts.items():
a += 1
b = label
c = count
sheet.set('A' + str(a), str(a-1))
sheet.set('B' + str(a), str(b))
sheet.set('C' + str(a), str(c))
total_count = sum(countedParts.values())
sheet.set('B'+str(a+1), 'Итого')
sheet.set('C' + str(a+1), str(total_count))
def run_BoM_list():
createSpreadsheet()
sheet = App.ActiveDocument.getObject("BoM_List")
labelParts = SolidBodiesParcer()._asmThere
sortedParts = uniquePartsSort(labelParts)
countedParts = countForUniques(sortedParts)
fillInBoMList(sheet, countedParts)

View file

@ -0,0 +1,49 @@
import FreeCAD
import FreeCADGui
from PySide import QtGui, QtCore
class DatumTool:
"""
A tool for creating datums in existing models
"""
def __init__(self):
self.active = False
def activate(self):
self.active = True
FreeCAD.Console.PrintMessage("Datum tool activatedn")
def deactivate(self):
self.active = False
FreeCAD.Console.PrintMessage("Datum tool deactivatedn")
def mousePressEvent(self, event):
if self.active:
# Create a datum at the position of the mouse click
pos = FreeCADGui.ActiveDocument.ActiveView.getCursorPos()
point = FreeCADGui.ActiveDocument.ActiveView.getPoint(pos)
datum = FreeCAD.ActiveDocument.addObject("Part::Datum", "Datum")
datum.Placement.Base = point
datum.ViewObject.ShapeColor = (0.0, 1.0, 0.0) # Set the color of the datum to green
FreeCAD.ActiveDocument.recompute()
class DatumCommand:
"""
A command for activating and deactivating the datum tool
"""
def __init__(self):
self.tool = DatumTool()
def Activated(self):
self.tool.activate()
FreeCADGui.ActiveDocument.ActiveView.addEventCallback("SoMouseButtonEvent", self.tool.mousePressEvent)
def Deactivated(self):
self.tool.deactivate()
FreeCADGui.ActiveDocument.ActiveView.removeEventCallback("SoMouseButtonEvent", self.tool.mousePressEvent)
def GetResources(self):
return {'Pixmap': 'path/to/icon.png', 'MenuText': 'Datum Tool', 'ToolTip': 'Creates datum elements in existing models'}
# Add the command to the Draft Workbench
FreeCADGui.addCommand('DatumCommand', DatumCommand())

View file

@ -11,9 +11,11 @@
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
from BoMList import run_BoM_list
import FreeCAD
import Tools
from scenarios.robossembler_freecad_export_scenario import RobossemblerFreeCadExportScenario
from usecases.asm4parser_usecase import Asm4StructureParseUseCase
if FreeCAD.GuiUp:
import FreeCADGui
@ -297,6 +299,13 @@ def makeAllPartFrames():
def spawnFeatureFrameCreator():
ffpanel = FeatureFramePanel()
FreeCADGui.Control.showDialog(ffpanel)
def BoMGeneration(part):
obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython",
"FeatureFrame")
print(obj)
###################################################################
@ -306,6 +315,12 @@ uidir = os.path.join(FreeCAD.getUserAppDataDir(),
"Mod", __workbenchname__, "UI")
icondir = os.path.join(uidir, "icons")
Tools.spawnClassCommand("BoMGeneration",
run_BoM_list,
{"Pixmap": str(os.path.join(icondir, "BoMList.svg")),
"MenuText": "Generate Bill of Materials",
"ToolTip": "Press the button to create big BoM"})
Tools.spawnClassCommand("FrameCommand",
makeFrame,
{"Pixmap": str(os.path.join(icondir, "frame.svg")),

View file

@ -0,0 +1,105 @@
import os
import json
import FreeCAD
import Tools
def export_coordinate_systems():
# Получение активного документа FreeCAD
doc = FreeCAD.ActiveDocument
if not doc:
raise ValueError("Нет активного документа FreeCAD.")
# Получение имени активного документа
doc_name = doc.Name
# Получение пути к папке и имя файла активного документа
folder_path, file_name = os.path.split(doc.FileName)
# Создание папки для экспорта, если она не существует
output_folder = os.path.join(folder_path, 'entities')
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# Создание копии активного документа
# надо сохраняться, а не копироваться
doc.save()
# Получение списка объектов документа
objects = doc.Objects
# Обход объектов для сохранения локальных систем координат
for obj in objects:
if obj.TypeId == 'PartDesign::CoordinateSystem':
partprops = Tools.getLocalPartProps(obj)
output_file_path = os.path.join(output_folder, f"{obj.Label}.json")
with open(output_file_path, "w", encoding="utf8") as propfile:
json.dump(partprops, propfile, indent=1, separators=(',', ': '))
print("Экспорт обьектов завершен.")
export_coordinate_systems()
#работает
# def import_coordinate_systems():
# # Получение активного документа FreeCAD
# doc = FreeCAD.ActiveDocument
# if not doc:
# raise ValueError("Нет активного документа FreeCAD.")
# # Получение имени активного документа
# doc_name = doc.Name
# # Получение пути к папке активного документа
# folder_path, _ = os.path.split(doc.FileName)
# # Получение пути к папке с файлами JSON
# json_folder_path = os.path.join(folder_path, doc_name)
# # Проверка существования папки с файлами JSON
# if not os.path.exists(json_folder_path):
# raise ValueError(f"Папка {json_folder_path} не существует.")
# # Получение списка файлов JSON в папке
# json_files = [f for f in os.listdir(json_folder_path) if f.endswith(".json")]
# # Обход файлов JSON для создания локальных систем координат
# for json_file in json_files:
# json_file_path = os.path.join(json_folder_path, json_file)
# with open(json_file_path, "r") as file:
# json_data = json.load(file)
# # Извлечение информации о локальной системе координат из файла JSON
# name = json_data.get("Name")
# x = json_data.get("X")
# y = json_data.get("Y")
# z = json_data.get("Z")
# # Создание локальной системы координат
# placement = FreeCAD.Placement()
# placement.Base = FreeCAD.Vector(x, y, z)
# part = doc.addObject("Part::Feature", name)
# part.Placement = placement
# # Добавление дополнительных свойств из файла JSON
# for key, value in json_data.items():
# if key not in ["Name", "X", "Y", "Z"]:
# setattr(part, key, value)
# print("Импорт локальных систем координат завершен.")
# #import_coordinate_systems()

View file

@ -36,10 +36,13 @@ class Frames(Workbench):
"""This function is executed when FreeCAD starts"""
import Frames
self.framecommands = [
"BoMGeneration",
"FrameCommand",
"SelectedPartFrameCommand",
"AllPartFramesCommand",
"FeatureFrameCommand"
"FeatureFrameCommand",
]
self.toolcommands = [
"ExportPlacementAndPropertiesCommand",

View file

View file

@ -0,0 +1,60 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Sheet_addition_test.py
#
# Copyright 2015 Ulrich Brammer <ulrich@Pauline>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
#
theDoc = App.newDocument("SheetTest")
App.setActiveDocument("SheetTest")
#App.activeDocument("SheetTest")
p=App.ParamGet("User parameter:BaseApp/Preferences/General")
thePath = p.GetString('FileOpenSavePath')
mySheet = theDoc.addObject('Spreadsheet::Sheet','Spreadsheet')
mySheet.set('A1', '1')
mySheet.set('A2', '2')
theDoc.recompute()
mySheet.set('A3', '=A1+A2')
mySheet.setPosition('A4')
#theDoc.saveAs("/home/ulrich/FreeCAD/Spreadsheet/Sheet_4.fcstd")
theDoc.saveAs(thePath + '/Sheet_5.fcstd')
mySheet.set('A4', '=A3')
theDoc.recompute()
if mySheet.State == ['Invalid']:
print "Invalid Spreadsheet"
else:
print "No error found"
def main():
return 0
if __name__ == '__main__':
main()

View file

@ -184,11 +184,23 @@ def getLocalPartProps(obj):
"label": obj.Label,
"parent_label": obj_parent_label,
"placement": placement2pose(old_placement),
# "grip properties": { "grip open": obj.GripOpen, "grip depth": obj.GripDepth, "grip width": obj.GripWidth}
# "boundingbox": boundingBox2list(obj.Shape.BoundBox),
# "volume": obj.Shape.Volume*1e-9,
# "centerofmass": vector2list(obj.Shape.CenterOfMass),
# "principalproperties": principalProperties2dict(obj.Shape.PrincipalProperties)
}
if obj.Type == 'grip':
partprops["grip properties"] = { "grip open": obj.GripOpen, "grip depth": obj.GripDepth, "grip width": obj.GripWidth}
elif obj.Type == 'area':
partprops["area dim"] = { } #свойства, описывающие размер
elif obj.Type == 'vol':
partprops["vol dim"] = { } #свойства, описывающие размер области
# obj.Placement = old_placement
return partprops

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="32px" height="32px" id="svg3085" version="1.1" inkscape:version="0.92.4 (5da689c313, 2019-01-14)" sodipodi:docname="a2p_PartsList.svg">
<defs id="defs3087">
<inkscape:path-effect effect="skeletal" id="path-effect3066" is_visible="true" pattern="M 0,0 0,10 10,5 z" copytype="single_stretched" prop_scale="1" scale_y_rel="false" spacing="0" normal_offset="0" tang_offset="0" prop_units="false" vertical_pattern="false" fuse_tolerance="0"/>
<linearGradient inkscape:collect="always" id="linearGradient7597">
<stop style="stop-color:#000003;stop-opacity:1;" offset="0" id="stop7599"/>
<stop style="stop-color:#000003;stop-opacity:0;" offset="1" id="stop7601"/>
</linearGradient>
<marker inkscape:stockid="Arrow1Lstart" orient="auto" refY="0.0" refX="0.0" id="Arrow1Lstart" style="overflow:visible">
<path id="path3995" d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt" transform="scale(0.8) translate(12.5,0)"/>
</marker>
<marker inkscape:stockid="Arrow1Send" orient="auto" refY="0.0" refX="0.0" id="Arrow1Send" style="overflow:visible;">
<path id="path4010" d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;" transform="scale(0.2) rotate(180) translate(6,0)"/>
</marker>
<marker inkscape:stockid="Arrow1Sstart" orient="auto" refY="0.0" refX="0.0" id="Arrow1Sstart" style="overflow:visible">
<path id="path4007" d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt" transform="scale(0.2) translate(6,0)"/>
</marker>
<marker inkscape:stockid="Arrow2Sstart" orient="auto" refY="0.0" refX="0.0" id="Arrow2Sstart" style="overflow:visible">
<path id="path4025" style="fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round" d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z " transform="scale(0.3) translate(-2.3,0)"/>
</marker>
<marker inkscape:stockid="TriangleOutL" orient="auto" refY="0.0" refX="0.0" id="TriangleOutL" style="overflow:visible">
<path id="path4137" d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z " style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt" transform="scale(0.8)"/>
</marker>
<marker inkscape:stockid="DiamondLend" orient="auto" refY="0.0" refX="0.0" id="DiamondLend" style="overflow:visible">
<path id="path4092" d="M 0,-7.0710768 L -7.0710894,0 L 0,7.0710589 L 7.0710462,0 L 0,-7.0710768 z " style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt" transform="scale(0.8) translate(-7,0)"/>
</marker>
<marker inkscape:stockid="DiamondLstart" orient="auto" refY="0.0" refX="0.0" id="DiamondLstart" style="overflow:visible">
<path id="path4083" d="M 0,-7.0710768 L -7.0710894,0 L 0,7.0710589 L 7.0710462,0 L 0,-7.0710768 z " style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt" transform="scale(0.8) translate(7,0)"/>
</marker>
<marker inkscape:stockid="Arrow1Lend" orient="auto" refY="0.0" refX="0.0" id="Arrow1Lend" style="overflow:visible;">
<path id="path3998" d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;" transform="scale(0.8) rotate(180) translate(12.5,0)"/>
</marker>
<linearGradient id="linearGradient3893">
<stop style="stop-color:#ffffff;stop-opacity:1;" offset="0" id="stop3895"/>
<stop style="stop-color:#ffffff;stop-opacity:0;" offset="1" id="stop3897"/>
</linearGradient>
<linearGradient inkscape:collect="always" xlink:href="#linearGradient7597" id="linearGradient7603" x1="15.71482" y1="22.299378" x2="34.136209" y2="22.299378" gradientUnits="userSpaceOnUse"/>
</defs>
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="16" inkscape:cx="17.629635" inkscape:cy="8.4343604" inkscape:current-layer="layer1" showgrid="true" inkscape:grid-bbox="true" inkscape:document-units="px" inkscape:window-width="1920" inkscape:window-height="1018" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1"/>
<metadata id="metadata3090">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g id="layer1" inkscape:label="Layer 1" inkscape:groupmode="layer">
<rect style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.11415751000000000;stroke-linecap:butt;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" id="rect3018" width="1.2970899" height="32.02499" x="0.058561768" y="0.011311022"/>
<rect style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.11415751000000000;stroke-linecap:butt;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" id="rect3018-5" width="1.2970899" height="32.02499" x="30.72147" y="0.021291591"/>
<rect style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.11415751;stroke-linecap:butt;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" id="rect3018-3" width="1.2970899" height="32.02499" x="-0.026408914" y="-32.054981" transform="matrix(0,1,-1,0,0,0)"/>
<rect style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.11415751;stroke-linecap:butt;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" id="rect3018-3-6" width="1.2970899" height="32.02499" x="11.354873" y="-31.978708" transform="matrix(0,1,-1,0,0,0)"/>
<rect style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.11415751;stroke-linecap:butt;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" id="rect3018-3-6-9" width="1.2970899" height="32.02499" x="22.061981" y="-31.934517" transform="matrix(0,1,-1,0,0,0)"/>
<rect style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.11415751;stroke-linecap:butt;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" id="rect3018-3-6-9-2" width="1.2970899" height="32.02499" x="30.636494" y="-31.934515" transform="matrix(0,1,-1,0,0,0)"/>
<g id="g4617">
<path sodipodi:nodetypes="ccccccccc" inkscape:connector-curvature="0" id="path3369" d="M 1.4229313,27.000257 V 23.442626 H 16.029106 30.63528 v 3.557631 3.557631 H 16.029106 1.4229313 Z" style="fill:#ffffff;fill-opacity:1"/>
<text id="text3100" y="27.713165" x="11.433594" style="font-style:normal;font-weight:bold;line-height:0%;font-family:sans-serif;-inkscape-font-specification:'Sans Bold';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" xml:space="preserve"><tspan style="font-size:8px;line-height:1.25;font-family:sans-serif" y="27.713165" x="11.433594" id="tspan3102" sodipodi:role="line">...</tspan></text>
</g>
<path style="fill:#ffffff;fill-opacity:1" d="m 6.2842904,5.3724349 0,-0.8007207 0.5237353,0 c 0.5882447,0 0.7620882,0.041783 0.9637654,0.2316391 0.2499922,0.2353392 0.2907934,0.7148274 0.086406,1.0154223 C 7.6895473,6.0668098 7.4826438,6.1358185 6.848381,6.1555803 l -0.5640906,0.017575 0,-0.8007208 z" id="path3375" inkscape:connector-curvature="0"/>
<path style="fill:#ffffff;fill-opacity:1" d="M 11.823275,8.4123074 C 11.48911,8.3155617 11.339256,7.8685764 11.550817,7.5996196 11.705969,7.4023768 11.881121,7.3559471 12.470055,7.3559471 l 0.53175,0 -2.48e-4,0.1436311 c -7.16e-4,0.4175365 -0.294436,0.8141335 -0.673669,0.9096255 -0.234672,0.059091 -0.309638,0.059552 -0.504614,0.0031 l 1e-6,0 z" id="path3377" inkscape:connector-curvature="0"/>
<g id="g3754">
<path inkscape:connector-curvature="0" id="path3373" d="M 1.4229313,6.317384 V 1.345539 H 16.029106 30.63528 v 4.971845 4.971845 H 16.029106 1.4229313 Z" style="fill:#ffffff;fill-opacity:1" sodipodi:nodetypes="ccccccccc"/>
<text id="text3013" y="9.2841949" x="4.0273438" style="font-style:normal;font-weight:bold;line-height:0%;font-family:sans-serif;-inkscape-font-specification:'Sans Bold';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" xml:space="preserve"><tspan style="font-size:8px;line-height:1.25;font-family:sans-serif" y="9.2841949" x="4.0273438" id="tspan3015" sodipodi:role="line">Parts</tspan></text>
</g>
<g id="g4612">
<path sodipodi:nodetypes="ccccccccc" inkscape:connector-curvature="0" id="path3371" d="M 1.4229313,17.365927 V 12.747636 H 16.029106 30.63528 v 4.618291 4.618292 H 16.029106 1.4229313 Z" style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"/>
<text id="text3017" y="20.221827" x="7.5078125" style="font-style:normal;font-weight:bold;line-height:0%;font-family:sans-serif;-inkscape-font-specification:'Sans Bold';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" xml:space="preserve"><tspan style="font-size:8px;line-height:1.25;font-family:sans-serif" y="20.221827" x="7.5078125" id="tspan3019" sodipodi:role="line">List</tspan></text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -0,0 +1,240 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64px"
height="64px"
id="svg5821"
sodipodi:version="0.32"
inkscape:version="1.0 (1.0+r73+1)"
sodipodi:docname="Asm4_Datum.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.1"
inkscape:export-filename="/home/normand/Images/Icônes/PartDesign_Std_Plane_nc1.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs5823">
<linearGradient
inkscape:collect="always"
id="linearGradient6349">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop6351" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop6353" />
</linearGradient>
<linearGradient
id="linearGradient3377">
<stop
style="stop-color:#0019a3;stop-opacity:1;"
offset="0"
id="stop3379" />
<stop
style="stop-color:#0069ff;stop-opacity:1;"
offset="1"
id="stop3381" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3377"
id="linearGradient3383"
x1="901.1875"
y1="1190.875"
x2="1267.9062"
y2="1190.875"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-1,0,0,1,2199.356,0)" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 32 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="64 : 32 : 1"
inkscape:persp3d-origin="32 : 21.333333 : 1"
id="perspective5829" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient6349"
id="radialGradient6355"
cx="1103.6399"
cy="1424.4465"
fx="1103.6399"
fy="1424.4465"
r="194.40614"
gradientTransform="matrix(-1.4307499,-1.3605156e-7,-1.202713e-8,0.1264801,2674.7488,1244.2826)"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3776"
id="linearGradient3782"
x1="33.052631"
y1="73.676765"
x2="23.483253"
y2="19.131313"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
id="linearGradient3776">
<stop
style="stop-color:#cc0000;stop-opacity:1"
offset="0"
id="stop3778" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3780" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="8.0773852"
inkscape:cx="50.403398"
inkscape:cy="46.554586"
inkscape:current-layer="g3360-3"
showgrid="true"
inkscape:document-units="px"
inkscape:grid-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1048"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:snap-bbox="false"
inkscape:snap-nodes="true"
inkscape:snap-global="false"
inkscape:document-rotation="0">
<inkscape:grid
type="xygrid"
id="grid2997"
empspacing="2"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata5826">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:creator>
<cc:Agent>
<dc:title>[wmayer]</dc:title>
</cc:Agent>
</dc:creator>
<dc:title>Sketcher_Sketch</dc:title>
<dc:date>2011-10-10</dc:date>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Sketch.svg</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson</dc:title>
</cc:Agent>
</dc:contributor>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<g
id="g3360"
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/draft.png"
inkscape:export-xdpi="3.2478156"
inkscape:export-ydpi="3.2478156"
transform="matrix(0.1367863,0,0,0.1367863,-119.15519,-134.86962)">
<g
id="g3360-3"
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/draft.png"
inkscape:export-xdpi="3.2478156"
inkscape:export-ydpi="3.2478156"
transform="translate(-2.96179e-7,2.9614273e-6)">
<g
transform="matrix(0.86585328,0,0,1.0022331,158.36626,-5.6554462)"
id="g860">
<rect
transform="matrix(0.86410236,0.50331612,-0.8660254,0.5,0,0)"
y="479.03436"
x="1745.9357"
height="266.99686"
width="266.52176"
id="rect3860-36-6"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#c1b7bb;stroke-width:36.8974;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
<rect
ry="2.5860298"
transform="matrix(0.8660254,0.5,-0.8660254,0.5,0,0)"
y="487.53268"
x="1749.7632"
height="265.90598"
width="267.47815"
id="rect3860-3-1"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#999999;fill-opacity:1;stroke:#d6d6d6;stroke-width:15.7117;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
<g
transform="matrix(1,0,0,-1,15.589571,2441.7923)"
id="g4172">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#999999;fill-opacity:0.5;stroke:#403c3d;stroke-width:36.8936;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
id="rect3860-36"
width="266.52176"
height="266.99686"
x="1138.8126"
y="1717.2489"
transform="matrix(0.86793573,-0.49667653,0,1,0,0)" />
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;fill-opacity:1;stroke:#897e7e;stroke-width:15.7117;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
id="rect3860-3"
width="267.47815"
height="265.90598"
x="1141.3009"
y="1723.0597"
transform="matrix(0.8660254,-0.5,0,1,0,0)"
ry="2.5860298" />
</g>
<g
transform="translate(14.62135,61.744554)"
id="g4162">
<rect
style="fill:#b90720;fill-opacity:1;stroke:#172a04;stroke-width:13.677;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4154"
width="289.10248"
height="22.203979"
x="1130.2279"
y="528.82843"
ry="10.96601"
rx="0"
transform="matrix(0.8660254,0.5,0,1,0,0)" />
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

9697
cg/freecad/Frames/axis.gcode Normal file

File diff suppressed because it is too large Load diff

0
cg/freecad/Frames/box.py Normal file
View file

31
cg/freecad/Frames/file.py Normal file
View file

@ -0,0 +1,31 @@
import os
import subprocess
import time
import subprocess
# import FreeCAD
# import Mesh
import gcoder
# gcode_path = '/home/mark-voltov/Winder/axis.gcode'
# gcode.main(gcode)
file_path = '/home/mark-voltov/Winder/axis.gcode'
gcoder_path = '/home/mark-voltov/GitProjects/framework/cg/freecad/Frames/gcoder.py'
with open(file_path, 'r') as file:
gcode = file.readlines()
# print(gcode)
(gcoder.LightGCode(gcode))
cmd = [file_path gcoder_path]
subprocess.run(cmd)
# subprocess.run( '/home/mark-voltov/Winder/axis.gcode', '/home/mark-voltov/GitProjects/framework/cg/freecad/Frames/gcoder.py')

View file

@ -0,0 +1,60 @@
# import FreeCAD as App
# doc = App.ActiveDocument
#генерируем pddl
#импортируем кучу обьектов из дерева построения freecad
#задаем шаблон на действие
predicates = ';; Предикаты \n(:predicates \n(at ?c - component ?location - component) \n(printed ?c - component) \n(has-material ?m - material) \n(compatible ?m - material ?c - component))'
# print(predicates)
predicates =
(" ;; Предикаты \
'(:predicates (at ?c - component ?location - component)' \
'(printed ?c - component)' \
'(printed ?c - component))'
print(predicates)
# (:predicates
# (at ?c - component ?location - component)
# (printed ?c - component)
# (has-material ?m - material)
# (compatible ?m - material ?c - component))''
# ;; Предикаты
# (:predicates
# (at ?c - component ?location - component)
# (printed ?c - component)
# (has-material ?m - material)
# (compatible ?m - material ?c - component))
# ;; Действия
# (:action load-material
# :parameters (?m - material ?c - component)
# :precondition (and (at ?m ?c) (compatible ?m ?c))
# :effect (has-material ?m))
# (:action unload-material
# :parameters (?m - material ?c - component)
# :precondition (has-material ?m)
# :effect (and (not (has-material ?m)) (at ?m ?c)))
# (:action print-component
# :parameters (?c - component)
# :precondition (and (at ?c ?printer) (has-material ?m) (compatible ?m ?c))
# :effect (printed ?c))
# ;; Цель
# (:goal (forall (?c - component) (printed ?c))))

769
cg/freecad/Frames/gcoder.py Normal file
View file

@ -0,0 +1,769 @@
#!/usr/bin/env python3
#
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# # along with Printrun. If not, see <http://www.gnu.org/licenses/>.
# Добавлен файл программы, проводящей анализ файла .gcode и на основе полученных данных вычисляющая материальные и временные затраты на 3д-печать.
# Программа вычисляет габариты задания, длину затрачиваемой нити филамента, длительность маршрута движения головки принтера и длительность печати.
# Эксперименты показывают, что на данный момент есть расхождение между реальным временем и вычисленным с помощью программы,0 в зависимости от файла, на 20-40% (недооценка вычисленной длительности в сравнении с реальной). Остальные результаты выглядят адекватными, но проверить их сложнее.
import sys
import re
import math
import datetime
import logging
from array import array
gcode_parsed_args = ["x", "y", "e", "f", "z", "i", "j"]
gcode_parsed_nonargs = 'gtmnd'
to_parse = "".join(gcode_parsed_args) + gcode_parsed_nonargs
gcode_exp = re.compile("\([^\(\)]*\)|;.*|[/\*].*\n|([%s])\s*([-+]?[0-9]*\.?[0-9]*)" % to_parse)
gcode_strip_comment_exp = re.compile("\([^\(\)]*\)|;.*|[/\*].*\n")
m114_exp = re.compile("\([^\(\)]*\)|[/\*].*\n|([XYZ]):?([-+]?[0-9]*\.?[0-9]*)")
specific_exp = "(?:\([^\(\)]*\))|(?:;.*)|(?:[/\*].*\n)|(%s[-+]?[0-9]*\.?[0-9]*)"
move_gcodes = ["G0", "G1", "G2", "G3"]
class PyLine:
__slots__ = ('x', 'y', 'z', 'e', 'f', 'i', 'j',
'raw', 'command', 'is_move',
'relative', 'relative_e',
'current_x', 'current_y', 'current_z', 'extruding',
'current_tool',
'gcview_end_vertex')
def __init__(self, l):
self.raw = l
def __getattr__(self, name):
return None
class PyLightLine:
__slots__ = ('raw', 'command')
def __init__(self, l):
self.raw = l
def __getattr__(self, name):
return None
try:
from . import gcoder_line
Line = gcoder_line.GLine
LightLine = gcoder_line.GLightLine
except Exception as e:
logging.warning("Memory-efficient GCoder implementation unavailable: %s" % e)
Line = PyLine
LightLine = PyLightLine
def find_specific_code(line, code):
exp = specific_exp % code
bits = [bit for bit in re.findall(exp, line.raw) if bit]
if not bits: return None
else: return float(bits[0][1:])
def S(line):
return find_specific_code(line, "S")
def P(line):
return find_specific_code(line, "P")
def split(line):
split_raw = gcode_exp.findall(line.raw.lower())
if split_raw and split_raw[0][0] == "n":
del split_raw[0]
if not split_raw:
line.command = line.raw
line.is_move = False
logging.warning("raw G-Code line \"%s\" could not be parsed" % line.raw)
return [line.raw]
command = split_raw[0]
line.command = command[0].upper() + command[1]
line.is_move = line.command in move_gcodes
return split_raw
def parse_coordinates(line, split_raw, imperial = False, force = False):
# Not a G-line, we don't want to parse its arguments
if not force and line.command[0] != "G":
return
unit_factor = 25.4 if imperial else 1
for bit in split_raw:
code = bit[0]
if code not in gcode_parsed_nonargs and bit[1]:
setattr(line, code, unit_factor * float(bit[1]))
class Layer(list):
__slots__ = ("duration", "z")
def __init__(self, lines, z = None):
super(Layer, self).__init__(lines)
self.z = z
self.duration = 0
class GCode:
line_class = Line
lines = None
layers = None
all_layers = None
layer_idxs = None
line_idxs = None
append_layer = None
append_layer_id = None
imperial = False
cutting = False
relative = False
relative_e = False
current_tool = 0
# Home position: current absolute position counted from machine origin
home_x = 0
home_y = 0
home_z = 0
# Current position: current absolute position counted from machine origin
current_x = 0
current_y = 0
current_z = 0
# For E this is the absolute position from machine start
current_e = 0
current_e_multi=[0]
total_e = 0
total_e_multi=[0]
max_e = 0
max_e_multi=[0]
# Current feedrate
current_f = 0
# Offset: current offset between the machine origin and the machine current
# absolute coordinate system (as shifted by G92s)
offset_x = 0
offset_y = 0
offset_z = 0
offset_e = 0
offset_e_multi = [0]
# Expected behavior:
# - G28 X => X axis is homed, offset_x <- 0, current_x <- home_x
# - G92 Xk => X axis does not move, so current_x does not change
# and offset_x <- current_x - k,
# - absolute G1 Xk => X axis moves, current_x <- offset_x + k
# How to get...
# current abs X from machine origin: current_x
# current abs X in machine current coordinate system: current_x - offset_x
filament_length = None
filament_length_multi=[0]
duration = None
xmin = None
xmax = None
ymin = None
ymax = None
zmin = None
zmax = None
width = None
depth = None
height = None
est_layer_height = None
# abs_x is the current absolute X in machine current coordinate system
# (after the various G92 transformations) and can be used to store the
# absolute position of the head at a given time
def _get_abs_x(self):
return self.current_x - self.offset_x
abs_x = property(_get_abs_x)
def _get_abs_y(self):
return self.current_y - self.offset_y
abs_y = property(_get_abs_y)
def _get_abs_z(self):
return self.current_z - self.offset_z
abs_z = property(_get_abs_z)
def _get_abs_e(self):
return self.current_e - self.offset_e
abs_e = property(_get_abs_e)
def _get_abs_e_multi(self,i):
return self.current_e_multi[i] - self.offset_e_multi[i]
abs_e = property(_get_abs_e)
def _get_abs_pos(self):
return (self.abs_x, self.abs_y, self.abs_z)
abs_pos = property(_get_abs_pos)
def _get_current_pos(self):
return (self.current_x, self.current_y, self.current_z)
current_pos = property(_get_current_pos)
def _get_home_pos(self):
return (self.home_x, self.home_y, self.home_z)
def _set_home_pos(self, home_pos):
if home_pos:
self.home_x, self.home_y, self.home_z = home_pos
home_pos = property(_get_home_pos, _set_home_pos)
def _get_layers_count(self):
return len(self.all_zs)
layers_count = property(_get_layers_count)
def __init__(self, data = None, home_pos = None,
layer_callback = None, deferred = False,
cutting_as_extrusion = False):
self.cutting_as_extrusion = cutting_as_extrusion
if not deferred:
self.prepare(data, home_pos, layer_callback)
def prepare(self, data = None, home_pos = None, layer_callback = None):
self.home_pos = home_pos
if data:
line_class = self.line_class
self.lines = [line_class(l2) for l2 in
(l.strip() for l in data)
if l2]
self._preprocess(build_layers = True,
layer_callback = layer_callback)
else:
self.lines = []
self.append_layer_id = 0
self.append_layer = Layer([])
self.all_layers = [self.append_layer]
self.all_zs = set()
self.layers = {}
self.layer_idxs = array('I', [])
self.line_idxs = array('I', [])
def has_index(self, i):
return i < len(self)
def __len__(self):
return len(self.line_idxs)
def __iter__(self):
return self.lines.__iter__()
def prepend_to_layer(self, commands, layer_idx):
# Prepend commands in reverse order
commands = [c.strip() for c in commands[::-1] if c.strip()]
layer = self.all_layers[layer_idx]
# Find start index to append lines
# and end index to append new indices
start_index = self.layer_idxs.index(layer_idx)
for i in range(start_index, len(self.layer_idxs)):
if self.layer_idxs[i] != layer_idx:
end_index = i
break
else:
end_index = i + 1
end_line = self.line_idxs[end_index - 1]
for i, command in enumerate(commands):
gline = Line(command)
# Split to get command
split(gline)
# Force is_move to False
gline.is_move = False
# Insert gline at beginning of layer
layer.insert(0, gline)
# Insert gline at beginning of list
self.lines.insert(start_index, gline)
# Update indices arrays & global gcodes list
self.layer_idxs.insert(end_index + i, layer_idx)
self.line_idxs.insert(end_index + i, end_line + i + 1)
return commands[::-1]
def rewrite_layer(self, commands, layer_idx):
# Prepend commands in reverse order
commands = [c.strip() for c in commands[::-1] if c.strip()]
layer = self.all_layers[layer_idx]
# Find start index to append lines
# and end index to append new indices
start_index = self.layer_idxs.index(layer_idx)
for i in range(start_index, len(self.layer_idxs)):
if self.layer_idxs[i] != layer_idx:
end_index = i
break
else:
end_index = i + 1
self.layer_idxs = self.layer_idxs[:start_index] + array('I', len(commands) * [layer_idx]) + self.layer_idxs[end_index:]
self.line_idxs = self.line_idxs[:start_index] + array('I', range(len(commands))) + self.line_idxs[end_index:]
del self.lines[start_index:end_index]
del layer[:]
for i, command in enumerate(commands):
gline = Line(command)
# Split to get command
split(gline)
# Force is_move to False
gline.is_move = False
# Insert gline at beginning of layer
layer.insert(0, gline)
# Insert gline at beginning of list
self.lines.insert(start_index, gline)
return commands[::-1]
def append(self, command, store = True):
command = command.strip()
if not command:
return
gline = Line(command)
self._preprocess([gline])
if store:
self.lines.append(gline)
self.append_layer.append(gline)
self.layer_idxs.append(self.append_layer_id)
self.line_idxs.append(len(self.append_layer)-1)
return gline
def _preprocess(self, lines = None, build_layers = False,
layer_callback = None):
"""Checks for imperial/relativeness settings and tool changes"""
if not lines:
lines = self.lines
imperial = self.imperial
relative = self.relative
relative_e = self.relative_e
current_tool = self.current_tool
current_x = self.current_x
current_y = self.current_y
current_z = self.current_z
offset_x = self.offset_x
offset_y = self.offset_y
offset_z = self.offset_z
# Extrusion computation
current_e = self.current_e
offset_e = self.offset_e
total_e = self.total_e
max_e = self.max_e
cutting = self.cutting
current_e_multi = self.current_e_multi[current_tool]
offset_e_multi = self.offset_e_multi[current_tool]
total_e_multi = self.total_e_multi[current_tool]
max_e_multi = self.max_e_multi[current_tool]
# Store this one out of the build_layers scope for efficiency
cur_layer_has_extrusion = False
# Initialize layers and other global computations
if build_layers:
# Bounding box computation
xmin = float("inf")
ymin = float("inf")
zmin = 0
xmax = float("-inf")
ymax = float("-inf")
zmax = float("-inf")
# Also compute extrusion-only values
xmin_e = float("inf")
ymin_e = float("inf")
xmax_e = float("-inf")
ymax_e = float("-inf")
# Duration estimation
# TODO:
# get device caps from firmware: max speed, acceleration/axis
# (including extruder)
# calculate the maximum move duration accounting for above ;)
lastx = lasty = lastz = None
laste = lastf = 0
lastdx = 0
lastdy = 0
x = y = e = f = 0.0
currenttravel = 0.0
moveduration = 0.0
totalduration = 0.0
acceleration = 2000.0 # mm/s^2
layerbeginduration = 0.0
# Initialize layers
all_layers = self.all_layers = []
all_zs = self.all_zs = set()
layer_idxs = self.layer_idxs = []
line_idxs = self.line_idxs = []
last_layer_z = None
prev_z = None
cur_z = None
cur_lines = []
def append_lines(lines, isEnd):
if not build_layers:
return
nonlocal layerbeginduration, last_layer_z
if cur_layer_has_extrusion and prev_z != last_layer_z \
or not all_layers:
layer = Layer([], prev_z)
last_layer_z = prev_z
finished_layer = len(all_layers)-1 if all_layers else None
all_layers.append(layer)
all_zs.add(prev_z)
else:
layer = all_layers[-1]
finished_layer = None
layer_id = len(all_layers)-1
layer_line = len(layer)
for i, ln in enumerate(lines):
layer.append(ln)
layer_idxs.append(layer_id)
line_idxs.append(layer_line+i)
layer.duration += totalduration - layerbeginduration
layerbeginduration = totalduration
if layer_callback:
# we finish a layer when inserting the next
if finished_layer is not None:
layer_callback(self, finished_layer)
# notify about end layer, there will not be next
if isEnd:
layer_callback(self, layer_id)
if self.line_class != Line:
get_line = lambda l: Line(l.raw)
else:
get_line = lambda l: l
for true_line in lines:
# # Parse line
# Use a heavy copy of the light line to preprocess
line = get_line(true_line)
split_raw = split(line)
if line.command:
# Update properties
if line.is_move:
line.relative = relative
line.relative_e = relative_e
line.current_tool = current_tool
elif line.command == "G20":
imperial = True
elif line.command == "G21":
imperial = False
elif line.command == "G90":
relative = False
relative_e = False
elif line.command == "G91":
relative = True
relative_e = True
elif line.command == "M82":
relative_e = False
elif line.command == "M83":
relative_e = True
elif line.command[0] == "T":
try:
current_tool = int(line.command[1:])
except:
pass #handle T? by treating it as no tool change
while current_tool+1 > len(self.current_e_multi):
self.current_e_multi+=[0]
self.offset_e_multi+=[0]
self.total_e_multi+=[0]
self.max_e_multi+=[0]
elif line.command == "M3" or line.command == "M4":
cutting = True
elif line.command == "M5":
cutting = False
current_e_multi = self.current_e_multi[current_tool]
offset_e_multi = self.offset_e_multi[current_tool]
total_e_multi = self.total_e_multi[current_tool]
max_e_multi = self.max_e_multi[current_tool]
if line.command[0] == "G":
parse_coordinates(line, split_raw, imperial)
# Compute current position
if line.is_move:
x = line.x
y = line.y
z = line.z
if line.f is not None:
self.current_f = line.f
if line.relative:
x = current_x + (x or 0)
y = current_y + (y or 0)
z = current_z + (z or 0)
else:
if x is not None: x = x + offset_x
if y is not None: y = y + offset_y
if z is not None: z = z + offset_z
if x is not None: current_x = x
if y is not None: current_y = y
if z is not None: current_z = z
elif line.command == "G28":
home_all = not any([line.x, line.y, line.z])
if home_all or line.x is not None:
offset_x = 0
current_x = self.home_x
if home_all or line.y is not None:
offset_y = 0
current_y = self.home_y
if home_all or line.z is not None:
offset_z = 0
current_z = self.home_z
elif line.command == "G92":
if line.x is not None: offset_x = current_x - line.x
if line.y is not None: offset_y = current_y - line.y
if line.z is not None: offset_z = current_z - line.z
line.current_x = current_x
line.current_y = current_y
line.current_z = current_z
# # Process extrusion
if line.e is not None:
if line.is_move:
if line.relative_e:
line.extruding = line.e > 0
total_e += line.e
current_e += line.e
total_e_multi += line.e
current_e_multi += line.e
else:
new_e = line.e + offset_e
line.extruding = new_e > current_e
total_e += new_e - current_e
current_e = new_e
new_e_multi = line.e + offset_e_multi
total_e_multi += new_e_multi - current_e_multi
current_e_multi = new_e_multi
max_e = max(max_e, total_e)
max_e_multi=max(max_e_multi, total_e_multi)
cur_layer_has_extrusion |= line.extruding and (line.x is not None or line.y is not None)
elif line.command == "G92":
offset_e = current_e - line.e
offset_e_multi = current_e_multi - line.e
if cutting and self.cutting_as_extrusion:
line.extruding = True
self.current_e_multi[current_tool]=current_e_multi
self.offset_e_multi[current_tool]=offset_e_multi
self.max_e_multi[current_tool]=max_e_multi
self.total_e_multi[current_tool]=total_e_multi
# # Create layers and perform global computations
if build_layers:
# Update bounding box
if line.is_move:
if line.extruding:
if line.current_x is not None:
# G0 X10 ; G1 X20 E5 results in 10..20 even as G0 is not extruding
xmin_e = min(xmin_e, line.current_x, xmin_e if lastx is None else lastx)
xmax_e = max(xmax_e, line.current_x, xmax_e if lastx is None else lastx)
if line.current_y is not None:
ymin_e = min(ymin_e, line.current_y, ymin_e if lasty is None else lasty)
ymax_e = max(ymax_e, line.current_y, ymax_e if lasty is None else lasty)
if max_e <= 0:
if line.current_x is not None:
xmin = min(xmin, line.current_x)
xmax = max(xmax, line.current_x)
if line.current_y is not None:
ymin = min(ymin, line.current_y)
ymax = max(ymax, line.current_y)
# Compute duration
if line.command == "G0" or line.command == "G1":
x = line.x if line.x is not None else (lastx or 0)
y = line.y if line.y is not None else (lasty or 0)
z = line.z if line.z is not None else (lastz or 0)
e = line.e if line.e is not None else laste
# mm/s vs mm/m => divide by 60
f = line.f / 60.0 if line.f is not None else lastf
# given last feedrate and current feedrate calculate the
# distance needed to achieve current feedrate.
# if travel is longer than req'd distance, then subtract
# distance to achieve full speed, and add the time it took
# to get there.
# then calculate the time taken to complete the remaining
# distance
# FIXME: this code has been proven to be super wrong when 2
# subsquent moves are in opposite directions, as requested
# speed is constant but printer has to fully decellerate
# and reaccelerate
# The following code tries to fix it by forcing a full
# reacceleration if this move is in the opposite direction
# of the previous one
dx = x - (lastx or 0)
dy = y - (lasty or 0)
if dx * lastdx + dy * lastdy <= 0:
lastf = 0
currenttravel = math.hypot(dx, dy)
if currenttravel == 0:
if line.z is not None:
currenttravel = abs(line.z) if line.relative else abs(line.z - (lastz or 0))
elif line.e is not None:
currenttravel = abs(line.e) if line.relative_e else abs(line.e - laste)
# Feedrate hasn't changed, no acceleration/decceleration planned
if f == lastf:
moveduration = currenttravel / f if f != 0 else 0.
else:
# FIXME: review this better
# this looks wrong : there's little chance that the feedrate we'll decelerate to is the previous feedrate
# shouldn't we instead look at three consecutive moves ?
distance = 2 * abs(((lastf + f) * (f - lastf) * 0.5) / acceleration) # multiply by 2 because we have to accelerate and decelerate
if distance <= currenttravel and lastf + f != 0 and f != 0:
moveduration = 2 * distance / (lastf + f) # This is distance / mean(lastf, f)
moveduration += (currenttravel - distance) / f
else:
moveduration = 2 * currenttravel / (lastf + f) # This is currenttravel / mean(lastf, f)
# FIXME: probably a little bit optimistic, but probably a much better estimate than the previous one:
# moveduration = math.sqrt(2 * distance / acceleration) # probably buggy : not taking actual travel into account
lastdx = dx
lastdy = dy
totalduration += moveduration
lastx = x
lasty = y
lastz = z
laste = e
lastf = f
elif line.command == "G4":
moveduration = P(line)
if moveduration:
moveduration /= 1000.0
totalduration += moveduration
# FIXME : looks like this needs to be tested with "lift Z on move"
if line.z is not None:
if line.command == "G92":
cur_z = line.z
elif line.is_move:
if line.relative and cur_z is not None:
cur_z += line.z
else:
cur_z = line.z
if cur_z != prev_z and cur_layer_has_extrusion:
append_lines(cur_lines, False)
cur_lines = []
cur_layer_has_extrusion = False
if build_layers:
cur_lines.append(true_line)
prev_z = cur_z
# ## Loop done
# Store current status
self.imperial = imperial
self.relative = relative
self.relative_e = relative_e
self.current_tool = current_tool
self.current_x = current_x
self.current_y = current_y
self.current_z = current_z
self.offset_x = offset_x
self.offset_y = offset_y
self.offset_z = offset_z
self.current_e = current_e
self.offset_e = offset_e
self.max_e = max_e
self.total_e = total_e
self.current_e_multi[current_tool]=current_e_multi
self.offset_e_multi[current_tool]=offset_e_multi
self.max_e_multi[current_tool]=max_e_multi
self.total_e_multi[current_tool]=total_e_multi
self.cutting = cutting
# Finalize layers
if build_layers:
if cur_lines:
append_lines(cur_lines, True)
self.append_layer_id = len(all_layers)
self.append_layer = Layer([])
self.append_layer.duration = 0
all_layers.append(self.append_layer)
self.layer_idxs = array('I', layer_idxs)
self.line_idxs = array('I', line_idxs)
# Compute bounding box
all_zs = self.all_zs.union({zmin}).difference({None})
zmin = min(all_zs)
zmax = max(all_zs)
self.filament_length = self.max_e
while len(self.filament_length_multi)<len(self.max_e_multi):
self.filament_length_multi+=[0]
for i in enumerate(self.max_e_multi):
self.filament_length_multi[i[0]]=i[1]
if self.filament_length > 0:
self.xmin = xmin_e if not math.isinf(xmin_e) else 0
self.xmax = xmax_e if not math.isinf(xmax_e) else 0
self.ymin = ymin_e if not math.isinf(ymin_e) else 0
self.ymax = ymax_e if not math.isinf(ymax_e) else 0
else:
self.xmin = xmin if not math.isinf(xmin) else 0
self.xmax = xmax if not math.isinf(xmax) else 0
self.ymin = ymin if not math.isinf(ymin) else 0
self.ymax = ymax if not math.isinf(ymax) else 0
self.zmin = zmin if not math.isinf(zmin) else 0
self.zmax = zmax if not math.isinf(zmax) else 0
self.width = self.xmax - self.xmin
self.depth = self.ymax - self.ymin
self.height = self.zmax - self.zmin
# Finalize duration
totaltime = datetime.timedelta(seconds = int(totalduration))
self.duration = totaltime
def idxs(self, i):
return self.layer_idxs[i], self.line_idxs[i]
def estimate_duration(self):
return self.layers_count, self.duration
class LightGCode(GCode):
line_class = LightLine
def main():
if len(sys.argv) < 2:
print("usage: %s filename.gcode" % sys.argv[0])
return
print("Line object size:", sys.getsizeof(Line("G0 X0")))
print("Light line object size:", sys.getsizeof(LightLine("G0 X0")))
gcode = GCode(open(sys.argv[1], "rU"))
print("Dimensions:")
xdims = (gcode.xmin, gcode.xmax, gcode.width)
print("\tX: %0.02f - %0.02f (%0.02f)" % xdims)
ydims = (gcode.ymin, gcode.ymax, gcode.depth)
print("\tY: %0.02f - %0.02f (%0.02f)" % ydims)
zdims = (gcode.zmin, gcode.zmax, gcode.height)
print("\tZ: %0.02f - %0.02f (%0.02f)" % zdims)
print("Filament used: %0.02fmm" % gcode.filament_length)
for i in enumerate(gcode.filament_length_multi):
print("E%d %0.02fmm" % (i[0],i[1]))
print("Number of layers: %d" % gcode.layers_count)
print("Estimated duration: %s" % gcode.estimate_duration()[1])
if __name__ == '__main__':
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View file

@ -0,0 +1,131 @@
import FreeCAD as App
import FreeCADGui as Gui
doc = App.ActiveDocument
print('lcs - локальная система координат')
print('grip - позиция захвата')
print('area - плоскость')
print('vol - зона')
print('joint - соединение')
entityType = input('Введите тип получаемого обьекта')
def create(entityType):
if entityType == 'grip':
print('задайте начальную точку захватной зоны. ')
print('Учтите, что ось X должна быть направлена вдоль направления раскрытия пальцев')
print('Ось Z должна быть направлена в противоположную сторону от направления кончика пальца захвата')
objname = input('Введите название начальной точки' + "\n")
obj = doc.getObject(objname)
poseGenerator(obj)
elif entityType == 'area':
print('задайте плоскость')
objname = input('Введите название плоскости'+ "\n")
obj = doc.getObject(objname)
areaProps(obj)
elif entityType == 'vol':
print('задайте обьем')
objname = input('Введите название обьема'+ "\n")
obj = doc.getObject(objname)
volProps(obj)
elif entityType == 'joint':
print('Задайте позицию соединения')
objname = input('Введите название соединения'+ "\n")
part1 = input('укажите название первой детали'+ "\n")
part2 = input('укажите название второй детали'+ "\n")
jointGenerator(objname, part1, part2)
obj.addProperty("App::PropertyString", "Type").Type = entityType
## заглушки функций на случай, если что-то придумаю полезное для них
def areaProps(area):
#здесь нужно отметить свойства зоны
#в принципе, Placement и размеры тут есть, больше ничего особо не нужно
#добавить характеристику entityType
print(area.Label)
def volProps(vol):
#желательно указать координаты, габариты, позицию привязки
#но думаю, что это все уже есть
print(vol.Label)
def jointGenerator(jointName, part1, part2):
#получаем относительные координаты для первой детали и для второй детали
#создаем две сущности - точка входа и точка выхода (??????)
#в работе сейчас
print(jointName.Label)
print(part1.Label)
print(part2.Label)
# Эта функция работает нормально
def poseGenerator(lcs):
box = doc.addObject("Part::Box", "gripSpace")
box.Length = 62 #раскрытие
box.Width = 10 #ширина пальца
box.Height = 40 #глубина
box.Placement = lcs.Placement
#есть смысл создавать привязку прямо здесь же. благодаря параметризации, при подстройке куба все точки сместятся как надо
gripPose = App.ActiveDocument.addObject('PartDesign::CoordinateSystem', 'GripPose')
gripPose.Support = box
gripPose.positionBySupport()
gripPose.MapMode = 'ObjectXY'
gripPose.AttachmentOffset.Base = [str(box.Label) + '.Length' + '/2', str(box.Label) + '.Length'+ '/2', 0] #здесь должна быть активная привязка, не просто значения координат
gripPose.addProperty("App::PropertyFloat", "GripOpen")
gripPose.addProperty("App::PropertyFloat", "GripDepth")
gripPose.addProperty("App::PropertyFloat", "GripWidth")
gripPose.setExpression('GripDepth', str(box.Label) + '.Height')
gripPose.setExpression('GripOpen', str(box.Label) + '.Length')
gripPose.setExpression('GripWidth', str(box.Label) + '.Width')
#нужно создавать внутри Part, а не внутри главного документа. сбиваются привязки !!!
print('Установите захватную зону вручную, растянув обьект GripSpace')
doc.recompute()
#теперь нужно производить экспорт
#он делается через Tools.py или через импорт-экспорт
create(entityType)

View file

@ -0,0 +1,259 @@
#!/usr/bin/env python3
# coding: utf-8
#
# LGPL
# Copyright HUBERT Zoltán
#
# newDatumCmd.py
import os
from PySide import QtGui, QtCore
import FreeCADGui as Gui
import FreeCAD as App
from FreeCAD import Console as FCC
import Asm4_libs as Asm4
"""
+-----------------------------------------------+
| a class to create all Datum objects |
+-----------------------------------------------+
"""
class newDatum:
"My tool object"
def __init__(self, datumName):
self.datumName = datumName
# recognised containers (not the same as Asm4.containerTypes !)
self.containers = [ 'App::Part', 'PartDesign::Body', 'App::DocumentObjectGroup']
if self.datumName == 'Point':
self.datumType = 'PartDesign::Point'
self.menutext = "New Point"
self.tooltip = "Create a new Datum Point in a Part"
self.icon = os.path.join( Asm4.iconPath , 'Asm4_Point.svg')
self.datumColor = (0.00,0.00,0.00)
self.datumAlpha = []
elif self.datumName == 'Axis':
self.datumType = 'PartDesign::Line'
self.menutext = "New Axis"
self.tooltip = "Create a new Datum Axis in a Part"
self.icon = os.path.join( Asm4.iconPath , 'Asm4_Axis.svg')
self.datumColor = (0.00,0.00,0.50)
self.datumAlpha = []
elif self.datumName == 'Plane':
self.datumType = 'PartDesign::Plane'
self.menutext = "New Plane"
self.tooltip = "Create a new Datum Plane in a Part"
self.icon = os.path.join( Asm4.iconPath , 'Asm4_Plane.svg')
self.datumColor = (0.50,0.50,0.50)
self.datumAlpha = 80
elif self.datumName == 'LCS':
self.datumType = 'PartDesign::CoordinateSystem'
self.menutext = "New Coordinate System"
self.tooltip = "Create a new Coordinate System in a Part"
self.icon = os.path.join( Asm4.iconPath , 'Asm4_CoordinateSystem.svg')
self.datumColor = []
self.datumAlpha = []
elif self.datumName == 'Sketch':
self.datumType = 'Sketcher::SketchObject'
self.menutext = "New Sketch"
self.tooltip = "Create a new Sketch in a Part"
self.icon = os.path.join( Asm4.iconPath , 'Asm4_Sketch.svg')
self.datumColor = []
self.datumAlpha = []
def GetResources(self):
return {"MenuText": self.menutext,
"ToolTip": self.tooltip,
"Pixmap" : self.icon }
def IsActive(self):
if App.ActiveDocument:
# is something correct selected ?
if self.checkSelection():
return(True)
return(False)
def checkSelection(self):
# if something is selected ...
if Gui.Selection.getSelection():
selectedObj = Gui.Selection.getSelection()[0]
# ... and it's an App::Part or an datum object
selType = selectedObj.TypeId
if selType in self.containers or selType in Asm4.datumTypes or selType=='Sketcher::SketchObject':
return(selectedObj)
# or of nothing is selected ...
elif Asm4.getAssembly():
# ... but there is as assembly:
return Asm4.getAssembly()
# if we're here it's because we didn't find a good reason to not be here
return None
"""
+-----------------------------------------------+
| the real stuff |
+-----------------------------------------------+
"""
def Activated(self):
# check that we have somewhere to put our stuff
selectedObj = self.checkSelection()
# default name increments the datum type's end numeral
proposedName = Asm4.nextInstance( self.datumName, startAtOne=True )
parentContainer = None
# check whether we have selected a container
if selectedObj.TypeId in self.containers:
parentContainer = selectedObj
# if a datum object is selected
elif selectedObj.TypeId in Asm4.datumTypes or selectedObj.TypeId=='Sketcher::SketchObject':
# see whether it's in a container
parent = selectedObj.getParentGeoFeatureGroup()
if parent.TypeId in self.containers:
parentContainer = parent
# if there is an assembly
elif Asm4.getAssembly():
parentContainer = Asm4.getAssembly()
# something went wrong
else:
Asm4.warningBox("I can't create a "+self.datumType+" with the current selections")
# check whether there is already a similar datum, and increment the instance number
# instanceNum = 1
#while App.ActiveDocument.getObject( self.datumName+'_'+str(instanceNum) ):
# instanceNum += 1
#datumName = self.datumName+'_'+str(instanceNum)
if parentContainer:
# input dialog to ask the user the name of the Sketch:
#proposedName = Asm4.nextInstance( self.datumName + '_' + selectedObj.Label, startAtOne=True )
text,ok = QtGui.QInputDialog.getText(None,'Create new '+self.datumName,
'Enter '+self.datumName+' name :'+' '*40, text = proposedName)
if ok and text:
# App.activeDocument().getObject('Model').newObject( 'Sketcher::SketchObject', text )
createdDatum = App.ActiveDocument.addObject( self.datumType, text )
parentContainer.addObject( createdDatum )
createdDatum.Label = text
# automatic resizing of datum Plane sucks, so we set it to manual
if self.datumType=='PartDesign::Plane':
createdDatum.ResizeMode = 'Manual'
createdDatum.Length = 100
createdDatum.Width = 100
elif self.datumType=='PartDesign::Line':
createdDatum.ResizeMode = 'Manual'
createdDatum.Length = 200
# if color or transparency is specified for this datum type
if self.datumColor:
Gui.ActiveDocument.getObject(createdDatum.Name).ShapeColor = self.datumColor
if self.datumAlpha:
Gui.ActiveDocument.getObject(createdDatum.Name).Transparency = self.datumAlpha
# highlight the created datum object
Gui.Selection.clearSelection()
Gui.Selection.addSelection( App.ActiveDocument.Name, parentContainer.Name, createdDatum.Name+'.' )
Gui.runCommand('Part_EditAttachment')
"""
+-----------------------------------------------+
| a class to create an LCS on a hole |
+-----------------------------------------------+
"""
class newHole:
def GetResources(self):
return {"MenuText": "New Hole Axis",
"ToolTip": "Create a Datum Axis attached to a hole",
"Pixmap" : os.path.join( Asm4.iconPath , 'Asm4_Hole.svg')
}
def IsActive(self):
selection = self.getSelectedEdges()
if selection is None:
return False
else:
return True
"""
+-----------------------------------------------+
| the real stuff |
+-----------------------------------------------+
"""
def getSelectedEdges(self):
# check that we have selected only circular edges
selection = None
parent = None
edges = []
# 1 selection means a single parent
if App.ActiveDocument and len(Gui.Selection.getSelection()) == 1:
parent = Gui.Selection.getSelection()[0]
# parse all sub-elemets of the selection
for i in range(len(Gui.Selection.getSelectionEx()[0].SubObjects)):
edgeObj = Gui.Selection.getSelectionEx()[0].SubObjects[i]
edgeName = Gui.Selection.getSelectionEx()[0].SubElementNames[i]
# if the edge is circular
if Asm4.isCircle(edgeObj):
edges.append( [edgeObj,edgeName] )
# if we found circular edges
if len(edges) > 0:
selection = ( parent, edges )
return selection
def Activated(self):
( selectedObj, edges ) = self.getSelectedEdges()
for i in range(len(edges)):
edgeObj = edges[i][0]
edgeName = edges[i][1]
parentPart = selectedObj.getParentGeoFeatureGroup()
# we can create a datum only in a container
if parentPart:
parentDoc = parentPart.Document
# if the solid having the edge is indeed in an App::Part
if parentPart and (parentPart.TypeId=='App::Part' or parentPart.TypeId=='PartDesign::Body'):
# check whether there is already a similar datum, and increment the instance number
instanceNum = 1
while parentDoc.getObject( 'HoleAxis_'+str(instanceNum) ):
instanceNum += 1
axis = parentPart.newObject('PartDesign::Line','HoleAxis_'+str(instanceNum))
axis.Support = [( selectedObj, (edgeName,) )]
axis.MapMode = 'AxisOfCurvature'
axis.MapReversed = False
axis.ResizeMode = 'Manual'
axis.Length = edgeObj.BoundBox.DiagonalLength
axis.ViewObject.ShapeColor = (0.0,0.0,1.0)
axis.ViewObject.Transparency = 50
axis.recompute()
parentPart.recompute()
#
else:
FCC.PrintMessage('Datum objects can only be created inside Part or Body containers')
"""
+-----------------------------------------------+
| add the commands to the workbench |
+-----------------------------------------------+
"""
Gui.addCommand( 'Asm4_newPoint', newDatum('Point') )
Gui.addCommand( 'Asm4_newAxis', newDatum('Axis') )
Gui.addCommand( 'Asm4_newPlane', newDatum('Plane') )
Gui.addCommand( 'Asm4_newLCS', newDatum('LCS') )
Gui.addCommand( 'Asm4_newSketch',newDatum('Sketch'))
Gui.addCommand( 'Asm4_newHole', newHole() )
# defines the drop-down button for Datum objects
createDatumList = [ 'Asm4_newLCS',
'Asm4_newPlane',
'Asm4_newAxis',
'Asm4_newPoint',
'Asm4_newHole' ]
Gui.addCommand( 'Asm4_createDatum', Asm4.dropDownCmd( createDatumList, 'Create Datum Object'))

View file

@ -0,0 +1,17 @@
import FreeCAD as App
def is_object_solid(obj):
"""If obj is solid return True"""
if not isinstance(obj, FreeCAD.DocumentObject):
return False
if not hasattr(obj, 'Shape'):
return False
return obj.Shape.isClosed()
def addProperty():
for obj in App.ActiveDocument().Objects:
if is_object_solid(obj):

View file

View file

@ -0,0 +1,53 @@
import FreeCAD as App
import FreeCADGui as Gui
# App.newDocument()
doc = App.ActiveDocument
print('задайте начальную точку захватной зоны. ')
print('Учтите, что ось X должна быть направлена вдоль направления раскрытия пальцев')
print('Ось Z должна быть направлена в противоположную сторону от направления кончика пальца захвата')
lcsname = input('Введите название начальной точки' + "\n")
lcs = doc.getObject(lcsname)
def poseGenerator(lcs):
box = doc.addObject("Part::Box", "gripSpace")
box.Length = 62 #раскрытие
box.Width = 10 #ширина пальца
box.Height = 40 #глубина
box.Placement = lcs.Placement
#box.Transparency = 80 #не работает, хз почему
#есть смысл создавать привязку прямо здесь же. благодаря параметризации, при подстройке куба все точки сместятся как надо
gripPose = App.ActiveDocument.addObject('PartDesign::CoordinateSystem', 'GripPose')
gripPose.Support = box
gripPose.MapMode = 'ObjectXY'
gripPose.AttachmentOffset.Base = [box.Length/2, box.Width/2, 0] #здесь должна быть активная привязка, не просто значения координат
gripPose.addProperty("App::PropertyFloat", "GripOpen")
gripPose.addProperty("App::PropertyFloat", "GripDepth")
gripPose.addProperty("App::PropertyFloat", "GripWidth")
gripPose.GripOpen = box.Length.Value
gripPose.GripWidth = box.Width.Value
gripPose.GripDepth = box.Height.Value
#нужно создавать обьект внутри Part, а не внутри главного документа. сбиваются привязки !!!
print('Установите захватную зону вручную, растянув обьект GripSpace')
doc.recompute()
#вроде как работает
poseGenerator(lcs)

View file

@ -0,0 +1,98 @@
import os
import subprocess
import time
import FreeCAD
import Mesh
import gcoder
def export_to_stl(doc, output_dir):
objects = doc.Objects
for obj in objects:
if isinstance(obj, Part.Feature):
stl_path = os.path.join(output_dir, obj.Label + ".stl")
mesh = obj.Shape.tessellate(0.1)
# Создаем меш из геометрии объекта
mesh_obj = Mesh.Mesh(mesh)
# Проверяем, что меш содержит данные
if len(mesh_obj.Points) > 0:
# Экспортируем меш в формат STL
mesh_obj.write(stl_path)
def create_gcode(stl_dir, output_dir):
stl_files = [f for f in os.listdir(stl_dir) if f.endswith(".stl")]
for stl_file in stl_files:
stl_path = os.path.join(stl_dir, stl_file)
gcode_file = stl_file.replace(".stl", ".gcode")
gcode_path = os.path.join(output_dir, gcode_file)
# Команда для запуска Slic3r (в Ubuntu используется slic3r-пакет)
cmd = ["slic3r", "--load", "/home/mark-voltov/my_config.ini", "-o", gcode_path, stl_path]
# Запускаем Slic3r
subprocess.run(cmd)
def get_print_duration(gcode_dir):
gcoder.G
gcode_files = [f for f in os.listdir(gcode_dir) if f.endswith(".gcode")]
total_duration = 0
for gcode_file in gcode_files:
gcode_path = os.path.join(gcode_dir, gcode_file)
with open(gcode_path, "r") as file:
lines = file.readlines()
for line in lines:
if line.startswith("; estimated printing time"):
duration = float(line.split(":")[1].strip()) * 60
total_duration += duration
break
return total_duration
file_location = App.ActiveDocument.FileName
file_name = os.path.basename(file_location ) #eds_report.csv
location = os.path.dirname(file_location )
# Путь к документу FreeCAD
doc_path = file_location
# Каталог для сохранения STL-файлов
stl_dir = location + '/stl'
# Каталог для сохранения G-code файлов
gcode_dir = location + '/gcode'
# Открываем документ FreeCAD
doc = FreeCAD.open(doc_path)
# Экспортируем модель в файлы STL
export_to_stl(doc, stl_dir)
print("STL файлы успешно созданы.")
# Создаем G-code файлы с помощью Slic3r
create_gcode(stl_dir, gcode_dir)
print("G-code файлы успешно созданы.")
# Получаем оценочную длительность печати
print_duration = get_print_duration(gcode_dir)
print("Оценочная длительность печати: {} секунд.".format(print_duration))
# Закрываем документ FreeCAD
FreeCAD.closeDocument(doc)

View file

@ -0,0 +1,77 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# faculSpread.py
#
# Copyright 2015 ulrich1a
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
#
p=App.ParamGet("User parameter:BaseApp/Preferences/General")
thePath = p.GetString('FileOpenSavePath')
theDoc = App.newDocument("SheetTest2")
App.setActiveDocument("SheetTest2")
mySheet = theDoc.addObject('Spreadsheet::Sheet','Spreadsheet')
col = 'A'
mySheet.setColumnWidth(col, 125)
theDoc.recompute()
startRow = 3
for i in range(20):
print "i: ", i
if i == 0:
print "setting start"
mySheet.set(col+str(startRow+i), '1')
#App.activeDocument().recompute()
else:
mySheet.set(col + str(startRow+i), '='+col+str(startRow+i-1)+'*'+str(i))
#App.activeDocument().recompute()
#mySheet.show()
#App.activeDocument().recompute()
mySheet.set('A1', 'This is the very long Titel, which needs more space!')
mySheet.set('A2', '=A1')
# mySheet.setDisplayUnit('A10:A11', 'mm')
theDoc.recompute()
mySheet.setAlias('A1', 'Title')
mySheet.set('D1', '=Title')
theDoc.recompute()
theDoc.saveAs(thePath + '/Sheet_6.fcstd')
mySheet.set('B4','=A4')
mySheet.set('E1', '=D1')
theDoc.recompute()
mySheet.setAlias('A2', 'huhu')
theDoc.recompute()
if mySheet.State == ['Invalid']:
print "Invalid Spreadsheet"
else:
print "No error found"

View file

@ -0,0 +1,53 @@
import FreeCAD as App
class Asm4StructureParseUseCase:
_parts = []
_label = []
def getSubPartsLabel(self, group):
groupLabel = []
for el in group:
if str(el) == '<Part::PartFeature>':
groupLabel.append(el.Label)
return groupLabel
def parseLabel(self, nextGroup, label, level=2, nextGroupParse=0):
if nextGroup.__len__() == nextGroupParse:
return
else:
groupParts = []
for el in nextGroup:
if str(el) == '<App::Link object>':
groupParts.append(el)
for el in groupParts:
if str(el) == '<App::Link object>':
label.append({
"level": level,
"attachedTo": el.AttachedTo.split('#'),
"label": el.Label,
"axis": self.getSubPartsLabel(el.Group)
})
def initParse(self):
model = App.ActiveDocument.RootObjects[1]
self._label.append({
"level": 1,
"attachedTo": "Parent Assembly",
"label": model.Label,
"axis": self.getSubPartsLabel(model.Group)
})
for parent in model.Group:
if str(parent) == '<App::Link object>':
self._label.append({
"level": 1,
"attachedTo": parent.AttachedTo.split('#'),
"label": parent.Label,
"axis": self.getSubPartsLabel(parent.Group)
})
print(self._label)

View file

@ -0,0 +1,60 @@
# Модуль технологической подготовки
На вход: - step-модель описываемого оборудования.
1. Описание имеющихся сущностей:
- Мы можем создать описание действий со станком и его состояниями для связи с окружающим миром нужен некий обобщенный объект
его свойством будет возможность воздействовать на состояния, но сам он будет любым. Думаю, нужно описать сами рычаги, а не того, кто будет их нажимать.
- Описать объекты можно созданием зон вокруг них и/или созданием призрака геометрии оборудования (может быть сложно и избыточно).
- Для этого возможно создать параллелепипед/цилиндр, сопоставимый по габаритам с рассматриваемой зоной. С помощью инструментов верстака Part создается объект, имеющий желаемое название (Станок, рабочая зона, etc). Эта зона с помощью отношений parent привязана к геометрии станка.
2. Описание действий
- Создается папка actions, в которую сохраняются создаваемые действия
- Интерфейс создания действий аналогичен интерфейсу задания других сущностей. Через него мы задаем ссылки на существующие действия и элементы и указываем их тип: триггеры, рабочие органы, конечные состояния, начальные состояния. Указываем их статус (выбор "да/нет")
- Указываем ссылки на привязываемую к этим действиям геометрию. (катушки, статоры, расходники и тд) Для этого их геометрия должна быть импортирована в модель. Ссылка будет указывать на конкретный импортированный файл. Если существует идентификатор детали, можно ссылаться на него.
3. Задание состояний и переменных
- Переменные задаются средствами параметризации FreeCAD, инструменты для этого можно взять из ASM4, используя функционал переменных (Variables) и панели конфигураций.
- Для состояний переменных аналогично создается (в ASM4 уже имеется при создании модели) отдельная директория.
4. Результатом описания будет модель, имеющая дерево объектов, в свойствах которых мы имеем всю необходимую информацию. Геометрические характеристики мы сохраняем как json и отправляем в среды, работающие с геометрией и физикой. Действия и геометрия подставляются в шаблон pddl в соответствующие абзацы.
## Пример описания объекта
Action - "Заправка 3д-принтера пластиком"
- |- Объекты:
- - 3d-принтер [printer_id] /прямоугольная зона по габаритам принтера. Зона привязана к геометрии оборудования
- Workzone [printer_id] / прямоугольная зона. Указание на объект workzone, который содержит в себе габариты и позиционирование рабочей зоны относительно 3d-принтера.
- Wirenest [printer_id] /цилиндрическая зона. Указание на объект wirenest (цилиндр), хранящий информацию об ориентации и положении гнезда для катушки с пластиком
- Filament [filament_id] /катушка с пластиком требуемой модели, формы и габаритов.
- Observer [observer_id] / некая сущность(манипулятор, человек, камера), к которой обращается станок, чтобы с ним провели внешние манипуляции
- |- Длительность действия, с
- |- Стартовые состояния:
- Пластика достаточно (нет)
- Наблюдатель свободен (да)
- |- Во время действия:
- Наблюдатель[observer_id] свободен (нет)
- Катушка пластика установлена (нет)
- |- После окончания:
- Катушка пластика установлена (да)
- Наблюдатель [observer_id] свободен (да)
- Пластика достаточно (да)
--В раздел Variables мы можем (должны ли?) полуавтоматически/автоматически указать подобные состояния, привязанные к значениям да/нет.-- (Указывать стартовые значения по умолчанию?)
Указанные отдельно состояния пригодились бы, чтобы ссылаться на них при задавании действий, поскольку действия сообщаются между собой не напрямую, а через выполнение определенного набора состояний.
Пример размеченной модели:
![Разметка](img/qXX7sBMbsvA.jpg)