Export helper object from FreeCAD: grasp poses, part's zero positions, planar (tables, surfaces) and volumetric zones (storage, boxes)
This commit is contained in:
parent
a38c3bec5a
commit
9fa936cfba
26 changed files with 12259 additions and 2 deletions
286
cg/freecad/Frames/AuxObjCreation.py
Normal file
286
cg/freecad/Frames/AuxObjCreation.py
Normal 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'))
|
110
cg/freecad/Frames/BoMList.py
Normal file
110
cg/freecad/Frames/BoMList.py
Normal 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)
|
49
cg/freecad/Frames/DatumCommand.py
Normal file
49
cg/freecad/Frames/DatumCommand.py
Normal 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())
|
|
@ -11,9 +11,11 @@
|
||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# 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/>.
|
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
from BoMList import run_BoM_list
|
||||||
import FreeCAD
|
import FreeCAD
|
||||||
import Tools
|
import Tools
|
||||||
from scenarios.robossembler_freecad_export_scenario import RobossemblerFreeCadExportScenario
|
from usecases.asm4parser_usecase import Asm4StructureParseUseCase
|
||||||
|
|
||||||
|
|
||||||
if FreeCAD.GuiUp:
|
if FreeCAD.GuiUp:
|
||||||
import FreeCADGui
|
import FreeCADGui
|
||||||
|
@ -298,6 +300,13 @@ def spawnFeatureFrameCreator():
|
||||||
ffpanel = FeatureFramePanel()
|
ffpanel = FeatureFramePanel()
|
||||||
FreeCADGui.Control.showDialog(ffpanel)
|
FreeCADGui.Control.showDialog(ffpanel)
|
||||||
|
|
||||||
|
def BoMGeneration(part):
|
||||||
|
|
||||||
|
|
||||||
|
obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython",
|
||||||
|
"FeatureFrame")
|
||||||
|
print(obj)
|
||||||
|
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
# GUI Related
|
# GUI Related
|
||||||
|
@ -306,6 +315,12 @@ uidir = os.path.join(FreeCAD.getUserAppDataDir(),
|
||||||
"Mod", __workbenchname__, "UI")
|
"Mod", __workbenchname__, "UI")
|
||||||
icondir = os.path.join(uidir, "icons")
|
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",
|
Tools.spawnClassCommand("FrameCommand",
|
||||||
makeFrame,
|
makeFrame,
|
||||||
{"Pixmap": str(os.path.join(icondir, "frame.svg")),
|
{"Pixmap": str(os.path.join(icondir, "frame.svg")),
|
||||||
|
|
105
cg/freecad/Frames/ImportExportEntities.py
Normal file
105
cg/freecad/Frames/ImportExportEntities.py
Normal 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()
|
||||||
|
|
|
@ -36,10 +36,13 @@ class Frames(Workbench):
|
||||||
"""This function is executed when FreeCAD starts"""
|
"""This function is executed when FreeCAD starts"""
|
||||||
import Frames
|
import Frames
|
||||||
self.framecommands = [
|
self.framecommands = [
|
||||||
|
"BoMGeneration",
|
||||||
"FrameCommand",
|
"FrameCommand",
|
||||||
|
|
||||||
"SelectedPartFrameCommand",
|
"SelectedPartFrameCommand",
|
||||||
"AllPartFramesCommand",
|
"AllPartFramesCommand",
|
||||||
"FeatureFrameCommand"
|
"FeatureFrameCommand",
|
||||||
|
|
||||||
]
|
]
|
||||||
self.toolcommands = [
|
self.toolcommands = [
|
||||||
"ExportPlacementAndPropertiesCommand",
|
"ExportPlacementAndPropertiesCommand",
|
||||||
|
|
0
cg/freecad/Frames/MetaObj.py
Normal file
0
cg/freecad/Frames/MetaObj.py
Normal file
60
cg/freecad/Frames/Sheet_addition_test.py
Normal file
60
cg/freecad/Frames/Sheet_addition_test.py
Normal 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()
|
||||||
|
|
|
@ -184,11 +184,23 @@ def getLocalPartProps(obj):
|
||||||
"label": obj.Label,
|
"label": obj.Label,
|
||||||
"parent_label": obj_parent_label,
|
"parent_label": obj_parent_label,
|
||||||
"placement": placement2pose(old_placement),
|
"placement": placement2pose(old_placement),
|
||||||
|
# "grip properties": { "grip open": obj.GripOpen, "grip depth": obj.GripDepth, "grip width": obj.GripWidth}
|
||||||
# "boundingbox": boundingBox2list(obj.Shape.BoundBox),
|
# "boundingbox": boundingBox2list(obj.Shape.BoundBox),
|
||||||
# "volume": obj.Shape.Volume*1e-9,
|
# "volume": obj.Shape.Volume*1e-9,
|
||||||
# "centerofmass": vector2list(obj.Shape.CenterOfMass),
|
# "centerofmass": vector2list(obj.Shape.CenterOfMass),
|
||||||
# "principalproperties": principalProperties2dict(obj.Shape.PrincipalProperties)
|
# "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
|
# obj.Placement = old_placement
|
||||||
return partprops
|
return partprops
|
||||||
|
|
||||||
|
|
72
cg/freecad/Frames/UI/icons/BoMList.svg
Normal file
72
cg/freecad/Frames/UI/icons/BoMList.svg
Normal 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 |
240
cg/freecad/Frames/UI/icons/auxDatum.svg
Normal file
240
cg/freecad/Frames/UI/icons/auxDatum.svg
Normal 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
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
0
cg/freecad/Frames/box.py
Normal file
31
cg/freecad/Frames/file.py
Normal file
31
cg/freecad/Frames/file.py
Normal 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')
|
60
cg/freecad/Frames/freecad2pddl.py
Normal file
60
cg/freecad/Frames/freecad2pddl.py
Normal 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
769
cg/freecad/Frames/gcoder.py
Normal 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()
|
BIN
cg/freecad/Frames/img/qXX7sBMbsvA.jpg
Normal file
BIN
cg/freecad/Frames/img/qXX7sBMbsvA.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
131
cg/freecad/Frames/markupEntities.py
Normal file
131
cg/freecad/Frames/markupEntities.py
Normal 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)
|
||||||
|
|
||||||
|
|
259
cg/freecad/Frames/newDatumCmd.py
Normal file
259
cg/freecad/Frames/newDatumCmd.py
Normal 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'))
|
17
cg/freecad/Frames/newLabel.py
Normal file
17
cg/freecad/Frames/newLabel.py
Normal 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):
|
||||||
|
|
0
cg/freecad/Frames/normalEstimator
Normal file
0
cg/freecad/Frames/normalEstimator
Normal file
53
cg/freecad/Frames/poseGenerator.py
Normal file
53
cg/freecad/Frames/poseGenerator.py
Normal 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)
|
||||||
|
|
98
cg/freecad/Frames/printETA.py
Normal file
98
cg/freecad/Frames/printETA.py
Normal 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)
|
||||||
|
|
77
cg/freecad/Frames/testSpread2.py
Normal file
77
cg/freecad/Frames/testSpread2.py
Normal 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"
|
53
cg/freecad/Frames/usecases/asm4parser_usecase.py
Normal file
53
cg/freecad/Frames/usecases/asm4parser_usecase.py
Normal 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
60
cg/freecad/Frames/Модуль технологической подготовки.md
Normal file
60
cg/freecad/Frames/Модуль технологической подготовки.md
Normal 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 мы можем (должны ли?) полуавтоматически/автоматически указать подобные состояния, привязанные к значениям да/нет.-- (Указывать стартовые значения по умолчанию?)
|
||||||
|
|
||||||
|
|
||||||
|
Указанные отдельно состояния пригодились бы, чтобы ссылаться на них при задавании действий, поскольку действия сообщаются между собой не напрямую, а через выполнение определенного набора состояний.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Пример размеченной модели:
|
||||||
|
|
||||||
|

|
Loading…
Add table
Add a link
Reference in a new issue